# EU Pharma Sites Graph - EudraGMDP Manufacturers & Distributors (`rastriq/eu-pharma-sites-graph`) Actor

Every EU/EEA pharma site from EMA EudraGMDP with all its authorisations and certificates (WDA, MIA, GMP, GDP, API) and status signals: active WDA, valid GDP, GMP non-compliance. Company graph by OMS ID.

- **URL**: https://apify.com/rastriq/eu-pharma-sites-graph.md
- **Developed by:** [Rastriq — Structured data from the world](https://apify.com/rastriq) (community)
- **Categories:** Lead generation, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 site returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## EU Pharma Sites Graph — every pharma factory and warehouse in Europe, with its licences

One row per **pharmaceutical site** (factory, warehouse, distributor premises) registered with the European Medicines Agency (EMA **EudraGMDP**), with **all its licences and certificates** and simple yes/no answers such as *"is it allowed to distribute medicines?"* or *"did it fail a factory inspection?"*.

No pharma background needed — this page explains everything in plain words.

***

### What is a site?

A **site** is one physical place where medicines or their ingredients are made, stored or shipped: a factory, a logistics warehouse, a hospital pharmacy that manufactures, a blood centre...

- One **company** can own many sites. Each site has an official ID (`LOC-…`) and belongs to a company ID (`ORG-…`).
- Each site can hold several **documents** from regulators:

| Document | Plain meaning |
|---|---|
| **WDA** | Licence to **store and distribute** medicines (wholesaler) |
| **MIA** | Licence to **manufacture or import** medicines |
| **GMP** | Certificate: the **factory passed** its inspection |
| **GDP** | Certificate: the **warehouse passed** its inspection (storage, temperature, traceability) |
| **API-REG** | Registration to make, import or trade **active ingredients** |

So a row tells you, for one building: *who owns it, where it is, what it's allowed to do, and whether its inspections are in order.*

```
Company  ORG-100010408  (BITRADE d.o.o.)
   └── Site LOC-100068134  Blatnica 12, Trzin, Slovenia
          ├── WDA licence 801-39/2021-11   Active   2022-02-22
          └── GDP certificate 402-16/2023-4 Active  2023-08-29
```

***

### How to run it (3 steps)

1. **Countries** → e.g. *Spain* (≈1,700 sites) or *All EEA countries* (≈24,000 sites, several hours).
2. Optionally **Company / site name contains** → e.g. `pharma`.
3. Click **Start**. Download as Excel, CSV or JSON from the *Output* tab.

Optional:

- **Include full licence details** — opens every document to add inspection dates, authorised activities and **special product types** (cold chain, vaccines, blood products, narcotics). 2–3 s per document.
- **Filters** — address, exact site ID, only sites with current licences, failed-inspection check.

***

### What you get: field-by-field guide

| Column | Plain meaning | Example |
|---|---|---|
| `siteName` | Name of the site / company at that location | `BITRADE d.o.o.` |
| `omsOrganisationId` | Official **company** ID — group by it to see a company's whole footprint | `ORG-100010408` |
| `omsLocationId` | Official **site** ID — best key to join with other data | `LOC-100068134` |
| `ncaReferenceKey` | The national authority's own reference for the site | `1648016744469` |
| `address.street / city / postcode / country / countryCode` | Where it is | `Trzin`, `1236`, `SI` |
| `documentCount` | How many current licences/certificates it has | `2` |
| `documents[]` | One entry per document: `type`, `number`, `nca` (issuing authority code), `status`, `issueDate`, `permalink` | see example |
| `documents[].status` | Document status, normalised (`Active`); `statusRaw` keeps the original spelling | `Active` |
| `documents[].detail` | Full document content, only with *Include full licence details* | |
| `nonComplianceStatements[]` | Failed factory inspections linked to this site | |
| `_flags` | Data-quality warnings (see below) | |

#### `signals` — ready-made yes/no answers

| Signal | Answers the question |
|---|---|
| `hasActiveWDA` | Is it licensed to **distribute** medicines right now? |
| `hasActiveMIA` | Is it licensed to **manufacture/import** right now? |
| `hasActiveGDP` | Does its **warehouse** hold a current inspection certificate? |
| `hasActiveGMP` | Does its **factory** hold a current inspection certificate? |
| `hasAPIReg` | Is it registered for **active ingredients**? |
| `hasNonCompliance` | Did it **fail** a factory (GMP) inspection that is still current? (`null` if the check was off or unavailable — never a guessed "no") |
| `documentTypes` | List of document types it holds |
| `latestIssueDate` | Most recent document date |
| `latestInspectionDate` | Most recent inspection (only with details) |

All signals are computed **only from data the source publishes**.

#### `_flags` — warnings, never filters

| Flag | Meaning |
|---|---|
| `no_documents` | The site exists in the register but lists no current licence (about 30% of sites). Kept so you decide |
| `encoding_suspect` | Garbled characters in the source address; kept as published |
| `address_unparsed` | City/postcode couldn't be separated |
| `uk_legacy_pre2021` | UK data frozen since Brexit (Northern Ireland excepted) |
| `detail_fetch_failed` | A document's details couldn't be loaded |
| `noncompliance_check_unavailable` | The failed-inspection check couldn't run this time |

***

### Example (real record)

```json
{
  "siteId": 190000,
  "siteName": "BITRADE podjetje za trgovino na debelo in drobno d.o.o.",
  "omsOrganisationId": "ORG-100010408",
  "omsLocationId": "LOC-100068134",
  "address": { "street": "Blatnica 12", "city": "Trzin", "postcode": "1236", "countryCode": "SI" },
  "documentCount": 2,
  "documents": [
    { "type": "WDA", "number": "801-39/2021-11", "nca": "SI_ARSZMP", "status": "Active", "issueDate": "2022-02-22" },
    { "type": "GDP", "number": "402-16/2023-4", "nca": "SI_ARSZMP", "status": "Active", "issueDate": "2023-08-29" }
  ],
  "signals": { "hasActiveWDA": true, "hasActiveGDP": true, "hasActiveMIA": false, "hasNonCompliance": false,
               "latestIssueDate": "2023-08-29" }
}
```

Reading it: *a Slovenian wholesaler, licensed to distribute medicines, whose warehouse passed its last inspection.* With details on, its licence also shows it handles **cold-chain products**.

***

### EU Pharma Sites Graph input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `countries` | array of strings | `["EEA"]` (pre-filled: `["ES"]`) | Where the sites are located. `EEA` = all EEA countries (about 24,000 sites, several hours), `ALL` = all countries (GMP/GDP include non-EEA sites), `THIRD` = only non-EEA countries; or any 2-letter country code. |
| `siteName` | string | empty | Company / site name contains (at least 3 characters, for example `pharma`). |
| `fetchDocumentDetails` | boolean | `false` | Include full licence details: opens every document to add issuing authority, inspection dates, authorised activities and special product types. About 2-3 seconds per document. |
| `maxItems` | integer (min 1) | `500` (pre-filled: `50`) | Stop after this many sites. |
| `legalAddress` | string | empty | Address contains (at least 3 characters): city, street, postcode. |
| `omsLocationId` | string | empty | Look up one exact site by its `LOC-...` ID. |
| `skipSitesWithoutDocuments` | boolean | `false` | Only sites with current licences: hides sites that list no licence or certificate (about 30% of sites). |
| `crossCheckNonCompliance` | boolean | `true` | Adds `signals.hasNonCompliance` from current GMP non-compliance statements. Adds about 3 minutes per run. |
| `documentTypes` | array of strings | `["WDA","GDP","MIA","GMP","API-REG"]` | With details on, open only these document types. |
| `startPage` | integer (min 1) | `1` | Resume a long run from this results page (20 sites per page). |
| `maxConcurrency` | integer (1-2) | `2` | Parallel requests, capped at 2 out of respect for the public EMA website. |
| `proxyConfiguration` | object | Apify Proxy off | Not needed: the source is public. |
| `scope` | string | empty | Legacy and hidden; use `countries` instead. |

Pre-filled input (the fastest way to try EU Pharma Sites Graph):

```json
{"countries": ["ES"], "maxItems": 50}
```

### Ready-to-use examples

Copy any of these into the **JSON** input tab, or open the matching saved example from the *Examples* tab.

**1. Every pharma site in Spain, licences grouped**

```json
{ "countries": ["ES"], "maxItems": 2000, "skipSitesWithoutDocuments": true }
```

**2. All sites of one company** (then group by `omsOrganisationId`)

```json
{ "countries": ["EEA"], "siteName": "Novo Nordisk" }
```

**3. Pharma sites in one city**

```json
{ "countries": ["AT"], "legalAddress": "Vienna", "maxItems": 500 }
```

**4. Check one supplier site by its official ID**

```json
{ "countries": ["ALL"], "omsLocationId": "LOC-100001653", "fetchDocumentDetails": true }
```

**5. Cold-chain / vaccine warehouses in the Netherlands** (details on WDA only)

```json
{ "countries": ["NL"], "fetchDocumentDetails": true, "documentTypes": ["WDA"], "skipSitesWithoutDocuments": true, "maxItems": 500 }
```

#### Example values for the empty fields

| Field | Try one of these |
|---|---|
| **Company / site name contains** | `Alliance Healthcare`, `Phoenix`, `Novo Nordisk`, `Sanofi`, `Sandoz`, `Teva`, `Zentiva`, `Takeda`, `Fresenius Kabi`, `Gedeon Richter`, `Octapharma`, `Linde` — or `Logistics`, `Pharma`, `Apotheke` |
| **Address contains** | `Vienna`, `Warsaw`, `Budapest`, `Prague`, `Berlin`, `Hamburg`, `Munich`, `Barcelona`, `Madrid`, `Amsterdam`, `Paris`, `Milan`, `Dublin` |
| **Site ID (LOC-…)** | `LOC-100000862` (Novo Nordisk, Hilleroed DK), `LOC-100001653` (Gedeon Richter, Debrecen HU), `LOC-100000528` (GSK Biologicals, Rixensart BE), `LOC-100005629` (Laboratorios Normon, Tres Cantos ES), `LOC-100068134` (BITRADE, Trzin SI) |

***

### Use cases for EU pharma site data (EudraGMDP)

- **Sales prospecting** — every licensed distributor and manufacturer in a country, by site.
- **Pharma logistics** — find warehouses with cold-chain or special products (with details).
- **Supplier checks** — is this site licensed and inspected? Any failed inspection?
- **Data enrichment** — join with your CRM or company registers via `ORG-…` / `LOC-…`.

### Run time

| Scenario | Typical time |
|---|---|
| Failed-inspection check (once per run) | ~3 min |
| One country, e.g. Spain (≈1,700 sites) | ~15–20 min |
| All EEA (≈24,000 sites) | ~4 h |
| With full details | +2–3 s per document |

### How much does it cost to scrape EU pharma sites from EudraGMDP?

Pay per result, no subscription. Prices per 1,000 events (Apify plan discounts applied automatically):

| Event | Free | Bronze | Silver | Gold+ |
|---|---|---|---|---|
| **Site returned** (one row, all documents grouped + signals) | $8.00 | $7.00 | $6.00 | $5.00 |
| **Document details loaded** (only with *Include full licence details*) | $3.00 | $2.50 | $2.00 | $1.50 |

Failed detail loads are never charged. Typical costs (Free plan):

| Run | Sites | Cost |
|---|---|---|
| One company or city | 10–200 | $0.08–$1.60 |
| Spain | ~1,700 | ~$13.60 |
| Whole EEA | ~24,000 | ~$190 |

**Worked example (BRONZE tier):** 100 sites with *Include full licence details* on, loading 150 documents: 100 x $0.007 = $0.70 for the sites, plus 150 x $0.0025 = $0.375 for the document details, plus the $0.00005 Actor start event (512 MB = one event). Total: about **$1.08**. Without details the same 100 sites cost about $0.70.

Set **Max sites** to cap your spend on any run.

### EudraGMDP source, limits and good to know

- **Source**: EudraGMDP is run by the EMA; content is provided by national authorities, and the EMA accepts no liability for it.
- The public site list shows **current documents only**: withdrawn licences and failed inspections are not attached by the source (we add failed factory inspections via a separate check; failed warehouse inspections are available in *EudraGMDP Pharma Registry*).
- **No personal data** is collected. We query the EMA website politely (max 2 parallel requests).

### FAQ

**Difference with EudraGMDP Pharma Registry?** That actor gives **one row per document** (and covers failed warehouse inspections and certificate dates in bulk); this one gives **one row per site** with everything grouped.

**How do I see all sites of one company?** Group rows by `omsOrganisationId`.

**How much does a test run cost?** The pre-filled input (Spain, 50 sites) on the Free plan is 50 x $0.008 = $0.40, plus the $0.00005 Actor start event. **Max sites** caps any run.

**Why is `hasNonCompliance` sometimes `null`?** It is `null` when the failed-inspection check was switched off or could not run (flag `noncompliance_check_unavailable`); it is never a guessed "no".

**How often should I run it?** The Actor reads the EMA website live on every run, so run it when you need current data. It has no change-tracking mode; for scheduled alerts on changed documents see *EudraGMDP Pharma Registry*, which offers *Return only changes*.

**Does it need proxies or collect personal data?** No proxy is needed (the source is public) and no personal data is collected.

**Something looks wrong?** Open an issue from the Actor page and include the `omsLocationId` of the site.

### Related Rastriq actors

- [EudraGMDP Pharma Registry (EMA)](https://apify.com/rastriq/eudragmdp-pharma-registry) - one row per licence or certificate, including failed warehouse inspections.
- [FDA Drug Establishments (DECRS)](https://apify.com/rastriq/fda-drug-establishments-delta) - drug establishments registered with the US FDA, with daily changes and removals.
- [Europages Scraper](https://apify.com/rastriq/europages-scraper) - European B2B company directory.

# Actor input Schema

## `countries` (type: `array`):

Choose where the sites are located. <b>All EEA countries</b> is about 24,000 sites (several hours). You can also type any other 2-letter country code.

## `siteName` (type: `string`):

Optional, at least 3 characters (e.g. <code>pharma</code>). Pick a suggestion or type any text.

## `fetchDocumentDetails` (type: `boolean`):

Opens every document to add: issuing authority, inspection dates, authorised activities and <b>special product types</b> (cold chain, vaccines/immunologicals, blood products, narcotics...).<br>About 2-3 seconds per document: great for one country, slow for all of Europe.

## `maxItems` (type: `integer`):

Stop after this many sites. Use a small number for your first test.

## `legalAddress` (type: `string`):

At least 3 characters: city, street, postcode... Pick a city or type a street / postcode.

## `omsLocationId` (type: `string`):

Look up one exact site, e.g. <code>LOC-100068134</code>. Pick an example site or paste any <code>LOC-…</code> ID (found in the <code>omsLocationId</code> column).

## `skipSitesWithoutDocuments` (type: `boolean`):

Hide sites that currently list no licence or certificate (about 30% of sites).

## `crossCheckNonCompliance` (type: `boolean`):

Adds <code>signals.hasNonCompliance</code> from current GMP non-compliance statements (the site list does not show them). Adds about 3 minutes per run.

## `documentTypes` (type: `array`):

When details are on, open only these document types (saves time).

## `startPage` (type: `integer`):

Resume a long run from this results page (20 sites per page).

## `maxConcurrency` (type: `integer`):

Capped at 2 out of respect for the public EMA website.

## `proxyConfiguration` (type: `object`):

Not needed: the source is public.

## `scope` (type: `string`):

Deprecated: use Countries (EEA / ALL / THIRD).

## Actor input object example

```json
{
  "countries": [
    "ES"
  ],
  "fetchDocumentDetails": false,
  "maxItems": 50,
  "skipSitesWithoutDocuments": false,
  "crossCheckNonCompliance": true,
  "documentTypes": [
    "WDA",
    "GDP",
    "MIA",
    "GMP",
    "API-REG"
  ],
  "startPage": 1,
  "maxConcurrency": 2,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `sites` (type: `string`):

No description

## `runStats` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "countries": [
        "ES"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("rastriq/eu-pharma-sites-graph").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "countries": ["ES"],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("rastriq/eu-pharma-sites-graph").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "countries": [
    "ES"
  ],
  "maxItems": 50
}' |
apify call rastriq/eu-pharma-sites-graph --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rastriq/eu-pharma-sites-graph"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/f46Pi9B9Cuc79MUyK/builds/5rch2gvNDUr6LhR6U/openapi.json
