← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / scientific-agent-skills / skills / paper-lookup / references / europepmc.md

Europe PMC API

Europe PMC is a single search surface over PubMed abstracts, PMC full text, preprints (bioRxiv, medRxiv, Research Square, SSRN and others), patents, NHS guidelines, and theses. It is the one API in this skill that does keyword search across those corpora at once.

Reach for it when you need something the others cannot do:

All figures below verified 2026-07-27.

Base URL

https://www.ebi.ac.uk/europepmc/webservices/rest

Authentication

None. No key, no email parameter, no registration.

Rate Limits

No published per-second limit. Europe PMC asks for reasonable use and recommends cursorMark pagination over deep page offsets for large walks. Keep concurrency low and serialize long walks.

Key Endpoints

GET /search?query={query}&format=json&pageSize={n}&resultType={type}
Parameter Default Description
query required Query language below. URL-encode it.
format xml json, xml, or dc
resultType lite idlist (IDs only), lite (core bibliographic), core (adds abstract, full-text links, MeSH, grants)
pageSize 25 Max 1,000. Over that is rejected, not clamped -- see the error shape below.
cursorMark * Deep pagination -- use this, not page
page 1 1-based. Only for shallow paging.
sort relevance CITED desc, P_PDATE_D desc (publication date), TITLE asc -- note the space before the direction, not a colon

Example -- preprints about CRISPR:

curl -s --get "https://www.ebi.ac.uk/europepmc/webservices/rest/search" \
  --data-urlencode 'query=CRISPR AND SRC:"PPR"' \
  --data-urlencode 'format=json' \
  --data-urlencode 'pageSize=2' \
  --data-urlencode 'resultType=lite'

Returns hitCount 13341 with resultList.result[] entries whose id values look like PPR1283561 and source is PPR.

Response envelope:

{
  "version": "6.9",
  "hitCount": 13341,
  "nextCursorMark": "AoIIQExCVyg1NTg2NjE3NQ==",
  "nextPageUrl": "https://www.ebi.ac.uk/europepmc/webservices/rest/search?...",
  "request": {"queryString": "CRISPR AND SRC:\"PPR\"", "resultType": "lite", "cursorMark": "*", "pageSize": 2},
  "resultList": {"result": [...]}
}

The echoed request.queryString is the query as parsed -- diff it against what you sent to catch a mangled or truncated query before trusting hitCount.

Errors arrive with HTTP 200 and no resultList. pageSize=1001 returns:

{"errCode": 404, "errMsg": "Invalid page size provided. Valid size is between 1 and 1000"}

Note the errCode is 404 inside a 200 response. Check for errCode / the absence of resultList before indexing into results -- neither the HTTP status nor an exception will tell you.

2. Full text XML

GET /{PMCID}/fullTextXML
https://www.ebi.ac.uk/europepmc/webservices/rest/PMC7029759/fullTextXML

Returns a JATS <article> (not wrapped in <pmc-articleset> the way eFetch is). Pipe it through scripts/jats_to_text.py, which handles both wrappers.

404 means not open access -- verified on PMC1500000, the same article for which eFetch returns a 200 with no <body>. A 404 here is the honest answer, so prefer this endpoint when you need to know whether full text exists.

3. Citations and references

GET /{source}/{id}/citations?format=json&pageSize={n}&page={n}
GET /{source}/{id}/references?format=json&pageSize={n}&page={n}

source is the corpus code: MED (PubMed), PMC, PPR (preprints), PAT (patents), AGR, CBA, CTX, ETH, HIR, NBK.

https://www.ebi.ac.uk/europepmc/webservices/rest/MED/32117569/citations?format=json&pageSize=1

Returns hitCount plus citationList.citation[] (or referenceList.reference[]). Both wrap the list in a corpus-specific key, so parse by endpoint rather than assuming resultList.

4. Text-mined terms and supplementary files

GET /{source}/{id}/textMinedTerms/{semanticType}?format=json
GET /{source}/{id}/supplementaryFiles

Both are per-article optional and return 404 when the article has none. A 404 here means "this article has no such data", not a broken request -- do not treat it as an outage or retry it.

Verified on MED/32117569: resultType=core reports hasSuppl: "N", and supplementaryFiles 404s, consistent with each other. Read hasSuppl from a core search first and skip the call when it is "N"; there is no equivalent pre-check for textMinedTerms, which 404s for the same article.

Query Language

Field-prefixed terms combined with AND / OR / NOT (uppercase), quoted phrases, and parentheses.

Field Matches Example
SRC Corpus SRC:"PPR" (preprints), SRC:"MED", SRC:"PMC"
PUBLISHER Preprint server or publisher PUBLISHER:"bioRxiv", PUBLISHER:"medRxiv"
AUTH Author name AUTH:"Doudna J"
TITLE Title TITLE:"gene editing"
ABSTRACT Abstract ABSTRACT:organoid
PUB_YEAR Publication year PUB_YEAR:2023, PUB_YEAR:[2020 TO 2024]
HAS_FT Full text indexed HAS_FT:Y
OPEN_ACCESS Open access OPEN_ACCESS:Y
IN_EPMC Full text hosted in Europe PMC IN_EPMC:Y
DOI DOI DOI:"10.1038/nature12373"
EXT_ID PMID EXT_ID:32117569
JOURNAL Journal title JOURNAL:"Nature"
MESH MeSH term MESH:"CRISPR-Cas Systems"
LANG Language LANG:eng

A bare term with no prefix searches title, abstract, and full text together.

The pattern that closes the preprint gap:

curl -s --get "https://www.ebi.ac.uk/europepmc/webservices/rest/search" \
  --data-urlencode 'query=(SRC:"PPR" AND PUBLISHER:"bioRxiv" AND "organoid")' \
  --data-urlencode 'format=json&pageSize=2&resultType=lite'

hitCount 1972, with bookOrReportDetails.publisher confirming bioRxiv on each hit. Take the doi (a 10.1101/... preprint DOI) from these results and hand it to the bioRxiv API for preprint-specific metadata such as the published-version link.

Result Object (resultType=core, key fields)

{
  "id": "37917583",
  "source": "MED",
  "pmid": "37917583",
  "pmcid": "PMC10680139",
  "doi": "10.1016/j.celrep.2023.113339",
  "title": "...",
  "authorString": "Smith J, Jones A.",
  "journalInfo": {"volume": "42", "journal": {"title": "Cell reports"}},
  "pubYear": "2023",
  "abstractText": "...",
  "isOpenAccess": "Y",
  "inEPMC": "Y",
  "hasPDF": "Y",
  "hasSuppl": "Y",
  "citedByCount": 14,
  "fullTextUrlList": {"fullTextUrl": [{"documentStyle": "pdf", "url": "..."}]}
}

The boolean-ish fields are the strings "Y" / "N", not JSON booleans. A truthiness test passes for "N", so compare explicitly. pmcid is absent -- not null -- when the article is not in PMC.

Preprint (SRC:"PPR") records differ in shape: the server name lives in bookOrReportDetails.publisher, and journalInfo is absent. Do not assume one schema across corpora.

Identifiers

Every result carries id + source, and that pair is the key -- id alone is not unique across corpora. Endpoints that take an article path want {source}/{id}, e.g. MED/32117569. Preprint IDs are PPR-prefixed (PPR1283561) and are Europe PMC's own, not bioRxiv's; use the record's doi to cross-reference.

Pagination and Count Reconciliation

  1. hitCount on the first response is the total.
  2. Request with cursorMark=*, then pass the returned nextCursorMark on each subsequent call.
  3. Stop when resultList.result is empty or nextCursorMark equals the cursor you sent. There is no null terminator: at exhaustion Europe PMC returns an empty result list and echoes your own cursor back. Detecting the end therefore costs one extra empty request -- expected, not a fault.
  4. Reconcile retrieved count against hitCount and report both.

Verified walk (AUTH:"Doudna J" AND PUB_YEAR:2013 AND SRC:"MED", pageSize=5): pages of 5, 5, 5, 4, then a 5th request returning 0 results with the cursor unchanged. Retrieved 19, hitCount 19.

scripts/paginate.py --api europepmc implements this, including the repeated-cursor stop condition.

Deep page offsets degrade and are capped; cursorMark is the supported path for anything past a few pages.