pyeuropepmc-mcp lets an AI agent search Europe PMC, read papers and export references through the Model Context Protocol. This page shows how the tools fit together, gives a workflow for the common jobs, and lists the Europe PMC behaviours that the tools work around. The package README has the flags, the transports and the reference table of every tool.
The server is part of the base install. Most clients start it themselves:
claude mcp add pyeuropepmc -- uvx pyeuropepmc mcp # Claude Code, no install needed
pyeuropepmc-mcp # stdio, for any client that spawns it
pyeuropepmc-mcp --transport streamable-http # HTTP at http://127.0.0.1:8000/mcp
For Claude Desktop, Cursor and VS Code, add "command": "pyeuropepmc-mcp" (or "command": "uvx" with "args": ["pyeuropepmc", "mcp"]) to the client’s MCP configuration; the README shows the file for each. The server has no authentication of its own: keep the HTTP transport on 127.0.0.1 or put an authenticating proxy in front of it. Behind a proxy that forwards its public host name, add --allowed-host mcp.example.org (MCP SDK 2.x answers HTTP 421 to unknown Host headers).
Two tools need configuration. find_open_access reads UNPAYWALL_EMAIL (Unpaywall asks for a contact address), and the LLM tools need the agentic extra and an OPENAI_API_KEY. A tool whose dependency is missing stays in the tool list and answers with the pip install line it needs.
| You want to | Use |
|---|---|
| Check a query before running it | validate_query, then count_papers |
| Write a query from parts | build_query |
| Turn a research question into a query | pico_decompose (PICO, PICOS, PICOT, SPIDER), then mesh_expand |
| See how a search is distributed over years, journals, authors, countries, funders and open access | analyze_search (exact counts where Europe PMC can count, a labelled sample otherwise) |
| Run the same search on PubMed or another database | translate_query |
| Test a query against papers you already know | evaluate_query |
| Search | search_papers (Europe PMC), unified_search (several sources, duplicates merged; diagnostics shows which source failed, was rate limited or found nothing), search_authors |
| Look up papers you have identifiers for | get_paper_details (one), get_papers_batch (up to 100) |
| See impact, funders, institutions and retractions of one paper | enrich_paper |
| Read a paper | get_paper_fulltext, get_papers_fulltext_batch, paper_figures |
| Pull the methods, results or limitations out of a paper whatever its headings say | tag_paper_sentences |
| Find a free copy of a paper that is not open access in Europe PMC | find_open_access |
| Find genes, chemicals, diseases | get_paper_annotations, find_papers_by_entity |
| Find where the data is | get_paper_data_links, get_paper_lab_links |
| Follow citations | get_paper_citations, get_paper_references, citation_snowball |
| Get references into a reference manager | export_papers |
| Find out why something failed | epmc_status, cache_status, clear_cache |
Europe PMC does not reject a malformed query; it answers a different one. An unclosed parenthesis is ignored, a AND b OR c returns the hits for a alone, and an unknown field name gives zero hits. Run the query through validate_query first: it reports each problem with its position, suggests the field you probably meant, and says what Europe PMC will do with the query as written. build_query produces queries that pass this check by construction.
validate_query query="CRISPR AND (TITLE:base editing"
count_papers query="CRISPR AND TITLE:\"base editing\"", date_from="2020-01-01", open_access=true
search_papers query="CRISPR AND TITLE:\"base editing\"", limit=25, sort="cited"
For a systematic review, give evaluate_query the PMIDs of papers that must be found and, optionally, papers that must not. It returns the recall, the PMIDs the query misses, and the excluded papers it still finds. translate_query rewrites the final query for PubMed and reports what it could not translate.
pico_decompose question="In adults with type 2 diabetes, does metformin reduce cardiovascular mortality compared with placebo?"
mesh_expand terms=["type 2 diabetes", "metformin"]
pico_decompose splits a question into PICO, PICOS, PICOT or SPIDER elements and builds a query from them, with no model, key or request. Each element is a verbatim piece of the question with its position (start, end), so you can check it; an element the question does not state is listed in missing (required for the framework) or optional_missing, never filled in. The query is an AND of one group per element and is checked with the same linter as validate_query (query_check). By default it uses the population (the condition, not the demographics: adults with type 2 diabetes becomes "type 2 diabetes") and the intervention, because outcome and comparison terms are rarely indexed and cut recall; query_elements chooses others, and mesh=true replaces the terms it knows with MeSH groups.
| Framework | Complete when it has |
|---|---|
PICO |
population, intervention, outcome (comparison is optional) |
PICOS |
those and a study design |
PICOT |
those and a time frame |
SPIDER |
sample, phenomenon of interest, evaluation (design and research type are optional) |
The rules know the usual clinical phrasings: In P, does I reduce O compared with C?, What is the effect of I on O in P?, Is I more effective than C for O?, Is I associated with O?, Effectiveness of I for O in P and P treated with I. A question phrased another way is understood only in part, and the answer says so in notes and missing: ask the user to restate it before relying on the query. Is I effective for X? reads X as the outcome; if X is the condition treated, it belongs to the population.
Measured on 36 gold questions (tests/fixtures/pico, benchmarks/evaluate_pico_parser.py), counting only elements the question states:
| Question set | Rules, found exactly | Older parser (features.review.pico), found exactly |
|---|---|---|
| 12 questions used while writing the rules | 39 of 39 | 2 of 39 |
| 12 questions written before the rules and run once afterwards | 39 of 39 | 2 of 39 |
| 12 questions phrased outside the patterns | 11 of 28 | 1 of 28 |
The rules never extracted text that is not in the question and never filled an element the question leaves out (0 of 46 unstated elements on the three sets). The second set was written by the same person as the rules, so it shows that the patterns are not fitted to the first set, not how it fares on questions from elsewhere; the third set is the realistic warning: outside the known phrasings fewer than half of the elements are found. The older parser takes the rest of the sentence for each element, so its elements overlap and its query contains words such as does and with; the MCP tool does not use it.
mesh_expand returns, for each term, the MeSH descriptor (ID, heading, tree numbers), its entry terms and a ready fragment such as (MESH:"Diabetes Mellitus, Type 2" OR "Diabetes Mellitus, Type 2" OR "type 2 diabetes" OR "NIDDM" ...), and joins the fragments with AND (or OR, combine="or") into a query that is checked like validate_query. By default it works offline from a snapshot of 244 common clinical headings (conditions, drugs, interventions, populations, outcomes and study designs) taken from NLM’s published MeSH, with entry terms and tree numbers as NLM lists them. A term that is not in the snapshot comes back with matched: false and is searched as written; nothing is guessed. use_api=true asks the NLM MeSH service instead (up to four requests per term, so it needs the network and takes longer) and covers all of MeSH; a request that fails, or a term not reached within time_budget (30 seconds for the whole call, 2 to 120), is reported for that term and does not fail the call. Europe PMC’s MESH: field is much narrower than PubMed’s MeSH index (3,477 hits for MESH:"Diabetes Mellitus, Type 2" against 561,715 for the phrase "type 2 diabetes"), which is why each fragment also searches the heading and the entry terms as phrases. scripts/build_mesh_snapshot.py rebuilds the snapshot from the NLM service.
analyze_search query="malaria", date_from="2015", date_to="2024"
analyze_search query="CRISPR AND TITLE:therapy", metrics=["by_year", "open_access"]
analyze_search query="malaria", metrics=["journals", "funders"], limit=1000, sort="CITED desc"
analyze_search answers “how is this search distributed?” without returning thousands of records. Every metric says in basis where its numbers come from, because Europe PMC can count some things over the whole result set and the rest has to be read off records:
| Metric | basis |
How |
|---|---|---|
by_year |
exact |
One count per first-publication year (FIRST_PDATE:[Y TO Y]), from the oldest to the newest record. The years end with the current one, because Europe PMC holds records dated far in the future (2096 on a live search for cancer); those are one after bucket. The latest 30 years are counted by default (year_window, 1 to 60) plus one before bucket for everything older, so the sum stays exact; each year is one request, counted in parallel; on live searches with millions of hits (cancer, CRISPR, 28 Oct) 30 years took 14 to 20 s and 60 years 15 to 25 s, so the window saves a little, not half. undated is what the rest does not account for, so years + before + after + undated is hit_count |
open_access |
exact |
Open-access and other records, and the share |
citations thresholds |
exact |
Records cited at least 1, 10, 100 and 1,000 times (CITED:[n TO *]) |
citations statistics, journals, authors, types, countries, funders |
sample |
Computed with the library’s analytics functions from the first limit records of the search, in relevance order or the order sort gives. They describe those records only: the journals of the first 500 hits are not the journals of 120,000 |
When limit is at least hit_count the records are the whole result and the sample metrics are exact too. Otherwise the response has a sample_note that says how many records of how many were read. On a live search for malaria from 2015 to 2024 (22 October 2026), hit_count was 123,390, by_year summed to exactly 123,390 and 89,994 records were open access, while the sample of 200 records had 42 articles in Malaria Journal: a statement about those 200 records, not about the decade.
The sample metrics need pip install "pyeuropepmc[analytics]" (pandas and numpy). Without it a call that names one of them fails before any request and says what to install; a call that leaves metrics out, as an agent usually does, still returns by_year, open_access and the citation thresholds, with the others marked basis: unavailable and the same hint, because a plain uvx pyeuropepmc mcp has no pandas. countries takes the last comma-separated part of each affiliation, so a record without an affiliation is not counted and a few countries may be misread; types counts a record once per type it has, and Europe PMC types a record differently by source (Journal Article for MEDLINE records, research-article for PMC ones). lite records are read unless countries or funders is asked for, which need core records.
export_papers writes BibTeX, RIS, CSL-JSON, CSV, JSON or Markdown for a search query or a list of ids. Nothing is written to disk; the text comes back in the response.
export_papers query="malaria AND TITLE:vaccine", format="bibtex", limit=50
export_papers ids=["16004729", "PMC5321187", "10.1038/s41579-019-0296-0"], format="ris"
BibTeX keys look like vasiliou2005analysis, are unique within an export, and stay the same when an export is cut to size. A record is never cut in the middle: when the response is full, truncated is true and next_skip (for a query) or remaining (for ids) says where to continue. Identifiers that Europe PMC does not know are listed in skipped with the reason, in the order you gave them.
get_paper_details pmid="16004729"
get_paper_fulltext pmcid="PMC5321187", format="sections", sections=["Methods"], max_chars=20000
format is markdown, sections (typed blocks) or outline. max_chars cuts the text at a block boundary and sets truncated, so a long paper fits in one response. get_papers_fulltext_batch reads up to 20 papers within a size budget and reports each paper’s status (ok, not_open_access, not_in_pmc, no_matching_section, skipped_budget, …).
Europe PMC serves full-text XML only for papers in its open-access set. For any other paper, find_open_access asks Unpaywall where a free copy is (publisher, repository or preprint) and returns the best link, the licence and a few alternatives.
tag_paper_sentences pmcid="PMC8658482", roles=["method"], sections=["methods"]
tag_paper_sentences doi="10.3390/jcm10235527", roles=["result", "limitation"]
tag_paper_sentences splits the body of an open-access paper into sentences and says what each one does: background, motivation (aim, hypothesis, gap), method, result, claim (the authors’ interpretation), limitation or conclusion. Front matter, the reference list, figure and table captions and administrative sections (acknowledgments, funding, conflicts of interest, ethics and consent statements) are left out. It uses the same download and cached XML as get_paper_fulltext. roles and sections filter what is returned (statistics always counts the whole body), sentences come in reading order and are never cut, and truncated says whether max_chars left some out. A sentence no rule is sure about is unknown; min_confidence raises the bar further.
It uses rules, not a model: the section a sentence is in gives its default role (methods describe what was done, results what was found) and cues in the sentence move it where a section mixes roles (a discussion restates results, cites earlier work, admits limits). The older RhetoricalHighlighter of the library has keyword rules written for computer-science papers; it labelled 13 of the 93 test sentences correctly, so the tool does not use it. use_llm=true asks the configured LLM about up to 40 sentences the rules left unknown (it needs the agentic extra and a provider like the other LLM tools, and the sentences are sent to it); that path is tested with a fake client only.
Measured on 93 sentences from three open-access papers (a retrospective cohort, a yeast study, a randomized trial), labelled by hand by one annotator (tests/fixtures/rhetoric, benchmarks/evaluate_rhetoric_roles.py):
| Role | Given | Precision | Gold | Recall |
|---|---|---|---|---|
| background | 18 | 0.94 | 20 | 0.85 |
| motivation | 3 | 1.00 | 4 | 0.75 |
| method | 16 | 1.00 | 17 | 0.94 |
| result | 26 | 0.92 | 26 | 0.92 |
| claim | 12 | 0.92 | 13 | 0.85 |
| limitation | 6 | 1.00 | 7 | 0.86 |
| conclusion | 5 | 1.00 | 6 | 0.83 |
82 of the 93 sentences got the right role and 7 were unknown. By set: 43 of 46 on the sentences used while writing the rules, 22 of 23 on a paper labelled before the rules were run, and 17 of 24 on a harder sample of introduction, results and discussion sentences labelled before the rules saw them. The discussion is the hard part: a sentence there with no cue at all is unknown, because guessing background was right in only 2 of 7 such cases. The test set is small and one person labelled it and wrote the rules, so read the table as what the rules can do on ordinary biomedical papers, not as a guarantee; the precision of a role on your papers may be lower.
A paper without recognisable headings (a letter, a short report) has no section to start from. Then only the sentence cues work, and only the roles they were precise for are returned: on the same 93 sentences background (17 of 22 given) , method (6 of 7) and conclusion (3 of 3); result, claim, limitation and motivation stay unknown (61 of 93 sentences), and the answer carries a note saying so.
enrich_paper doi="10.1016/S0140-6736(97)11096-0", include=["updates", "impact"]
enrich_paper pmid="26017442", sources=["crossref", "openalex"], time_budget=20
enrich_paper asks Europe PMC, Crossref, OpenAlex, Semantic Scholar and iCite (and Unpaywall once UNPAYWALL_EMAIL is set) at the same time and merges their answers into one record of a few kilobytes, usually in two seconds (see how long it takes). ror_details=true adds each institution’s city, country code, website and coordinates from ROR, at about 0.3 seconds per distinct institution. include picks the groups: impact, funding, affiliations, oa and updates. updates lists correction and retraction notices from Crossref and Europe PMC and sets is_retracted; it means that no retraction notice was found there, not that none exists anywhere.
Look at sources before trusting a gap. Each source is ok, empty (it answered without data), error (with the reason, for example a Semantic Scholar rate limit), timeout (still running when time_budget ended, 30 seconds by default) or unavailable (the extra or e-mail address it needs is missing). complete is true only when every source answered. A failed source gives a partial record, never an error.
get_paper_annotations pmid="16004729", annotation_type="Chemicals", limit=20
find_papers_by_entity entity="CHEBI:16236", limit=8
get_paper_data_links pmid="32257953", category="SNPs"
get_paper_lab_links pmcid="PMC3257301", provider="genome"
paper_figures pmcid="PMC5321187"
get_paper_annotations groups the text-mined entities of one paper with their mention counts, sections and examples. find_papers_by_entity goes the other way and pages through articles that mention an entity, one to eight per page. get_paper_data_links lists accession numbers that Europe PMC found in the text (dbSNP, RefSeq, OMIM, ClinicalTrials.gov, cited DOIs) and records that providers link to the paper, each with its category, kind and origin; Altmetric pages are in the list too, marked kind: altmetrics. Supplementary files are part of paper_figures, with their download URLs.
citation_snowball walks the citation graph forwards, backwards or both from one paper, with limits per level and a minimum citation count. get_paper_citations and get_paper_references return one step.
Clients that support MCP resources can attach a paper without asking the model to call a tool: epmc://paper/PMC3525259 is its Markdown full text, epmc://paper/PMC3525259/outline its sections with sizes and epmc://paper/PMC3525259/references its parsed reference list. Each runs the same code as get_paper_fulltext, so an article that is not open access fails with the same “not Open Access” message, and the text is cut at the same 30,000 characters (the end of a cut text names the call that returns a section). They are URI templates: a client lists them with resources/templates/list. A search as a resource (epmc://search/{query}) is deliberately not offered; search_papers takes the query as a typed argument instead of encoding it into a URI.
Three prompts encode workflows that would otherwise be improvised. They name only tools of this server, and a test checks every tool and parameter they mention against the live tool list.
literature_review topic="statins and dementia", years="2018-2024", max_papers=15
critical_appraisal pmcid="PMC3525259"
citation_trace pmid="22745249", direction="backward"
literature_review sizes the topic with count_papers, searches, screens by title and abstract with the dropped papers listed, reads the selected papers with get_paper_fulltext, and writes a review that cites every claim and ends with the counts. critical_appraisal reads the outline, methods and results of one open-access paper, collects the limitations its authors state with tag_paper_sentences, checks funders and retractions with enrich_paper, and ends with a verdict. citation_trace follows the references and citations of a PMID and inspects the most cited neighbours.
stdio, the default, needs no protection: the client starts the process and owns its pipes. The streamable-http and sse transports open a port, and then anyone who can reach it can call every tool, which means spending your Europe PMC, Crossref and OpenAlex quota, filling the server’s caches, and (with OPENAI_API_KEY set) running the LLM tools on your key. The tools themselves are read-only and write nothing on the server’s disk except the caches.
Safe defaults. The server binds to 127.0.0.1 and has no token until you set one. If you bind to another address without a token it logs a warning at startup. stdio ignores tokens.
Bearer token. Authentication is a bearer token checked on every HTTP request, before the MCP server sees it:
( umask 077; pyeuropepmc-mcp --generate-token > /etc/pyeuropepmc/mcp.token ) # 256 random bits, never world-readable
pyeuropepmc-mcp --transport streamable-http --host 0.0.0.0 --auth-token-file /etc/pyeuropepmc/mcp.token
A client sends Authorization: Bearer <token>; any other request, including a missing, malformed or wrong header, gets HTTP 401 with WWW-Authenticate: Bearer and the same body whatever the reason. The server logs the refused method and path, never a token. Tokens are compared in constant time. The token goes in a file (--auth-token-file, one token per line, # comments) or in PYEUROPEPMC_MCP_AUTH_TOKEN (comma separated); there is no command-line value because ps shows it to every user. A token must be at least 16 printable characters without spaces, and a variable or file that is set but empty is an error: a typo cannot turn into an open server.
Rotation. Several tokens are accepted at once. Add the new token on a second line, restart, move the clients over, then remove the old one and restart.
Behind a proxy. Terminate TLS in the proxy (the server speaks plain HTTP), forward to 127.0.0.1:8000, and add --allowed-host mcp.example.org for the public name (SDK 2.x). With the token on, the proxy can pass the Authorization header through unchanged; if the proxy authenticates users itself (OAuth, mTLS, an identity-aware proxy) you can leave the token off and keep the server on loopback.
What this does not do. It is one shared secret, not per-user identity: every holder has the same access, nothing is rate limited per client, and a token sent over plain HTTP can be read on the wire, so use TLS. It is not OAuth; clients that insist on an OAuth flow need a proxy that provides it. Resources and prompts go through the same checks as the tools.
These were measured against the live service in October 2026. The tools handle them for you; they are listed so that the results make sense.
validate_query exists for this reason.*cancer) gives HTTP 503. validate_query flags it.CITES: works with PMIDs only. build_query(cites=...) takes a PMID for that reason.labsLinks ignores its provider filter and datalinks ignores its page size. get_paper_lab_links and get_paper_data_links filter and limit on the server side.enrich_paper takesMeasured on 21 October 2026 with benchmarks/benchmark_enrich_paper.py: three papers (a retracted paper with 24 authors, a three-author paper from a DOI, an open-access paper), three calls each, one at a time against the live services. One Linux machine, Python 3.10, no API keys and no e-mail addresses configured (so Semantic Scholar, OpenAlex and Crossref ran without their polite or authenticated pools), no caching. Public services vary by network and by day: read these as the order of magnitude, not as a guarantee. Run the script to measure your own setup.
Time of a whole call, in seconds, median (fastest to slowest of 9 calls). Every call also asks Europe PMC, which resolves the identifier first; that lookup takes about 1.4 s of every figure below.
| Sources asked | Call | Answer size |
|---|---|---|
| Europe PMC only | 1.8 (1.7 to 4.9) | 3.0 KB (the merged record, without the tool’s framing) |
| + Crossref | 1.7 (1.7 to 3.7) | 2.9 KB |
| + OpenAlex | 1.9 (1.7 to 31.9) | 4.4 KB |
| + Semantic Scholar | 1.8 (1.7 to 4.5) | 1.8 KB |
| + iCite | 1.9 (1.6 to 4.2) | 1.8 KB |
| All sources (default) | 2.0 (1.6 to 74.4) | 5.6 KB |
All sources + ror_details |
5.3 (1.9 to 32.3) | 7.8 KB |
The sources run at the same time, so asking for all of them costs about as much as the slowest one, not their sum. Adding a source mostly adds data, not time. The answer is 2 to 8 KB, well under 15 KB; the size depends on the number of authors, institutions and funders more than on the sources.
Time of each source inside those calls (its own request, seconds, median and range over 9 to 63 calls):
| Source | Median | Range |
|---|---|---|
| Europe PMC | 0.4 | 0.4 to 33.7 |
| Crossref | 0.1 | 0.1 to 35.8 |
| iCite | 0.3 | 0.1 to 36.2 |
| OpenAlex | 0.4 | 0.3 to 73.0 |
| Semantic Scholar | 0.4 | 0.3 to 2.9 |
ROR (ror_details) |
about 0.3 per distinct institution | 0.1 to 0.3 s for 1 institution, 1.3 to 1.7 s for 5, 3.4 to 7.4 s for 12 |
ROR details cost about 0.3 s per distinct institution. For the three papers the median call went from about 2 s to 3.6, 4.0 and 5.4 s. A later single pass (one call per paper) gave 8.5 s as the median with ROR, with the ROR step between 0.3 and 6.7 s, so expect a few seconds with a wide spread. The lookup runs once, after the merge, and each institution is fetched once however many authors share it (the retracted paper has 13 author-institution pairs but one institution, so one request). An earlier version fetched every institution twice, once inside the OpenAlex client and once in the enricher, and once per author-institution pair inside OpenAlex: 14 ROR requests for the retracted paper instead of 1, 29 instead of 12 for the open-access paper, and a median of 8.4 s instead of 5.3 s with ROR on.
Slow outliers. 4 of the 72 calls (the 63 of the first pass and 9 repeats of the ROR row after the change) had a source wait 30 to 36 s before it answered: OpenAlex in all four and, in one of them, Europe PMC, Crossref and iCite as well (74 s in all). The cause is the measuring machine, not the services: it has no working IPv6 route, and a Python client tries the IPv6 address of a host first and waits for it to time out before it tries IPv4 (Python waited 61 s for NLM’s service, forcing IPv4 made the same request take 1.1 s, and curl falls back at once and took 0.6 s). It hits any host that has an IPv6 address; most of the calls above were fast because a connection that is reused is not delayed again, and OpenAlex was the host most often affected. With the default time_budget of 30 s such a call is reported as timeout for the sources still waiting, and the rest of the record is returned. These measurements predate the connection fallback: since then the clients start the IPv4 address 0.25 s after the IPv6 one and use whichever connects first (see the FAQ), so such a stall no longer happens in the clients’ own sessions. If you still see timeout entries, raise time_budget (up to 120) if you would rather wait than get a partial record.
Failures. Semantic Scholar did not know the DOI-only paper and answered “not found” in all 9 calls for it, which shows up as error in sources and does not affect the rest. No rate limit (HTTP 429) occurred in these calls without an API key; if one does, the source shows error with the reason and the record is partial.
Cost control. Pass sources to leave out sources you do not need, keep ror_details off unless you want an institution’s place or website, and lower time_budget for an interactive answer. A slow request that misses the budget keeps its worker thread until it ends; each enricher allows at most four requests per source in flight, so repeated calls against a stalled service queue up, and are reported as timeout, instead of starting more threads.
Call epmc_status first. It makes one request to each of the four endpoints the tools depend on (search, full text, citations, annotations) and answers ok, degraded, down or unknown with the HTTP status and the time of each. The answer is kept for 30 seconds, so asking again does not add load to a service that may be struggling. If Europe PMC is healthy and a result looks stale, cache_status shows the hit rate of the in-memory caches and clear_cache empties one of them (search, article, annotations, xml or all); nothing on disk is touched.
A failed call comes back with isError: true and a message that names the cause. In the batch tools, a failure for one identifier does not fail the call: every input gets its own status in the order it was given.
| Tool | Limit |
|---|---|
analyze_search |
limit 1 to 1,000 records (500 by default) for the sample metrics; top_n 1 to 100; year_window 1 to 60 (30 by default) for by_year, one count request per year plus one for the older records |
get_papers_batch |
100 identifiers per call, looked up 50 at a time |
get_papers_fulltext_batch |
20 papers, within max_chars_per_paper and max_total_chars |
evaluate_query |
200 included and 200 excluded PMIDs |
pico_decompose |
questions up to 2,000 characters; offline |
tag_paper_sentences |
max_chars 1,000 to 500,000 (30,000 by default), whole sentences; use_llm looks at 40 sentences at most |
mesh_expand |
20 terms per call; offline unless use_api is set |
enrich_paper |
time_budget 2 to 120 seconds (30 by default); at most 30 authors, 20 funders and 20 notices in the answer; at most four requests per source in flight |
export_papers |
1,000 records per call; continue with skip or the remaining ids |
find_papers_by_entity |
1 to 8 articles per page, continued with cursor |
get_paper_fulltext |
max_chars up to 500,000 |