Back to Claude Scientific Skills

Europe PMC API

skills/paper-lookup/references/europepmc.md

2.61.08.7 KB
Original Source

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:

  • Keyword search of bioRxiv/medRxiv preprints. The preprint servers' own APIs have no keyword search at all (see references/biorxiv.md). Europe PMC indexes them and filters with SRC:"PPR".
  • Search inside full text, not just titles and abstracts, with the results-limiting filters (HAS_FT:Y, OPEN_ACCESS:Y) applied server-side.
  • Full text that fails honestly. fullTextXML returns a clean 404 when an article is not open access, where NCBI eFetch returns HTTP 200 with metadata and no <body> (see the hazard section of references/pmc.md).

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}
ParameterDefaultDescription
queryrequiredQuery language below. URL-encode it.
formatxmljson, xml, or dc
resultTypeliteidlist (IDs only), lite (core bibliographic), core (adds abstract, full-text links, MeSH, grants)
pageSize25Max 1,000. Over that is rejected, not clamped -- see the error shape below.
cursorMark*Deep pagination -- use this, not page
page11-based. Only for shallow paging.
sortrelevanceCITED desc, P_PDATE_D desc (publication date), TITLE asc -- note the space before the direction, not a colon

Example -- preprints about CRISPR:

bash
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:

json
{
  "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:

json
{"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.

FieldMatchesExample
SRCCorpusSRC:"PPR" (preprints), SRC:"MED", SRC:"PMC"
PUBLISHERPreprint server or publisherPUBLISHER:"bioRxiv", PUBLISHER:"medRxiv"
AUTHAuthor nameAUTH:"Doudna J"
TITLETitleTITLE:"gene editing"
ABSTRACTAbstractABSTRACT:organoid
PUB_YEARPublication yearPUB_YEAR:2023, PUB_YEAR:[2020 TO 2024]
HAS_FTFull text indexedHAS_FT:Y
OPEN_ACCESSOpen accessOPEN_ACCESS:Y
IN_EPMCFull text hosted in Europe PMCIN_EPMC:Y
DOIDOIDOI:"10.1038/nature12373"
EXT_IDPMIDEXT_ID:32117569
JOURNALJournal titleJOURNAL:"Nature"
MESHMeSH termMESH:"CRISPR-Cas Systems"
LANGLanguageLANG:eng

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

The pattern that closes the preprint gap:

bash
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)

json
{
  "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.