# Europe PMC Scraper (`scrapyx/europepmc-scraper`) Actor

Search Europe PMC, the superset of PubMed: 43M+ abstracts, 10M+ open-access full-text articles, preprints (bioRxiv/medRxiv), patents and theses. Lucene query plus filters, cursor pagination with no ceiling, lookup by PMID/PMCID/DOI, citation networks, text-mined annotations. Keyless.

- **URL**: https://apify.com/scrapyx/europepmc-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 results

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?

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

## Europe PMC Scraper (Biomedical Papers, Preprints, Citations)

Search **[Europe PMC](https://europepmc.org)** — the superset of PubMed.
**43M+ abstracts** (`MED`, i.e. PubMed/MEDLINE), **10M+ open-access full-text
articles** (`PMC`), **preprints** from bioRxiv and medRxiv (`PPR`), plus
patents, Agricola, Chinese biomedical, theses and the NCBI bookshelf. No key,
no login, HTTP-only.

Companion to `pubmed-articles-scraper` (PubMed only), `openalex-scholar-scraper`
(works), `crossref-works-scraper` (articles) and `datacite-scraper` (datasets
and software).

- **`search`** — a Lucene `query` and/or structured filters (sources, author,
  journal, publication type, year range, open access, full text in Europe PMC,
  has-abstract). **Cursor-paginated** — no result ceiling.
- **`articles`** — look up specific articles by **PMID** (`23000000`),
  source-qualified id (`MED:23000000`, `PMC:PMC3531190`, `PPR:PPR1303040`) or
  **DOI** → the full `core` record each.
- **`citation-network`** — walk an article's citations **forwards**
  (`citing` — who cites it) or **backwards** (`cited` — its reference list).

Set `includeAnnotations` to attach Europe PMC's **text-mined annotation
layer** — genes/proteins, diseases, chemicals, organisms, cell types,
accession numbers, extracted from the full text.

| Record type | One per | Carries |
| --- | --- | --- |
| `SEARCH_SUMMARY` | search / citation walk | composed `query`, `hitCount`, `resultsReturned`, `pagesFetched`, `requestsMade`, `duplicatesSkipped`, `truncated` |
| `ARTICLE` | article | `id`, `source`, `pmid`, `pmcid`, `doi`, `title`, `abstract`, `authors` (+ ORCID + affiliation), `journal`, `pubYear`, `citedByCount`, `isOpenAccess`, `license`, `meshTerms`, `chemicals`, `keywords`, `grants`, `fullTextUrls`, `textMinedAnnotations`, `europepmcUrl` |
| `ERROR` | bad input / missing article | `_error` + `_errorDetail` |

Every `ARTICLE` row carries the verbatim Europe PMC record in `raw` (drop with
`slimOutput`).

### Things this API will mislead you about

Each is measured, and each has a scenario in
`tests/smoke/europepmc-scraper_traps.sh` (10/10 passing).

**⭐ The citation endpoints page by `page` *and* `pageSize` — and `page` indexes
pages of the size you asked for.** Shrinking `pageSize` on the last page to
avoid over-fetching silently re-serves rows you already have:
`page=1&pageSize=100` then `page=2&pageSize=50` returns rows 51–100 a second
time, HTTP 200 throughout, with no duplicate signal anywhere in the response.
Measured 2026-08-29 — 150 rows of which only 100 were unique. This Actor holds
`pageSize` constant for the whole walk, trims at the end, and reports
`duplicatesSkipped` so any future reshuffle is visible rather than silent.

**Every response is HTTP 200** — a malformed query, a gibberish query and a
non-existent article all answer 200. There is no "bad query" signal, so a
zero-hit search is reported as a `SEARCH_SUMMARY` with `hitCount: 0`, never as
an error, and an empty run stays distinguishable from a failed one.

**An unknown `SRC` returns zero hits, silently.** `sources` is validated
against Europe PMC's controlled list up front rather than becoming a mystery
empty result.

**`/search` has no `page` parameter** — deep paging is `cursorMark` only
(`*`, then `nextCursorMark`). `pageSize` maxes at 1000; asking for 1001
returns 200 with an *empty* result list.

**There is no `/article/<id>` endpoint.** A single article is looked up
through `search?query=EXT_ID:<id> AND SRC:<src>` (or `DOI:<doi>`), which is
why a missing article is an empty result list rather than a 404.

**A DOI has no citation endpoint.** `citation-network` needs a PMID / `MED:` /
`PMC:` id; a DOI is rejected before any request instead of returning nothing.

**The list fields are `{singular: [...]}` wrappers that can each be `null`** —
`meshHeadingList.meshHeading[]`, `grantsList.grant[]`, `chemicalList.chemical[]`,
`keywordList.keyword[]`, `authorList.author[]`, `fullTextUrlList.fullTextUrl[]`,
`pubTypeList.pubType[]`. All are unwrapped to plain lists.

**`isOpenAccess` / `inEPMC` / `hasPDF` are the strings `"Y"` / `"N"`**, not
booleans. Normalised to real booleans.

**`id` is source-specific** — a `MED` record's `id` is its PMID, a `PMC`
record's is its PMCID, a `PPR` record's is `PPR…`. `pmid` / `pmcid` / `doi`
are separate fields, and any of them can be absent.

**`citations` / `references` rows are lean** — title, authors, year, journal
abbreviation, id, source, cited-by count. No abstract. Feed the ids back
through `articles` mode if you need the full records.

### Notes on cost

`search`: one request per 100 rows (cursor pages). `articles`: one request per
article, plus one more each if `includeAnnotations` is on. `citation-network`:
one request per 100 linked articles. `resultType: "lite"` returns a much
smaller row when you only need bibliographic fields. Europe PMC has no
anti-bot layer, so no proxy is needed — the default is off.

### Policy

`www.ebi.ac.uk/robots.txt` allows `/europepmc/webservices/rest/…`. No key, no
login, no authenticated surface is touched. Europe PMC content is a mix of
licences — each row's `isOpenAccess` and `license` say which.

# Actor input Schema

## `mode` (type: `string`):

`search` runs a query / filters against the whole Europe PMC index. `articles` looks up specific articles by id. `citation-network` walks the citations of an article (forwards or backwards).

## `query` (type: `string`):

`search` mode. Lucene syntax against title / abstract / full text, e.g. `CRISPR base editing`, `"long covid"`, `TITLE:"gut microbiome"`. Optional if you supply filters instead. Every query returns HTTP 200 -- a zero-hit result is a genuine `hitCount: 0`, not an error.

## `articleIds` (type: `array`):

`articles` and `citation-network` modes. One per line. Accepts a bare PMID (`23000000`), a source-qualified id (`MED:23000000`, `PMC:PMC3531190`, `PPR:PPR1303040`), or a DOI (`10.1038/nature12373`). `citation-network` needs a PMID / MED / PMC id -- a DOI has no citation endpoint and is rejected up front.

## `direction` (type: `string`):

`citation-network` mode. `citing` = articles that cite this one (forward). `cited` = the reference list of this one (backward).

## `sources` (type: `array`):

`search` filter. Restrict to one or more Europe PMC databases: `MED` (PubMed/MEDLINE), `PMC` (open-access full text), `PPR` (preprints -- bioRxiv, medRxiv), `PAT` (patents), `AGR` (Agricola), `CBA` (Chinese biomedical), `ETH` (theses), `HIR`, `CTX`, `NBK` (NCBI bookshelf). An unknown code is rejected rather than silently returning nothing.

## `author` (type: `string`):

`search` filter. Matched against `AUTH`, e.g. `Doudna JA`.

## `journalTitle` (type: `string`):

`search` filter. Matched against `JOURNAL`, e.g. `Nature`, `Lancet`.

## `pubType` (type: `string`):

`search` filter. Matched against `PUB_TYPE`, e.g. `review`, `Randomized Controlled Trial`, `preprint`.

## `fromYear` (type: `integer`):

`search` filter. Keep articles first published in or after this year.

## `toYear` (type: `integer`):

`search` filter. Keep articles first published in or before this year.

## `openAccessOnly` (type: `boolean`):

`search` filter. Keep only articles Europe PMC flags as open access.

## `hasFullText` (type: `boolean`):

`search` filter. Keep only articles whose full text is hosted in Europe PMC.

## `hasAbstract` (type: `boolean`):

`search` filter. Drop records with no abstract (common for patents and some book chapters).

## `rawQuery` (type: `string`):

Appended verbatim (AND-joined) to the composed query. Full Europe PMC field syntax, e.g. `HAS_FT:Y`, `GRANT_AGENCY:"Wellcome Trust"`, `(SRC:PPR OR SRC:MED)`.

## `resultType` (type: `string`):

`core` returns the full record (abstract, MeSH terms, chemicals, grants, full-text URLs). `lite` is a much smaller row -- faster and cheaper when you only need bibliographic fields.

## `sort` (type: `string`):

`search` mode ordering.

## `order` (type: `string`):

Ignored when sorting by relevance.

## `includeAnnotations` (type: `boolean`):

Fetch the Europe PMC annotation layer for each article -- genes/proteins, diseases, chemicals, organisms, cell types, accession numbers, mined out of the full text. One extra request per article, so it is off by default.

## `slimOutput` (type: `boolean`):

By default every ARTICLE row carries the verbatim Europe PMC record in `raw`. Turn on for the normalised fields only.

## `maxResults` (type: `integer`):

Cap per query (search) or per article (citation network). Search uses cursor pagination, so this has no upstream ceiling.

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

Parallel in-flight requests (matters for `articles` mode and annotation fetches).

## `minRequestInterval` (type: `number`):

Politeness pacing for EMBL-EBI's public infrastructure.

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

Optional. Europe PMC has no anti-bot layer, so a proxy is OFF by default.

## Actor input object example

```json
{
  "mode": "search",
  "query": "CRISPR base editing",
  "articleIds": [
    "MED:23000000"
  ],
  "direction": "citing",
  "openAccessOnly": false,
  "hasFullText": false,
  "hasAbstract": false,
  "resultType": "core",
  "sort": "relevance",
  "order": "desc",
  "includeAnnotations": false,
  "slimOutput": false,
  "maxResults": 50,
  "maxConcurrency": 4,
  "minRequestInterval": 0.3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "query": "CRISPR base editing",
    "articleIds": [
        "MED:23000000"
    ],
    "maxResults": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/europepmc-scraper").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 = {
    "query": "CRISPR base editing",
    "articleIds": ["MED:23000000"],
    "maxResults": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/europepmc-scraper").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 '{
  "query": "CRISPR base editing",
  "articleIds": [
    "MED:23000000"
  ],
  "maxResults": 50
}' |
apify call scrapyx/europepmc-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/europepmc-scraper"
        }
    }
}
```

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/tkDnIJg1Nliv7utiM/builds/yMTdTF3QKjlDOtKCE/openapi.json
