Skill Tracer
Skills › Scientific Agent Skills › paper-lookup › europepmc.md

europepmc.md

232 lines · skills/paper-lookup/references/europepmc.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Europe PMC API
2
3Europe PMC is a single search surface over PubMed abstracts, PMC full text, **preprints** (bioRxiv,
4medRxiv, Research Square, SSRN and others), patents, NHS guidelines, and theses. It is the one API in
5this skill that does keyword search *across* those corpora at once.
6
7Reach for it when you need something the others cannot do:
8
9- **Keyword search of bioRxiv/medRxiv preprints.** The preprint servers' own APIs have no keyword
10 search at all (see `references/biorxiv.md`). Europe PMC indexes them and filters with `SRC:"PPR"`.
11- **Search inside full text**, not just titles and abstracts, with the results-limiting filters
12 (`HAS_FT:Y`, `OPEN_ACCESS:Y`) applied server-side.
13- **Full text that fails honestly.** `fullTextXML` returns a clean **404** when an article is not
14 open access, where NCBI eFetch returns HTTP 200 with metadata and no `<body>` (see the hazard
15 section of `references/pmc.md`).
17All figures below verified 2026-07-27.

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.

Raw lines 1 to 18
19## Base URL
21```
22https://www.ebi.ac.uk/europepmc/webservices/rest
23```

Base URL

https://www.ebi.ac.uk/europepmc/webservices/rest
Raw lines 19 to 24
25## Authentication
27None. No key, no email parameter, no registration.

Authentication

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

Raw lines 25 to 28
29## Rate Limits
31No published per-second limit. Europe PMC asks for reasonable use and recommends `cursorMark`
32pagination over deep `page` offsets for large walks. Keep concurrency low and serialize long walks.

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.

Raw lines 29 to 33
34## Key Endpoints

Key Endpoints

Raw lines 34 to 35
36### 1. Search
38```
39GET /search?query={query}&format=json&pageSize={n}&resultType={type}
40```
42| Parameter | Default | Description |
43|---|---|---|
44| `query` | required | Query language below. URL-encode it. |
45| `format` | `xml` | `json`, `xml`, or `dc` |
46| `resultType` | `lite` | `idlist` (IDs only), `lite` (core bibliographic), `core` (adds abstract, full-text links, MeSH, grants) |
47| `pageSize` | 25 | Max **1,000**. Over that is rejected, not clamped -- see the error shape below. |
48| `cursorMark` | `*` | Deep pagination -- use this, not `page` |
49| `page` | 1 | 1-based. Only for shallow paging. |
50| `sort` | relevance | `CITED desc`, `P_PDATE_D desc` (publication date), `TITLE asc` -- note the **space** before the direction, not a colon |
52**Example** -- preprints about CRISPR:
54```bash
55curl -s --get "https://www.ebi.ac.uk/europepmc/webservices/rest/search" \
56 --data-urlencode 'query=CRISPR AND SRC:"PPR"' \
57 --data-urlencode 'format=json' \
58 --data-urlencode 'pageSize=2' \
59 --data-urlencode 'resultType=lite'
60```
62Returns `hitCount` 13341 with `resultList.result[]` entries whose `id` values look like `PPR1283561`
63and `source` is `PPR`.
65**Response envelope:**
67```json
69 "version": "6.9",
70 "hitCount": 13341,
71 "nextCursorMark": "AoIIQExCVyg1NTg2NjE3NQ==",
72 "nextPageUrl": "https://www.ebi.ac.uk/europepmc/webservices/rest/search?...",
73 "request": {"queryString": "CRISPR AND SRC:\"PPR\"", "resultType": "lite", "cursorMark": "*", "pageSize": 2},
74 "resultList": {"result": [...]}
76```
78The echoed `request.queryString` is the query **as parsed** -- diff it against what you sent to catch
79a mangled or truncated query before trusting `hitCount`.
81**Errors arrive with HTTP 200 and no `resultList`.** `pageSize=1001` returns:
83```json
84{"errCode": 404, "errMsg": "Invalid page size provided. Valid size is between 1 and 1000"}
85```
87Note the `errCode` is 404 *inside a 200 response*. Check for `errCode` / the absence of `resultList`
88before indexing into results -- neither the HTTP status nor an exception will tell you.

1. Search

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:

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.

Raw lines 36 to 89
90### 2. Full text XML
92```
93GET /{PMCID}/fullTextXML
94```
96```
97https://www.ebi.ac.uk/europepmc/webservices/rest/PMC7029759/fullTextXML
98```
100Returns a JATS `<article>` (not wrapped in `<pmc-articleset>` the way eFetch is). Pipe it through
101`scripts/jats_to_text.py`, which handles both wrappers.
103**404 means this endpoint did not supply the requested XML.** Check the PMCID and metadata before attributing it to licensing; an absent/wrong identifier can also fail. The historical PMC1500000 probe returned 404, but a single failure is not a universal OA classifier.

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 this endpoint did not supply the requested XML. Check the PMCID and metadata before attributing it to licensing; an absent/wrong identifier can also fail. The historical PMC1500000 probe returned 404, but a single failure is not a universal OA classifier.

Raw lines 90 to 104
Runs code scripts/jats_to_text.py :101
105### 3. Citations and references
107```
108GET /{source}/{id}/citations?format=json&pageSize={n}&page={n}
109GET /{source}/{id}/references?format=json&pageSize={n}&page={n}
110```
112`source` is the corpus code: `MED` (PubMed), `PMC`, `PPR` (preprints), `PAT` (patents), `AGR`, `CBA`,
113`CTX`, `ETH`, `HIR`, `NBK`.
115```
116https://www.ebi.ac.uk/europepmc/webservices/rest/MED/32117569/citations?format=json&pageSize=1
117```
119Returns `hitCount` plus `citationList.citation[]` (or `referenceList.reference[]`). Both wrap the
120list in a corpus-specific key, so parse by endpoint rather than assuming `resultList`.

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.

Raw lines 105 to 121
122### 4. Supplementary files and annotations
124```
125GET /{PMCID}/supplementaryFiles
126```
128This route takes a **PMCID without a source segment**, e.g.
129`/PMC5998401/supplementaryFiles`, and returns a ZIP archive, not JSON.
130`/MED/32117569/supplementaryFiles` is not the documented route. Its 404
131cannot establish that an article has no supplements. Inspect `hasSuppl` in
132`resultType=core` and the archive content; non-OA images may be excluded.
134The previously listed `/textMinedTerms/{semanticType}` route is not in the
135current Articles REST operation list and was not verified as supported.
136Use PubTator3 for this skill's entity workflow, or consult the official
137[Europe PMC Annotations API](https://europepmc.org/AnnotationsApi). Do not
138interpret a legacy-route 404 as evidence that no annotations exist.

4. Supplementary files and annotations

GET /{PMCID}/supplementaryFiles

This route takes a PMCID without a source segment, e.g. /PMC5998401/supplementaryFiles, and returns a ZIP archive, not JSON. /MED/32117569/supplementaryFiles is not the documented route. Its 404 cannot establish that an article has no supplements. Inspect hasSuppl in resultType=core and the archive content; non-OA images may be excluded.

The previously listed /textMinedTerms/{semanticType} route is not in the current Articles REST operation list and was not verified as supported. Use PubTator3 for this skill's entity workflow, or consult the official Europe PMC Annotations API. Do not interpret a legacy-route 404 as evidence that no annotations exist.

Raw lines 122 to 139
140## Query Language
142Field-prefixed terms combined with `AND` / `OR` / `NOT` (uppercase), quoted phrases, and
143parentheses.
145| Field | Matches | Example |
146|---|---|---|
147| `SRC` | Corpus | `SRC:"PPR"` (preprints), `SRC:"MED"`, `SRC:"PMC"` |
148| `PUBLISHER` | Preprint server or publisher | `PUBLISHER:"bioRxiv"`, `PUBLISHER:"medRxiv"` |
149| `AUTH` | Author name | `AUTH:"Doudna J"` |
150| `TITLE` | Title | `TITLE:"gene editing"` |
151| `ABSTRACT` | Abstract | `ABSTRACT:organoid` |
152| `PUB_YEAR` | Publication year | `PUB_YEAR:2023`, `PUB_YEAR:[2020 TO 2024]` |
153| `HAS_FT` | Full text indexed | `HAS_FT:Y` |
154| `OPEN_ACCESS` | Open access | `OPEN_ACCESS:Y` |
155| `IN_EPMC` | Full text hosted in Europe PMC | `IN_EPMC:Y` |
156| `DOI` | DOI | `DOI:"10.1038/nature12373"` |
157| `EXT_ID` | PMID | `EXT_ID:32117569` |
158| `JOURNAL` | Journal title | `JOURNAL:"Nature"` |
159| `MESH` | MeSH term | `MESH:"CRISPR-Cas Systems"` |
160| `LANG` | Language | `LANG:eng` |
162A bare term with no prefix searches title, abstract, and full text together.
164**The pattern that closes the preprint gap:**
166```bash
167curl -s --get "https://www.ebi.ac.uk/europepmc/webservices/rest/search" \
168 --data-urlencode 'query=(SRC:"PPR" AND PUBLISHER:"bioRxiv" AND "organoid")' \
169 --data-urlencode 'format=json' \
170 --data-urlencode 'pageSize=2' \
171 --data-urlencode 'resultType=lite'
172```
174`hitCount` 1972, with `bookOrReportDetails.publisher` confirming `bioRxiv` on each hit. Take the
175`doi` (a `10.1101/...` preprint DOI) from these results and hand it to the bioRxiv API for
176preprint-specific metadata such as the published-version link.

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:

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' \
  --data-urlencode 'pageSize=2' \
  --data-urlencode '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.

Raw lines 140 to 177
178## Result Object (resultType=core, key fields)
180```json
182 "id": "37917583",
183 "source": "MED",
184 "pmid": "37917583",
185 "pmcid": "PMC10680139",
186 "doi": "10.1016/j.celrep.2023.113339",
187 "title": "...",
188 "authorString": "Smith J, Jones A.",
189 "journalInfo": {"volume": "42", "journal": {"title": "Cell reports"}},
190 "pubYear": "2023",
191 "abstractText": "...",
192 "isOpenAccess": "Y",
193 "inEPMC": "Y",
194 "hasPDF": "Y",
195 "hasSuppl": "Y",
196 "citedByCount": 14,
197 "fullTextUrlList": {"fullTextUrl": [{"documentStyle": "pdf", "url": "..."}]}
199```
201**The boolean-ish fields are the strings `"Y"` / `"N"`, not JSON booleans.** A truthiness test
202passes for `"N"`, so compare explicitly. `pmcid` is absent -- not null -- when the article is not in
203PMC.
205Preprint (`SRC:"PPR"`) records differ in shape: the server name lives in
206`bookOrReportDetails.publisher`, and `journalInfo` is absent. Do not assume one schema across corpora.

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.

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

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.

Raw lines 208 to 214
215## Pagination and Count Reconciliation
2171. `hitCount` on the first response is the total.
2182. Request with `cursorMark=*`, then pass the returned `nextCursorMark` on each subsequent call.
2193. **Stop when `resultList.result` is empty or `nextCursorMark` is absent, null, or equals the cursor you sent.** A historical walk echoed the cursor on an empty final page; a one-hit public query on 2026-09-30 omitted it. Do not assume either termination shape is universal.
2204. Reconcile retrieved count against `hitCount` and report both.
222Historical verified walk (`AUTH:"Doudna J" AND PUB_YEAR:2013 AND SRC:"MED"`, `pageSize=5`): pages of 5, 5, 5, 4,
223then a 5th request returning 0 results with the cursor unchanged. Retrieved 19, `hitCount` 19.
225`scripts/paginate.py --api europepmc` implements this, including the repeated-cursor stop condition.
227Deep `page` offsets degrade and are capped; `cursorMark` is the supported path for anything past a
228few pages.

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 is absent, null, or equals the cursor you sent.** A historical walk echoed the cursor on an empty final page; a one-hit public query on 2026-09-30 omitted it. Do not assume either termination shape is universal.
  4. Reconcile retrieved count against hitCount and report both.

Historical 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.

Raw lines 215 to 229
Runs code scripts/paginate.py :225
230## Official sources reviewed 2026-09-30
232- https://europepmc.org/RestfulWebService

Official sources reviewed 2026-09-30

  • https://europepmc.org/RestfulWebService
Raw lines 230 to 232