# EU medicines watch: EMA authorisations and shortages (`kittiwake/ema-medicines-watch`) Actor

EU medicines regulatory watch: authorisations, withdrawals and shortages for your substances, twice a day. Reads EMA's published JSON files, diffs against your last run and returns only what changed, matched against your substances, ATC codes and marketing-authorisation holders.

- **URL**: https://apify.com/kittiwake/ema-medicines-watch.md
- **Developed by:** [Kittiwake Data](https://apify.com/kittiwake) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 change returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## EU medicines watch: EMA authorisations and shortages

**EU medicines regulatory watch: authorisations, withdrawals and shortages for your substances, twice
a day.**

The European Medicines Agency publishes its full register of centrally authorised medicines, and its
list of supply shortages, as downloadable files that it refreshes about twice a day. The files are
public. What they do not tell you is **what changed since you last looked**.

This Actor reads both files, compares them with your previous run, and returns only the changes,
matched against the substances, ATC codes and companies you care about.

### What it is for

- **Generic and biosimilar developers**: see a reference product's withdrawal or a competitor's new
  authorisation the day EMA publishes it.
- **Distributors, parallel importers and hospital pharmacy buyers**: new and resolved shortages for
  the substances you handle.
- **Regulatory-intelligence and market-access teams**: a change feed by ATC class or by
  marketing-authorisation holder, into your own systems.

### What you get

| change | when |
|---|---|
| `new-medicine` | a product number appears in EMA's medicines file for the first time |
| `status-changed` | its status moved, e.g. `Opinion` → `Authorised`, `Authorised` → `Withdrawn`; `previousStatus` holds the old one |
| `updated` | a field you receive changed without a status change (e.g. additional monitoring added) |
| `removed-from-file` | a product number no longer appears in the file |
| `new-shortage`, `shortage-status-changed`, `shortage-updated`, `shortage-removed` | the same, for EMA's shortages file |
| `watchlist-baseline` | **first run only**: the current state of everything on your watchlist, so you start with a picture rather than an empty table |

A change to EMA's "last updated" date on its own is **not** returned: EMA touches that date without
changing anything in the fields you receive.

### Quick start

**Watch two substances and an ATC class, every change on your watchlist:**

```json
{
  "watchSubstances": ["semaglutide", "adalimumab"],
  "watchAtcCodes": ["L01FX"],
  "onlyWatchlistMatches": true
}
```

**Every change in the EU register, medicines only:**

```json
{ "includeShortages": false }
```

Schedule it **twice a day**. Each run compares with the last run of the same saved task, so two
tasks with different watchlists never consume each other's changes.

### Input

| field | type | default | what it does |
|---|---|---|---|
| `watchSubstances` | string\[] | — | Active substances or INNs. Case-insensitive substring |
| `watchAtcCodes` | string\[] | — | ATC codes. Prefix match: `L01` catches `L01FX22` |
| `watchHolders` | string\[] | — | Marketing-authorisation holders. Case-insensitive substring |
| `onlyWatchlistMatches` | boolean | `false` | Return only watchlist hits |
| `includeShortages` | boolean | `true` | Also read the shortages file |

### Output

One row per change. Every row has the same fields; the ones that do not apply are `null`.

```json
{
  "emaProductNumber": "EMEA/H/C/004127",
  "medicineName": "Zonisamide Viatris (previously Zonisamide Mylan)",
  "inn": "zonisamide",
  "activeSubstance": "zonisamide",
  "atcCode": "N03AX15",
  "category": "Human",
  "medicineStatus": "Withdrawn",
  "marketingAuthorisationHolder": "Viatris Limited",
  "generic": true,
  "withdrawalOrLapseDate": "2026-09-20",
  "medicineUrl": "https://www.ema.europa.eu/en/medicines/human/EPAR/zonisamide-viatris",
  "shortageStatus": null,
  "changeType": "status-changed",
  "previousStatus": "Authorised",
  "watchlistMatch": { "matchedOn": "substance", "value": "zonisamide" },
  "source": "Source: European Medicines Agency (EMA), https://www.ema.europa.eu/en/documents/report/medicines-output-medicines_json-report_en.json"
}
```

Also returned: `therapeuticArea`, `opinionStatus`, `biosimilar`, `orphan`, `conditionalApproval`,
`additionalMonitoring`, `opinionAdoptedDate`, `marketingAuthorisationDate`, `ecDecisionDate`,
`lastUpdatedDate`, and for shortages `shortageStart`, `expectedResolution` and
`alternativesAvailable`. Dates are ISO `YYYY-MM-DD`; a date EMA leaves blank is `null`.

### What you pay for

| event | price | when |
|---|---|---|
| **Change returned** | $0.01 | one change row written to the dataset |
| EMA file read | $0.01 | one file fetched, parsed and compared. Only when all of that succeeds |
| Watchlist match | $0.05 | a change matched your watchlist. Zero if you supply no watchlist |

Nothing is charged for a failed run. If a run reaches your spending limit, the changes it did not
return are kept and come back on the next run.

### What this Actor does not do

- **No personal data.** It returns medicine, substance, status and company fields only. EMA's
  free-text indication and any person-level field are never copied.
- **No logins, proxies or retries.** Two requests per run, ten seconds apart, with one identifying
  User-Agent. If EMA refuses a request, the run fails and says so; it does not try again.
- **No advice.** EMA's pages are the authority; every row links to the official page.

### Source and attribution

Data comes from the European Medicines Agency's published JSON files
(`ema.europa.eu/en/about-us/about-website/download-website-data-json-data-format`). This Actor
reformats and compares what EMA publishes and adds nothing to it.

EMA's legal notice allows reproduction *"for non-commercial and commercial purposes … provided that
EMA is always acknowledged as the source of the material. Such acknowledgement must be included in
each copy of the material."* So every row carries it, in the `source` field:

> *Source: European Medicines Agency (EMA), \<the EMA file URL the row came from>*

### Support

Use the **Issues** tab on this Actor. Include the run id and the input you used.

# Actor input Schema

## `watchSubstances` (type: `array`):

Matched case-insensitively as a substring of the active substance or INN, e.g. semaglutide, adalimumab.

## `watchAtcCodes` (type: `array`):

Matched as a prefix, so L01 catches every antineoplastic code under it.

## `watchHolders` (type: `array`):

Company names, matched case-insensitively as a substring, e.g. Pfizer, Teva.

## `onlyWatchlistMatches` (type: `boolean`):

Return only changes that hit your watchlist. Requires at least one entry above.

## `includeShortages` (type: `boolean`):

Also read EMA's shortages file (a second request, 10 seconds after the first).

## Actor input object example

```json
{
  "watchSubstances": [
    "semaglutide",
    "amoxicillin"
  ],
  "onlyWatchlistMatches": false,
  "includeShortages": true
}
```

# Actor output Schema

## `changes` (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 = {
    "watchSubstances": [
        "semaglutide",
        "amoxicillin"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kittiwake/ema-medicines-watch").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 = { "watchSubstances": [
        "semaglutide",
        "amoxicillin",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("kittiwake/ema-medicines-watch").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 '{
  "watchSubstances": [
    "semaglutide",
    "amoxicillin"
  ]
}' |
apify call kittiwake/ema-medicines-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kittiwake/ema-medicines-watch"
        }
    }
}
```

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/xOMI50pSmKUrKomVk/builds/Mc6Y9OQ3w89pMZ9Nn/openapi.json
