Skip to main content
Glama

Openalex Search Entities

openalex_search_entities
Read-onlyIdempotent

Search, filter, sort, or retrieve by ID. Covers all OpenAlex entity types (works, authors, sources, institutions, topics, keywords, publishers, funders, awards). Pass id to retrieve a single entity. Otherwise, use query and/or filters for discovery. Filters take a record object; an unambiguous native string such as "issn:0920-8542" is also accepted when enabled and disclosed in notice. Native clauses split on commas outside paired double quotes and the first colon; duplicate keys and bare fragments are rejected. Any filter value sent unquoted must have paired double quotes; balanced search phrases remain valid. Supports keyword search with boolean operators, exact phrase matching, AI semantic search, and seeded random samples. Use openalex_resolve_name to resolve names to IDs before filtering. Searches and ID lookups return a curated set of fields by default; pass select to override with specific fields, or ["*"] for the full record. A page holds per_page records and next_cursor continues it — semantic search and a seeded sample continue with page instead; an id lookup returns every selected array whole, and slice pages one array by count.

Input Schema

NameRequiredDescriptionDefault
idNoRetrieve a single entity by ID. Supports: OpenAlex ID ("W2741809807", or its OpenAlex record-page or API URL such as "https://openalex.org/works/W2741809807" or "https://api.openalex.org/works/W2741809807"), whose letter must name `entity_type` — W works, A authors, S sources, I institutions, T topics, P publishers, F funders, G awards; DOI ("10.1038/nature12373"; under entity_type "funders", a funder's Crossref Funder Registry DOI such as "10.13039/100000001"), ORCID ("0000-0002-1825-0097"), ROR ("https://ror.org/00hx57361" — one ROR can name an institution, a funder, and a publisher, and `entity_type` picks which), PMID ("12345678" or "https://pubmed.ncbi.nlm.nih.gov/12345678"), ISSN ("1234-5678"). Keywords are identified by slug rather than a native ID — pass either the slug ("groundwater") or the URL a search returns ("https://openalex.org/keywords/groundwater"). A PMCID is recognized too, bare ("PMC1234567") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `["*"]` for the complete record). Every selected array returns whole; `slice` pages a long one by count. To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown.
pageNoPage number (1-based) for semantic search and for a seeded `sample`, the two searches that paginate with `page` instead of `cursor`. Semantic search ranks a query-dependent candidate set whose size `meta.count` reports, so the last reachable page is ceil(meta.count / per_page) — e.g. page 14 for a count of 70 with per_page=5. A sample draws min(sample, meta.count) records, so its last page is ceil(min(sample, meta.count) / per_page); paging a sample requires `seed`, and OpenAlex serves only its first 10,000 records, so a page × per_page above 10,000 is rejected. Passing it with any other search is rejected.
seedNoDeterministic seed for `sample`. Same seed + same filters = the same draw, page for page — required to walk a sample larger than one page with `page`, and worth passing whenever reproducibility matters. Has no effect without `sample`, and a search that passes it alone is rejected.
sortNoSort field. Prefix with "-" for descending. Comma-separate for a multi-key sort, applied left to right, with the "-" prefix set per key ("-publication_year,cited_by_count" sorts by year descending, then citations ascending). Common: "cited_by_count", "-publication_date", "-relevance_score" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use "-relevance_score" or omit sort to keep the most relevant results first. "-relevance_score" requires an active search via "query" or a "filter:search" filter — passing it without one will fail. Not combinable with `sample` — a search passing both is rejected.
queryNoText search query. Supports boolean operators (AND, OR, NOT), quoted phrases ("exact match"), wildcards (machin*), fuzzy matching (machin~1), and proximity ("climate change"~5). Omit for filter-only queries — an empty string is rejected, since a blank search is a mistake rather than a request for the whole catalog.
sliceNoPage one array of the record at `id`: returns `id`, `display_name`, and `limit` elements of `field` from `offset` — fewer only at the array end — with the window in `windows`, whose `next` call repeats `limit` from the following offset. Requires `id`; replaces `select`. An offset at or past the end returns an empty window. A 100-author list cap can be continued on the singleton; at 5,000 stored authors the byline remains possibly capped, but `next` is null once stored entries are exhausted.
cursorNoPagination cursor from a previous response. Pass to get the next page. Omit it on the first call — an empty string is rejected, since a supplied-but-blank cursor is a caller mistake rather than a request for page 1. Keyword and exact modes only — semantic search walks its candidates with `page`, and a `cursor` sent with it is rejected.
sampleNoReturn a random sample of this many entities matching the filters, up to 10,000; a larger value draws 10,000 and says so (rejected instead on a server set to OPENALEX_CLAMP_PER_PAGE=false). The sample comes back `per_page` records at a time — by default the whole sample up to 100 — and a larger one is walked with `page` and a `seed`, which fixes the draw; without `seed` only its first page is returned. `meta.count` stays the population matching the filters. Keyword and exact modes only: OpenAlex does not sample a semantic search, so `sample` with search_mode "semantic" is rejected. Cannot be combined with `cursor` or `sort` — a sample has no order, and a search passing either is rejected. Useful for unbiased exploration: spot-checking filter correctness, stratified review prompts, or generating exploration sets without bias toward most-cited.
selectNoOpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `["*"]` to retrieve the complete record (every field). Only top-level fields project, so a nested value is requested by its parent object: bibliometrics (`h_index`, `i10_index`, `2yr_mean_citedness`) live under `summary_stats` on authors, sources, institutions, publishers, and funders, and naming a leaf returns that object. On sources, `publisher` returns `host_organization_name`: the publisher of a journal, the hosting institution of a repository, and null on most sources. On works, `author_list` returns each author in authorship order as `{id, display_name, orcid}` (`orcid` null when absent), a compact alternative to `authorships`, which also carries institutions and affiliations; neither the default nor `["*"]` includes it. Invalid field names produce an error identifying the rejected field. A wide projection on a large page makes a large response, so name the fields you need. Not applied with `slice`. Example: ["doi", "author_list", "primary_topic"].
filtersNoFilter criteria as field:value pairs. AND across fields (multiple keys). OR within field: pipe-separate ("us|gb"). NOT: prefix "!" ("!us"). Range: "2020-2024". Comparison: ">100", "<50". AND within same field: "+"-separate ("us+gb"); inside a `.search` value, a DOI, a date (a timestamp's "+00:00" offset), a URL field such as landing_page_url, an exactly matched name (display_name, awards.funder_display_name — "C++", "Erasmus+"), a biblio field (issue "1+2"), or awards.funder_award_id, "+" is a literal character. A value cannot mix "|" with an AND operator — a "+", or a space between values on an ID, code, or `language` key (a space inside a language name is part of the name, and a country field never reads a space as AND) — OpenAlex splits it on "|" first and drops every alternative that ANDs more than one value, so such a value is rejected; put the AND in a second key instead ("institutions.country_code": "US" beside "authorships.institutions.country_code": "DE|FR" on works). On a field matched as whole text (display_name, awards.funder_display_name, awards.funder_award_id, geo.city, geo.region, raw_affiliation_strings, block_key, biblio fields), a space is part of the value: "New York", "!New York", and "New York|Boston" match as written, but quotes around one "|" alternative make that alternative match nothing. A comma is part of such a value too ("University of California, Berkeley" on raw_affiliation_strings): a value holding one is sent as one quoted phrase, its "+" a literal character. A value sent as a quoted phrase cannot contain a double quote of its own. A multi-word value there cannot be ANDed with another ("New York+Boston" matches nothing); to require two funders or institutions, AND their IDs (awards.funder_id, authorships.institutions.id). Country fields (keys ending in country_code, country_codes, or countries) take ISO 3166-1 alpha-2 codes ("DE"): an alpha-3 code ("DEU"), numeric code ("276"), or English name ("Germany", "South Korea") is converted, "UK" becomes "GB", and a value that cannot be a country code is rejected. A space there is part of a name, never a separator: "South Korea|Japan" ORs two countries, and "DE US" is rejected. `language` takes ISO 639-1 codes ("en"): an ISO 639-2 code ("eng") or English name ("English") is converted. Two keys that resolve to the same upstream field (an alias and its canonical name, e.g. `year` and `publication_year`) are both applied and AND'd, so they narrow rather than override each other. Use OpenAlex IDs (not names) for entity filters — resolve names first. Common keys: `openalex` (filter by entity ID, e.g. {"openalex": "W123|W456"}), `cites` (works citing a given work), `publication_year` (range "2020-2024"), `authorships.author.id`, `type`, `is_oa`.
per_pageNoResults per page. Default 25, or under `sample` the sample size up to 100, so a sample of 100 or fewer returns whole in one call. A larger value returns at most 100 and says so — at most 50 under search_mode "semantic", which OpenAlex also limits to about 1 request per second. A server set to OPENALEX_CLAMP_PER_PAGE=false rejects a value over either cap instead. An `id` lookup ignores page size.
entity_typeYesType of scholarly entity to search.
search_modeNoSearch strategy. "keyword": stemmed full-text (default). "exact": no stemming, matches individual words (use quoted phrases for multi-word exact match). "semantic": AI embedding similarity over a query-dependent candidate set whose size `meta.count` reports, at ~1 req/sec, up to 50 per page, and paginated with `page` rather than `cursor`.keyword

Output Schema

NameRequiredDescriptionDefault
echoNoCompact echo of the criteria that actually ran (entity_type, query, filters, sort, search_mode) — surfaces what was searched when results are empty. An `id` lookup echoes entity_type and id alone, because the search criteria are not applied on that path.
metaNoResult metadata including pagination.
errorNoPresent when the call failed. Absent on success.
budgetNoWhat this call cost against the OpenAlex daily budget and what is left of it. Price a full traversal before committing to it: `totalCount` ÷ `per_page` × `costUsd` against `remainingUsd`. Absent when OpenAlex omitted the accounting headers.
noticeNoGuidance notice. Set when per_page, sample, or slice.limit was clamped (requested and applied sizes), a native filter string was converted, a first call has no matches, a continuation is exhausted, an id lookup ignores search criteria, semantic search reports a candidate total and whether a later `page` holds more, a sample draws more than one page (the next `page`, its last page, or that `seed` makes it pageable), an author array is possibly capped (100 list entries or 5,000 stored singleton entries), a slice offset is past the end, or slice replaces select. Absent otherwise.
resultsNoOpenAlex entity objects. Text values are plain text — HTML entities decoded, HTML/JATS/MathML markup removed — except identifier and URL fields such as `id`, `doi`, `ids`, and `*_url`, returned exactly as OpenAlex stores them; an abstract arrives reconstructed as `abstract`, and a selected `author_list` as one `{id, display_name, orcid}` entry per author. Additional fields depend on entity_type and select. Possibly capped author arrays and `slice` windows are in `windows`.
windowsNoAuthor arrays possibly capped at 100 on a list record or 5,000 stored on a singleton, and the window of every `slice` call — present even when the slice holds the whole array. A slice window's `next` repeats its `slice.limit` from the following offset; `next` is null at the stored-array end, including a possibly capped singleton.
totalCountNoTotal results matching the query/filters across all pages.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed24 schema fields changed
    • changedInput schema / properties / entity_type / enum
      Previous value: -[
      -  "works",
      -  "authors",
      -  "sources",
      -  "institutions",
      -  "topics",
      -  "keywords",
      -  "publishers",
      -  "funders"
      -]New value: +[
      +  "works",
      +  "authors",
      +  "sources",
      +  "institutions",
      +  "topics",
      +  "keywords",
      +  "publishers",
      +  "funders",
      +  "awards"
      +]
    • changedInput schema / properties / id / description
      Previous value: -"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\", or its OpenAlex record-page or API URL such as \"https://openalex.org/works/W2741809807\" or \"https://api.openalex.org/works/W2741809807\"), whose letter must name `entity_type` — W works, A authors, S sources, I institutions, T topics, P publishers, F funders; DOI (\"10.1038/nature12373\"; under entity_type \"funders\", a funder's Crossref Funder Registry DOI such as \"10.13039/100000001\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\" — one ROR can name an institution, a funder, and a publisher, and `entity_type` picks which), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). Keywords are identified by slug rather than a native ID — pass either the slug (\"groundwater\") or the URL a search returns (\"https://openalex.org/keywords/groundwater\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). An array too long for the response budget comes back as a window (`windows`); page the rest with `slice`. To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."New value: +"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\", or its OpenAlex record-page or API URL such as \"https://openalex.org/works/W2741809807\" or \"https://api.openalex.org/works/W2741809807\"), whose letter must name `entity_type` — W works, A authors, S sources, I institutions, T topics, P publishers, F funders, G awards; DOI (\"10.1038/nature12373\"; under entity_type \"funders\", a funder's Crossref Funder Registry DOI such as \"10.13039/100000001\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\" — one ROR can name an institution, a funder, and a publisher, and `entity_type` picks which), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). Keywords are identified by slug rather than a native ID — pass either the slug (\"groundwater\") or the URL a search returns (\"https://openalex.org/keywords/groundwater\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). Every selected array returns whole; `slice` pages a long one by count. To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."
    • changedInput schema / properties / page / description
      Previous value: -"Page number (1-based) for semantic search, the one mode that paginates with `page` instead of `cursor`. Semantic search ranks a query-dependent candidate set whose size `meta.count` reports, so the last reachable page is ceil(meta.count / per_page) — e.g. page 14 for a count of 70 with per_page=5. Passing it under any other search_mode is rejected."New value: +"Page number (1-based) for semantic search and for a seeded `sample`, the two searches that paginate with `page` instead of `cursor`. Semantic search ranks a query-dependent candidate set whose size `meta.count` reports, so the last reachable page is ceil(meta.count / per_page) — e.g. page 14 for a count of 70 with per_page=5. A sample draws min(sample, meta.count) records, so its last page is ceil(min(sample, meta.count) / per_page); paging a sample requires `seed`, and OpenAlex serves only its first 10,000 records, so a page × per_page above 10,000 is rejected. Passing it with any other search is rejected."
    • removedInput schema / properties / per_page / default
      Removed value: -25
    • changedInput schema / properties / per_page / description
      Previous value: -"Results per page. Default 25; positive integers above 100 clamp to 100 with a notice unless OPENALEX_CLAMP_PER_PAGE=false restores rejection. Semantic search caps at 50 — when search_mode=\"semantic\", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream). The semantic cap checks the requested size. An `id` lookup ignores page size, and `sample` overrides it. A budget-cut page returns fewer (`omitted`)."New value: +"Results per page. Default 25, or under `sample` the sample size up to 100, so a sample of 100 or fewer returns whole in one call. A larger value returns at most 100 and says so — at most 50 under search_mode \"semantic\", which OpenAlex also limits to about 1 request per second. A server set to OPENALEX_CLAMP_PER_PAGE=false rejects a value over either cap instead. An `id` lookup ignores page size."
    • removedInput schema / properties / per_page / maximum
      Removed value: -9007199254740991
    • changedInput schema / properties / sample / description
      Previous value: -"Return a random sample of this many entities matching the filters (1-100). Single page only — neither `cursor` nor `page` pagination applies to sampling, and a search that passes either alongside it is rejected. Keyword and exact modes only: OpenAlex does not sample a semantic search, so `sample` with search_mode \"semantic\" is rejected. Cannot be combined with `sort` — a sample has no order, and a search passing both is rejected. Overrides `per_page`. Useful for unbiased exploration: spot-checking filter correctness, stratified review prompts, or generating exploration sets without bias toward most-cited."New value: +"Return a random sample of this many entities matching the filters, up to 10,000; a larger value draws 10,000 and says so (rejected instead on a server set to OPENALEX_CLAMP_PER_PAGE=false). The sample comes back `per_page` records at a time — by default the whole sample up to 100 — and a larger one is walked with `page` and a `seed`, which fixes the draw; without `seed` only its first page is returned. `meta.count` stays the population matching the filters. Keyword and exact modes only: OpenAlex does not sample a semantic search, so `sample` with search_mode \"semantic\" is rejected. Cannot be combined with `cursor` or `sort` — a sample has no order, and a search passing either is rejected. Useful for unbiased exploration: spot-checking filter correctness, stratified review prompts, or generating exploration sets without bias toward most-cited."
    • removedInput schema / properties / sample / maximum
      Removed value: -100
    • changedInput schema / properties / seed / description
      Previous value: -"Deterministic seed for `sample`. Same seed + same filters = same results — pass when reproducibility matters. Has no effect without `sample`, and a search that passes it alone is rejected."New value: +"Deterministic seed for `sample`. Same seed + same filters = the same draw, page for page — required to walk a sample larger than one page with `page`, and worth passing whenever reproducibility matters. Has no effect without `sample`, and a search that passes it alone is rejected."
    • changedInput schema / properties / select / description
      Previous value: -"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Only top-level fields project, so a nested value is requested by its parent object: bibliometrics (`h_index`, `i10_index`, `2yr_mean_citedness`) live under `summary_stats` on authors, sources, institutions, publishers, and funders, and naming a leaf returns that object. On sources, `publisher` returns `host_organization_name`: the publisher of a journal, the hosting institution of a repository, and null on most sources. On works, `author_list` returns each author in authorship order as `{id, display_name, orcid}` (`orcid` null when absent), a compact alternative to `authorships`, which also carries institutions and affiliations; neither the default nor `[\"*\"]` includes it. Invalid field names produce an error identifying the rejected field. Large projections may be cut to fit the response budget (`omitted`, `windows`). Not applied with `slice`. Example: [\"doi\", \"author_list\", \"primary_topic\"]."New value: +"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Only top-level fields project, so a nested value is requested by its parent object: bibliometrics (`h_index`, `i10_index`, `2yr_mean_citedness`) live under `summary_stats` on authors, sources, institutions, publishers, and funders, and naming a leaf returns that object. On sources, `publisher` returns `host_organization_name`: the publisher of a journal, the hosting institution of a repository, and null on most sources. On works, `author_list` returns each author in authorship order as `{id, display_name, orcid}` (`orcid` null when absent), a compact alternative to `authorships`, which also carries institutions and affiliations; neither the default nor `[\"*\"]` includes it. Invalid field names produce an error identifying the rejected field. A wide projection on a large page makes a large response, so name the fields you need. Not applied with `slice`. Example: [\"doi\", \"author_list\", \"primary_topic\"]."
    • changedInput schema / properties / slice / description
      Previous value: -"Page one array of the record at `id`: returns `id`, `display_name`, and `field` from `offset` onward, as many elements as fit the response budget (at least one), with the window and its `next` call in `windows`. Requires `id`; replaces `select`. An offset at or past the end returns an empty window. A 100-author list cap can be continued on the singleton; at 5,000 stored authors the byline remains possibly capped, but `next` is null once stored entries are exhausted."New value: +"Page one array of the record at `id`: returns `id`, `display_name`, and `limit` elements of `field` from `offset` — fewer only at the array end — with the window in `windows`, whose `next` call repeats `limit` from the following offset. Requires `id`; replaces `select`. An offset at or past the end returns an empty window. A 100-author list cap can be continued on the singleton; at 5,000 stored authors the byline remains possibly capped, but `next` is null once stored entries are exhausted."
    • addedInput schema / properties / slice / properties / limit
      Added value: +{
      +  "default": 50,
      +  "description": "Elements to return. Default 50; a larger value returns at most 5,000 and says so (rejected instead on a server set to OPENALEX_CLAMP_PER_PAGE=false).",
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `invalid_filter_string`: A native filter string has an incomplete clause or unmatched double quotes. `duplicate_filter_string_key`: A native filter string repeats an exact key. `unmatched_quote_in_filter_value`: A filter value sent unquoted has an unmatched double quote. `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `slice_without_id`: A search passed `slice` without `id` — slice pages one array of one record. `slice_field_not_array`: The `slice` field is `*`, or is not an array on the record at `id` — absent, null, a scalar, or an object. `id_type_mismatch`: `id` is a native OpenAlex ID whose letter names another entity type than `entity_type`, or a concept ID (C…), a type this server does not serve. `id_dot_segment`: `id` holds a `.` or `..` path segment, written plainly or percent-encoded. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_plan_required`: A filter is one OpenAlex serves only on a paid plan — created_date, updated_date, or a from_/to_ form of either (HTTP 429 \"Plan upgrade required\"). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, a body that was empty, HTML, or unparseable JSON, or a semantic search whose query embedding failed upstream (HTTP 400 `Failed to embed query:`, or semantic search not configured). `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator, on a field that takes no phrase. Free-text search filters (any `.search` key, such as title.search or abstract.search.exact) and fields matched as whole text (display_name, awards.funder_display_name, awards.funder_award_id, geo.city, geo.region, raw_affiliation_strings, block_key, parsed_longest_name, biblio fields) are exempt: a comma there is sent quoted, as part of the phrase. `invalid_country_code`: A country filter (a key ending in country_code, country_codes, or countries) holds a segment that cannot be a country code, such as a misspelled name, an unassigned number, \"unknown\", or two codes joined by a space (\"DE US\"). `mixed_and_or_in_filter_value`: A filter value puts \"|\" (OR) beside an AND operator — a \"+\", or a space on a field where words mean AND (on a country field a space is always part of a country name, and inside a language name it is part of the name). OpenAlex splits the value on \"|\" first and drops every alternative that ANDs more than one value, so no single value can carry both. `quote_in_filter_value`: A filter value sent to OpenAlex double-quoted holds a double quote inside its outer pair — a value the server quotes (a multi-word value on a field matched as whole text, or a value holding a comma on such a field or on a free-text search filter), or one the caller wrapped in double quotes on any filter but a free-text search. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_filter_string`: A native filter string has an incomplete clause or unmatched double quotes. `duplicate_filter_string_key`: A native filter string repeats an exact key. `unmatched_quote_in_filter_value`: A filter value sent unquoted has an unmatched double quote. `semantic_per_page_cap`: A semantic search (no `id`) set per_page above its cap of 50 on a server with OPENALEX_CLAMP_PER_PAGE=false; with clamping on (the default), it returns 50 and says so. `response_too_large`: The response's structuredContent or content[] text would exceed 8,000,000 bytes — a wide `select` on a large page, or a record with very long arrays. A page is never shortened to fit. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` with neither search_mode \"semantic\" nor `sample`. `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_page_without_seed`: A search (no `id`) provided `page` with `sample` but no `seed`, which OpenAlex refuses: an unseeded sample redraws on every call, so its pages would overlap. `sample_page_too_deep`: A sample page reaches past record 10,000 (`page` × `per_page` above 10,000), the most of a sample OpenAlex serves by page number. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `slice_without_id`: A search passed `slice` without `id` — slice pages one array of one record. `slice_field_not_array`: The `slice` field is `*`, or is not an array on the record at `id` — absent, null, a scalar, or an object. `id_type_mismatch`: `id` is a native OpenAlex ID whose letter names another entity type than `entity_type`, or a concept ID (C…), a type this server does not serve. `id_dot_segment`: `id` holds a `.` or `..` path segment, written plainly or percent-encoded. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_plan_required`: A filter is one OpenAlex serves only on a paid plan — created_date, updated_date, or a from_/to_ form of either (HTTP 429 \"Plan upgrade required\"). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, a body that was empty, HTML, or unparseable JSON, or a semantic search whose query embedding failed upstream (HTTP 400 `Failed to embed query:`, or semantic search not configured). `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator, on a field that takes no phrase. Free-text search filters (any `.search` key, such as title.search or abstract.search.exact) and fields matched as whole text (display_name, awards.funder_display_name, awards.funder_award_id, geo.city, geo.region, raw_affiliation_strings, block_key, parsed_longest_name, biblio fields) are exempt: a comma there is sent quoted, as part of the phrase. `invalid_country_code`: A country filter (a key ending in country_code, country_codes, or countries) holds a segment that cannot be a country code, such as a misspelled name, an unassigned number, \"unknown\", or two codes joined by a space (\"DE US\"). `mixed_and_or_in_filter_value`: A filter value puts \"|\" (OR) beside an AND operator — a \"+\", or a space on a field where words mean AND (on a country field a space is always part of a country name, and inside a language name it is part of the name). OpenAlex splits the value on \"|\" first and drops every alternative that ANDs more than one value, so no single value can carry both. `quote_in_filter_value`: A filter value sent to OpenAlex double-quoted holds a double quote inside its outer pair — a value the server quotes (a multi-word value on a field matched as whole text, or a value holding a comma on such a field or on a free-text search filter), or one the caller wrapped in double quotes on any filter but a free-text search. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "invalid_filter_string",
      -  "duplicate_filter_string_key",
      -  "unmatched_quote_in_filter_value",
      -  "semantic_per_page_cap",
      -  "semantic_without_query",
      -  "semantic_with_cursor",
      -  "page_without_semantic",
      -  "sample_with_cursor",
      -  "sample_with_page",
      -  "sample_with_semantic",
      -  "sample_with_sort",
      -  "seed_without_sample",
      -  "slice_without_id",
      -  "slice_field_not_array",
      -  "id_type_mismatch",
      -  "id_dot_segment",
      -  "entity_not_found",
      -  "rate_limited",
      -  "upstream_budget_exhausted",
      -  "upstream_plan_required",
      -  "upstream_timeout",
      -  "upstream_unavailable",
      -  "upstream_unauthorized",
      -  "upstream_forbidden",
      -  "comma_in_filter_value",
      -  "invalid_country_code",
      -  "mixed_and_or_in_filter_value",
      -  "quote_in_filter_value",
      -  "upstream_invalid_params",
      -  "upstream_invalid_id_value",
      -  "upstream_sort_requires_search",
      -  "query_too_long",
      -  "upstream_invalid_params_other",
      -  "upstream_validation_failed"
      -]New value: +[
      +  "invalid_filter_string",
      +  "duplicate_filter_string_key",
      +  "unmatched_quote_in_filter_value",
      +  "semantic_per_page_cap",
      +  "response_too_large",
      +  "semantic_without_query",
      +  "semantic_with_cursor",
      +  "page_without_semantic",
      +  "sample_with_cursor",
      +  "sample_page_without_seed",
      +  "sample_page_too_deep",
      +  "sample_with_semantic",
      +  "sample_with_sort",
      +  "seed_without_sample",
      +  "slice_without_id",
      +  "slice_field_not_array",
      +  "id_type_mismatch",
      +  "id_dot_segment",
      +  "entity_not_found",
      +  "rate_limited",
      +  "upstream_budget_exhausted",
      +  "upstream_plan_required",
      +  "upstream_timeout",
      +  "upstream_unavailable",
      +  "upstream_unauthorized",
      +  "upstream_forbidden",
      +  "comma_in_filter_value",
      +  "invalid_country_code",
      +  "mixed_and_or_in_filter_value",
      +  "quote_in_filter_value",
      +  "upstream_invalid_params",
      +  "upstream_invalid_id_value",
      +  "upstream_sort_requires_search",
      +  "query_too_long",
      +  "upstream_invalid_params_other",
      +  "upstream_validation_failed"
      +]
    • changedOutput schema / properties / meta / properties / count / description
      Previous value: -"Total results matching the query/filters. Under search_mode \"semantic\" it is instead the size of the ranked candidate set — the most results `page` can reach — not an exhaustive match total."New value: +"Total results matching the query/filters — under `sample` too, whose draw holds min(sample, count) records. Under search_mode \"semantic\" it is instead the size of the ranked candidate set — the most results `page` can reach — not an exhaustive match total."
    • changedOutput schema / properties / meta / properties / next_cursor / description
      Previous value: -"Cursor for next page. null if no more results. Continues after the full upstream page, `omitted` records included."New value: +"Cursor for the next page; pass it back unchanged as `cursor`. null when no further page exists — always under semantic search and `sample`, which continue with `page` — except on a final page that exactly fills `per_page`: OpenAlex still returns a cursor there, and it leads to an empty page."
    • changedOutput schema / properties / meta / properties / per_page / description
      Previous value: -"Page size OpenAlex echoed for this request — the applied per_page after clamping or sample override, not the number of records returned. A singleton reports 1. A short or exhausted page carries fewer records than this, as does a page the response budget cut (`omitted`)."New value: +"Page size OpenAlex echoed for this request — the applied per_page after clamping or its default, not the number of records returned. A singleton reports 1. A short or exhausted page carries fewer records than this."
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance notice. Set when per_page was clamped (requested and applied sizes), a native filter string was converted, a first call has no matches, a continuation is exhausted, an id lookup ignores search criteria, semantic search reports a candidate total, the response budget cut records, windowed an array, or ran over, an author array is possibly capped (100 list entries or 5,000 stored singleton entries), a slice offset is past the end, or slice replaces select. Absent otherwise."New value: +"Guidance notice. Set when per_page, sample, or slice.limit was clamped (requested and applied sizes), a native filter string was converted, a first call has no matches, a continuation is exhausted, an id lookup ignores search criteria, semantic search reports a candidate total and whether a later `page` holds more, a sample draws more than one page (the next `page`, its last page, or that `seed` makes it pageable), an author array is possibly capped (100 list entries or 5,000 stored singleton entries), a slice offset is past the end, or slice replaces select. Absent otherwise."
    • removedOutput schema / properties / omitted
      Removed value: -{
      -  "additionalProperties": false,
      -  "description": "Present when the response budget cut whole records from the page end; pagination continues after the full upstream page.",
      -  "properties": {
      -    "ids": {
      -      "description": "Bare IDs of the records left out, in page order (a keyword keeps its URL).",
      -      "items": {
      -        "type": "string"
      -      },
      -      "type": "array"
      -    },
      -    "next": {
      -      "additionalProperties": false,
      -      "description": "One call returning those records as a set, in OpenAlex's order, not page order. Budgeted too, so it may carry its own `omitted`.",
      -      "properties": {
      -        "arguments": {
      -          "additionalProperties": {},
      -          "description": "Arguments to pass as-is.",
      -          "properties": {},
      -          "type": "object"
      -        },
      -        "tool": {
      -          "const": "openalex_search_entities",
      -          "description": "Tool to call.",
      -          "type": "string"
      -        }
      -      },
      -      "required": [
      -        "tool",
      -        "arguments"
      -      ],
      -      "type": "object"
      -    }
      -  },
      -  "required": [
      -    "ids",
      -    "next"
      -  ],
      -  "type": "object"
      -}
    • removedOutput schema / properties / over_budget
      Removed value: -{
      -  "description": "True when even the least this call can return — the first record's non-array fields (its arrays windowed to empty), or one `slice` element — exceeds the budget.",
      -  "type": "boolean"
      -}
    • changedOutput schema / properties / results / description
      Previous value: -"OpenAlex entity objects. Text values are plain text — HTML entities decoded, HTML/JATS/MathML markup removed — except identifier and URL fields such as `id`, `doi`, `ids`, and `*_url`, returned exactly as OpenAlex stores them; an abstract arrives reconstructed as `abstract`, and a selected `author_list` as one `{id, display_name, orcid}` entry per author. Additional fields depend on entity_type and select. Records cut by the response budget are in `omitted`; partial or possibly capped arrays are in `windows`."New value: +"OpenAlex entity objects. Text values are plain text — HTML entities decoded, HTML/JATS/MathML markup removed — except identifier and URL fields such as `id`, `doi`, `ids`, and `*_url`, returned exactly as OpenAlex stores them; an abstract arrives reconstructed as `abstract`, and a selected `author_list` as one `{id, display_name, orcid}` entry per author. Additional fields depend on entity_type and select. Possibly capped author arrays and `slice` windows are in `windows`."
    • changedOutput schema / properties / results / items / properties / id / description
      Previous value: -"OpenAlex ID URL (e.g., \"https://openalex.org/W2741809807\"). `windows` and `omitted` name records by the bare ID (\"W2741809807\"); a keyword keeps its URL."New value: +"OpenAlex ID URL (e.g., \"https://openalex.org/W2741809807\"). `windows` names records by the bare ID (\"W2741809807\"); a keyword keeps its URL."
    • changedOutput schema / properties / windows / description
      Previous value: -"Arrays windowed to fit the budget, paged by `slice`, or possibly capped at 100 list authors or 5,000 stored singleton authors. A `slice` call always carries its window, even for a whole array. `next` is null at the stored-array end, including a possibly capped singleton."New value: +"Author arrays possibly capped at 100 on a list record or 5,000 stored on a singleton, and the window of every `slice` call — present even when the slice holds the whole array. A slice window's `next` repeats its `slice.limit` from the following offset; `next` is null at the stored-array end, including a possibly capped singleton."
    • changedOutput schema / properties / windows / items / description
      Previous value: -"An array returned in part."New value: +"An array returned in part, or possibly in part."
  2. Changed10 schema fields changed
    • changedInput schema / properties / per_page / description
      Previous value: -"Results per page (1-100). Default 25. Semantic search caps at 50 — when search_mode=\"semantic\", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream). The cap applies to searches only; an `id` lookup returns its one record regardless of both. A budget-cut page returns fewer (`omitted`)."New value: +"Results per page. Default 25; positive integers above 100 clamp to 100 with a notice unless OPENALEX_CLAMP_PER_PAGE=false restores rejection. Semantic search caps at 50 — when search_mode=\"semantic\", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream). The semantic cap checks the requested size. An `id` lookup ignores page size, and `sample` overrides it. A budget-cut page returns fewer (`omitted`)."
    • changedInput schema / properties / per_page / maximum
      Previous value: -100New value: +9007199254740991
    • changedInput schema / properties / slice / description
      Previous value: -"Page one array of the record at `id`: returns `id`, `display_name`, and `field` from `offset` onward, as many elements as fit the response budget (at least one), with the window and its `next` call in `windows`. Requires `id`; replaces `select`. An offset at or past the end returns an empty window. Walks any partial array, including a possibly capped 100-entry `authorships` or `author_list`."New value: +"Page one array of the record at `id`: returns `id`, `display_name`, and `field` from `offset` onward, as many elements as fit the response budget (at least one), with the window and its `next` call in `windows`. Requires `id`; replaces `select`. An offset at or past the end returns an empty window. A 100-author list cap can be continued on the singleton; at 5,000 stored authors the byline remains possibly capped, but `next` is null once stored entries are exhausted."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `slice_without_id`: A search passed `slice` without `id` — slice pages one array of one record. `slice_field_not_array`: The `slice` field is `*`, or is not an array on the record at `id` — absent, null, a scalar, or an object. `id_type_mismatch`: `id` is a native OpenAlex ID whose letter names another entity type than `entity_type`, or a concept ID (C…), a type this server does not serve. `id_dot_segment`: `id` holds a `.` or `..` path segment, written plainly or percent-encoded. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_plan_required`: A filter is one OpenAlex serves only on a paid plan — created_date, updated_date, or a from_/to_ form of either (HTTP 429 \"Plan upgrade required\"). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, a body that was empty, HTML, or unparseable JSON, or a semantic search whose query embedding failed upstream (HTTP 400 `Failed to embed query:`, or semantic search not configured). `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator, on a field that takes no phrase. Free-text search filters (any `.search` key, such as title.search or abstract.search.exact) and fields matched as whole text (display_name, awards.funder_display_name, awards.funder_award_id, geo.city, geo.region, raw_affiliation_strings, block_key, parsed_longest_name, biblio fields) are exempt: a comma there is sent quoted, as part of the phrase. `invalid_country_code`: A country filter (a key ending in country_code, country_codes, or countries) holds a segment that cannot be a country code, such as a misspelled name, an unassigned number, \"unknown\", or two codes joined by a space (\"DE US\"). `mixed_and_or_in_filter_value`: A filter value puts \"|\" (OR) beside an AND operator — a \"+\", or a space on a field where words mean AND (on a country field a space is always part of a country name, and inside a language name it is part of the name). OpenAlex splits the value on \"|\" first and drops every alternative that ANDs more than one value, so no single value can carry both. `quote_in_filter_value`: A filter value sent to OpenAlex double-quoted holds a double quote inside its outer pair — a value the server quotes (a multi-word value on a field matched as whole text, or a value holding a comma on such a field or on a free-text search filter), or one the caller wrapped in double quotes on any filter but a free-text search. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `invalid_filter_string`: A native filter string has an incomplete clause or unmatched double quotes. `duplicate_filter_string_key`: A native filter string repeats an exact key. `unmatched_quote_in_filter_value`: A filter value sent unquoted has an unmatched double quote. `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `slice_without_id`: A search passed `slice` without `id` — slice pages one array of one record. `slice_field_not_array`: The `slice` field is `*`, or is not an array on the record at `id` — absent, null, a scalar, or an object. `id_type_mismatch`: `id` is a native OpenAlex ID whose letter names another entity type than `entity_type`, or a concept ID (C…), a type this server does not serve. `id_dot_segment`: `id` holds a `.` or `..` path segment, written plainly or percent-encoded. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_plan_required`: A filter is one OpenAlex serves only on a paid plan — created_date, updated_date, or a from_/to_ form of either (HTTP 429 \"Plan upgrade required\"). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, a body that was empty, HTML, or unparseable JSON, or a semantic search whose query embedding failed upstream (HTTP 400 `Failed to embed query:`, or semantic search not configured). `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator, on a field that takes no phrase. Free-text search filters (any `.search` key, such as title.search or abstract.search.exact) and fields matched as whole text (display_name, awards.funder_display_name, awards.funder_award_id, geo.city, geo.region, raw_affiliation_strings, block_key, parsed_longest_name, biblio fields) are exempt: a comma there is sent quoted, as part of the phrase. `invalid_country_code`: A country filter (a key ending in country_code, country_codes, or countries) holds a segment that cannot be a country code, such as a misspelled name, an unassigned number, \"unknown\", or two codes joined by a space (\"DE US\"). `mixed_and_or_in_filter_value`: A filter value puts \"|\" (OR) beside an AND operator — a \"+\", or a space on a field where words mean AND (on a country field a space is always part of a country name, and inside a language name it is part of the name). OpenAlex splits the value on \"|\" first and drops every alternative that ANDs more than one value, so no single value can carry both. `quote_in_filter_value`: A filter value sent to OpenAlex double-quoted holds a double quote inside its outer pair — a value the server quotes (a multi-word value on a field matched as whole text, or a value holding a comma on such a field or on a free-text search filter), or one the caller wrapped in double quotes on any filter but a free-text search. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "semantic_per_page_cap",
      -  "semantic_without_query",
      -  "semantic_with_cursor",
      -  "page_without_semantic",
      -  "sample_with_cursor",
      -  "sample_with_page",
      -  "sample_with_semantic",
      -  "sample_with_sort",
      -  "seed_without_sample",
      -  "slice_without_id",
      -  "slice_field_not_array",
      -  "id_type_mismatch",
      -  "id_dot_segment",
      -  "entity_not_found",
      -  "rate_limited",
      -  "upstream_budget_exhausted",
      -  "upstream_plan_required",
      -  "upstream_timeout",
      -  "upstream_unavailable",
      -  "upstream_unauthorized",
      -  "upstream_forbidden",
      -  "comma_in_filter_value",
      -  "invalid_country_code",
      -  "mixed_and_or_in_filter_value",
      -  "quote_in_filter_value",
      -  "upstream_invalid_params",
      -  "upstream_invalid_id_value",
      -  "upstream_sort_requires_search",
      -  "query_too_long",
      -  "upstream_invalid_params_other",
      -  "upstream_validation_failed"
      -]New value: +[
      +  "invalid_filter_string",
      +  "duplicate_filter_string_key",
      +  "unmatched_quote_in_filter_value",
      +  "semantic_per_page_cap",
      +  "semantic_without_query",
      +  "semantic_with_cursor",
      +  "page_without_semantic",
      +  "sample_with_cursor",
      +  "sample_with_page",
      +  "sample_with_semantic",
      +  "sample_with_sort",
      +  "seed_without_sample",
      +  "slice_without_id",
      +  "slice_field_not_array",
      +  "id_type_mismatch",
      +  "id_dot_segment",
      +  "entity_not_found",
      +  "rate_limited",
      +  "upstream_budget_exhausted",
      +  "upstream_plan_required",
      +  "upstream_timeout",
      +  "upstream_unavailable",
      +  "upstream_unauthorized",
      +  "upstream_forbidden",
      +  "comma_in_filter_value",
      +  "invalid_country_code",
      +  "mixed_and_or_in_filter_value",
      +  "quote_in_filter_value",
      +  "upstream_invalid_params",
      +  "upstream_invalid_id_value",
      +  "upstream_sort_requires_search",
      +  "query_too_long",
      +  "upstream_invalid_params_other",
      +  "upstream_validation_failed"
      +]
    • changedOutput schema / properties / meta / properties / per_page / description
      Previous value: -"Page size OpenAlex echoed for this request — the requested per_page, not the number of records returned. A short or exhausted page carries fewer records than this, as does a page the response budget cut (`omitted`)."New value: +"Page size OpenAlex echoed for this request — the applied per_page after clamping or sample override, not the number of records returned. A singleton reports 1. A short or exhausted page carries fewer records than this, as does a page the response budget cut (`omitted`)."
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance notice. Set when a first call returns no results (echoes the criteria and suggests how to broaden), when a paginated call ran past its last page (says the traversal is finished instead of advising a broader query), when an `id` lookup was passed search criteria it does not apply (names them), on every semantic search to disclose that `meta.count` is a candidate total rather than a match total, when the response budget cut records, windowed an array, or ran over, when a list record carries exactly 100 authorships or author_list entries (possibly capped), when a `slice` offset is past the end of its array, and when `slice` was passed with `select`, which it replaces. Absent otherwise."New value: +"Guidance notice. Set when per_page was clamped (requested and applied sizes), a native filter string was converted, a first call has no matches, a continuation is exhausted, an id lookup ignores search criteria, semantic search reports a candidate total, the response budget cut records, windowed an array, or ran over, an author array is possibly capped (100 list entries or 5,000 stored singleton entries), a slice offset is past the end, or slice replaces select. Absent otherwise."
    • changedOutput schema / properties / windows / description
      Previous value: -"Arrays returned in part — windowed to fit the budget, paged by `slice`, or a possibly capped 100-entry `authorships` or `author_list` — each with its continuation. A `slice` call always carries its window, even for a whole array; otherwise absent when every array is whole."New value: +"Arrays windowed to fit the budget, paged by `slice`, or possibly capped at 100 list authors or 5,000 stored singleton authors. A `slice` call always carries its window, even for a whole array. `next` is null at the stored-array end, including a possibly capped singleton."
    • changedOutput schema / properties / windows / items / properties / possibly_capped / description
      Previous value: -"True for exactly 100 `authorships` or `author_list` entries on a list record (OpenAlex's cap): the list may be longer."New value: +"True for exactly 100 authorships or author_list entries on a list record, or 5,000 on a singleton lookup: the byline may be longer than the returned array."
    • changedOutput schema / properties / windows / items / properties / total / description
      Previous value: -"Array length as returned to this call."New value: +"Stored array length returned to this call, not a complete byline total."
  3. Changed12 schema fields changed
    • changedInput schema / properties / filters / description
      Previous value: -"Filter criteria as field:value pairs. AND across fields (multiple keys). OR within field: pipe-separate (\"us|gb\"). NOT: prefix \"!\" (\"!us\"). Range: \"2020-2024\". Comparison: \">100\", \"<50\". AND within same field: \"+\"-separate. Two keys that resolve to the same upstream field (an alias and its canonical name, e.g. `year` and `publication_year`) are both applied and AND'd, so they narrow rather than override each other. Use OpenAlex IDs (not names) for entity filters — resolve names first. Common keys: `openalex` (filter by entity ID, e.g. {\"openalex\": \"W123|W456\"}), `cites` (works citing a given work), `publication_year` (range \"2020-2024\"), `authorships.author.id`, `type`, `is_oa`."New value: +"Filter criteria as field:value pairs. AND across fields (multiple keys). OR within field: pipe-separate (\"us|gb\"). NOT: prefix \"!\" (\"!us\"). Range: \"2020-2024\". Comparison: \">100\", \"<50\". AND within same field: \"+\"-separate (\"us+gb\"); inside a `.search` value, a DOI, a date (a timestamp's \"+00:00\" offset), a URL field such as landing_page_url, an exactly matched name (display_name, awards.funder_display_name — \"C++\", \"Erasmus+\"), a biblio field (issue \"1+2\"), or awards.funder_award_id, \"+\" is a literal character. A value cannot mix \"|\" with an AND operator — a \"+\", or a space between values on an ID, code, or `language` key (a space inside a language name is part of the name, and a country field never reads a space as AND) — OpenAlex splits it on \"|\" first and drops every alternative that ANDs more than one value, so such a value is rejected; put the AND in a second key instead (\"institutions.country_code\": \"US\" beside \"authorships.institutions.country_code\": \"DE|FR\" on works). On a field matched as whole text (display_name, awards.funder_display_name, awards.funder_award_id, geo.city, geo.region, raw_affiliation_strings, block_key, biblio fields), a space is part of the value: \"New York\", \"!New York\", and \"New York|Boston\" match as written, but quotes around one \"|\" alternative make that alternative match nothing. A comma is part of such a value too (\"University of California, Berkeley\" on raw_affiliation_strings): a value holding one is sent as one quoted phrase, its \"+\" a literal character. A value sent as a quoted phrase cannot contain a double quote of its own. A multi-word value there cannot be ANDed with another (\"New York+Boston\" matches nothing); to require two funders or institutions, AND their IDs (awards.funder_id, authorships.institutions.id). Country fields (keys ending in country_code, country_codes, or countries) take ISO 3166-1 alpha-2 codes (\"DE\"): an alpha-3 code (\"DEU\"), numeric code (\"276\"), or English name (\"Germany\", \"South Korea\") is converted, \"UK\" becomes \"GB\", and a value that cannot be a country code is rejected. A space there is part of a name, never a separator: \"South Korea|Japan\" ORs two countries, and \"DE US\" is rejected. `language` takes ISO 639-1 codes (\"en\"): an ISO 639-2 code (\"eng\") or English name (\"English\") is converted. Two keys that resolve to the same upstream field (an alias and its canonical name, e.g. `year` and `publication_year`) are both applied and AND'd, so they narrow rather than override each other. Use OpenAlex IDs (not names) for entity filters — resolve names first. Common keys: `openalex` (filter by entity ID, e.g. {\"openalex\": \"W123|W456\"}), `cites` (works citing a given work), `publication_year` (range \"2020-2024\"), `authorships.author.id`, `type`, `is_oa`."
    • changedInput schema / properties / id / description
      Previous value: -"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). Keywords are identified by slug rather than a native ID — pass either the slug (\"groundwater\") or the URL a search returns (\"https://openalex.org/keywords/groundwater\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). An array too long for the response budget comes back as a window (`windows`); page the rest with `slice`. To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."New value: +"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\", or its OpenAlex record-page or API URL such as \"https://openalex.org/works/W2741809807\" or \"https://api.openalex.org/works/W2741809807\"), whose letter must name `entity_type` — W works, A authors, S sources, I institutions, T topics, P publishers, F funders; DOI (\"10.1038/nature12373\"; under entity_type \"funders\", a funder's Crossref Funder Registry DOI such as \"10.13039/100000001\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\" — one ROR can name an institution, a funder, and a publisher, and `entity_type` picks which), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). Keywords are identified by slug rather than a native ID — pass either the slug (\"groundwater\") or the URL a search returns (\"https://openalex.org/keywords/groundwater\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). An array too long for the response budget comes back as a window (`windows`); page the rest with `slice`. To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."
    • changedInput schema / properties / select / description
      Previous value: -"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Only top-level fields project, so a nested value is requested by its parent object: bibliometrics (`h_index`, `i10_index`, `2yr_mean_citedness`) live under `summary_stats` on authors, sources, institutions, publishers, and funders, and naming a leaf returns that object. Invalid field names produce an error identifying the rejected field. Large projections may be cut to fit the response budget (`omitted`, `windows`). Not applied with `slice`. Example: [\"doi\", \"authorships\", \"primary_topic\"]."New value: +"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Only top-level fields project, so a nested value is requested by its parent object: bibliometrics (`h_index`, `i10_index`, `2yr_mean_citedness`) live under `summary_stats` on authors, sources, institutions, publishers, and funders, and naming a leaf returns that object. On sources, `publisher` returns `host_organization_name`: the publisher of a journal, the hosting institution of a repository, and null on most sources. On works, `author_list` returns each author in authorship order as `{id, display_name, orcid}` (`orcid` null when absent), a compact alternative to `authorships`, which also carries institutions and affiliations; neither the default nor `[\"*\"]` includes it. Invalid field names produce an error identifying the rejected field. Large projections may be cut to fit the response budget (`omitted`, `windows`). Not applied with `slice`. Example: [\"doi\", \"author_list\", \"primary_topic\"]."
    • changedInput schema / properties / slice / description
      Previous value: -"Page one array of the record at `id`: returns `id`, `display_name`, and `field` from `offset` onward, as many elements as fit the response budget (at least one), with the window and its `next` call in `windows`. Requires `id`; replaces `select`. An offset at or past the end returns an empty window. Walks any partial array, including a possibly capped 100-authorship list."New value: +"Page one array of the record at `id`: returns `id`, `display_name`, and `field` from `offset` onward, as many elements as fit the response budget (at least one), with the window and its `next` call in `windows`. Requires `id`; replaces `select`. An offset at or past the end returns an empty window. Walks any partial array, including a possibly capped 100-entry `authorships` or `author_list`."
    • changedInput schema / properties / slice / properties / field / description
      Previous value: -"Top-level array field to page, such as \"authorships\" or \"referenced_works\". Leave it empty to skip slicing."New value: +"Top-level array field to page, such as \"authorships\", \"author_list\", or \"referenced_works\". Leave it empty to skip slicing."
    • changedOutput schema / properties / budget / properties / costUsd / description
      Previous value: -"USD this call spent. 0 for an `id` lookup — OpenAlex does not bill single-entity fetches, so batching known IDs beats paging a filtered list."New value: +"USD this call spent. 0 for an `id` lookup by an OpenAlex ID, DOI, ORCID, ROR, PMID, or ISSN — OpenAlex does not bill those single-entity fetches, so batching known IDs beats paging a filtered list. OpenAlex bills like one list request a keyword looked up by its slug, an `id` carrying characters past the identifier itself (`W2741809807#x` still returns W2741809807), and a funder looked up by its Funder Registry DOI."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `slice_without_id`: A search passed `slice` without `id` — slice pages one array of one record. `slice_field_not_array`: The `slice` field is `*`, or is not an array on the record at `id` — absent, null, a scalar, or an object. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `slice_without_id`: A search passed `slice` without `id` — slice pages one array of one record. `slice_field_not_array`: The `slice` field is `*`, or is not an array on the record at `id` — absent, null, a scalar, or an object. `id_type_mismatch`: `id` is a native OpenAlex ID whose letter names another entity type than `entity_type`, or a concept ID (C…), a type this server does not serve. `id_dot_segment`: `id` holds a `.` or `..` path segment, written plainly or percent-encoded. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_plan_required`: A filter is one OpenAlex serves only on a paid plan — created_date, updated_date, or a from_/to_ form of either (HTTP 429 \"Plan upgrade required\"). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, a body that was empty, HTML, or unparseable JSON, or a semantic search whose query embedding failed upstream (HTTP 400 `Failed to embed query:`, or semantic search not configured). `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator, on a field that takes no phrase. Free-text search filters (any `.search` key, such as title.search or abstract.search.exact) and fields matched as whole text (display_name, awards.funder_display_name, awards.funder_award_id, geo.city, geo.region, raw_affiliation_strings, block_key, parsed_longest_name, biblio fields) are exempt: a comma there is sent quoted, as part of the phrase. `invalid_country_code`: A country filter (a key ending in country_code, country_codes, or countries) holds a segment that cannot be a country code, such as a misspelled name, an unassigned number, \"unknown\", or two codes joined by a space (\"DE US\"). `mixed_and_or_in_filter_value`: A filter value puts \"|\" (OR) beside an AND operator — a \"+\", or a space on a field where words mean AND (on a country field a space is always part of a country name, and inside a language name it is part of the name). OpenAlex splits the value on \"|\" first and drops every alternative that ANDs more than one value, so no single value can carry both. `quote_in_filter_value`: A filter value sent to OpenAlex double-quoted holds a double quote inside its outer pair — a value the server quotes (a multi-word value on a field matched as whole text, or a value holding a comma on such a field or on a free-text search filter), or one the caller wrapped in double quotes on any filter but a free-text search. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "semantic_per_page_cap",
      -  "semantic_without_query",
      -  "semantic_with_cursor",
      -  "page_without_semantic",
      -  "sample_with_cursor",
      -  "sample_with_page",
      -  "sample_with_semantic",
      -  "sample_with_sort",
      -  "seed_without_sample",
      -  "slice_without_id",
      -  "slice_field_not_array",
      -  "entity_not_found",
      -  "rate_limited",
      -  "upstream_budget_exhausted",
      -  "upstream_timeout",
      -  "upstream_unavailable",
      -  "upstream_unauthorized",
      -  "upstream_forbidden",
      -  "comma_in_filter_value",
      -  "upstream_invalid_params",
      -  "upstream_invalid_id_value",
      -  "upstream_sort_requires_search",
      -  "query_too_long",
      -  "upstream_invalid_params_other",
      -  "upstream_validation_failed"
      -]New value: +[
      +  "semantic_per_page_cap",
      +  "semantic_without_query",
      +  "semantic_with_cursor",
      +  "page_without_semantic",
      +  "sample_with_cursor",
      +  "sample_with_page",
      +  "sample_with_semantic",
      +  "sample_with_sort",
      +  "seed_without_sample",
      +  "slice_without_id",
      +  "slice_field_not_array",
      +  "id_type_mismatch",
      +  "id_dot_segment",
      +  "entity_not_found",
      +  "rate_limited",
      +  "upstream_budget_exhausted",
      +  "upstream_plan_required",
      +  "upstream_timeout",
      +  "upstream_unavailable",
      +  "upstream_unauthorized",
      +  "upstream_forbidden",
      +  "comma_in_filter_value",
      +  "invalid_country_code",
      +  "mixed_and_or_in_filter_value",
      +  "quote_in_filter_value",
      +  "upstream_invalid_params",
      +  "upstream_invalid_id_value",
      +  "upstream_sort_requires_search",
      +  "query_too_long",
      +  "upstream_invalid_params_other",
      +  "upstream_validation_failed"
      +]
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance notice. Set when a first call returns no results (echoes the criteria and suggests how to broaden), when a paginated call ran past its last page (says the traversal is finished instead of advising a broader query), when an `id` lookup was passed search criteria it does not apply (names them), on every semantic search to disclose that `meta.count` is a candidate total rather than a match total, when the response budget cut records, windowed an array, or ran over, when a list record carries exactly 100 authorships (possibly capped), when a `slice` offset is past the end of its array, and when `slice` was passed with `select`, which it replaces. Absent otherwise."New value: +"Guidance notice. Set when a first call returns no results (echoes the criteria and suggests how to broaden), when a paginated call ran past its last page (says the traversal is finished instead of advising a broader query), when an `id` lookup was passed search criteria it does not apply (names them), on every semantic search to disclose that `meta.count` is a candidate total rather than a match total, when the response budget cut records, windowed an array, or ran over, when a list record carries exactly 100 authorships or author_list entries (possibly capped), when a `slice` offset is past the end of its array, and when `slice` was passed with `select`, which it replaces. Absent otherwise."
    • changedOutput schema / properties / results / description
      Previous value: -"OpenAlex entity objects. Text values are plain text — HTML entities decoded, HTML/JATS/MathML markup removed — except identifier and URL fields such as `id`, `doi`, `ids`, and `*_url`, returned exactly as OpenAlex stores them; an abstract arrives reconstructed as `abstract`. Additional fields depend on entity_type and select. Records cut by the response budget are in `omitted`; partial or possibly capped arrays are in `windows`."New value: +"OpenAlex entity objects. Text values are plain text — HTML entities decoded, HTML/JATS/MathML markup removed — except identifier and URL fields such as `id`, `doi`, `ids`, and `*_url`, returned exactly as OpenAlex stores them; an abstract arrives reconstructed as `abstract`, and a selected `author_list` as one `{id, display_name, orcid}` entry per author. Additional fields depend on entity_type and select. Records cut by the response budget are in `omitted`; partial or possibly capped arrays are in `windows`."
    • changedOutput schema / properties / windows / description
      Previous value: -"Arrays returned in part — windowed to fit the budget, paged by `slice`, or 100 possibly capped authorships — each with its continuation. A `slice` call always carries its window, even for a whole array; otherwise absent when every array is whole."New value: +"Arrays returned in part — windowed to fit the budget, paged by `slice`, or a possibly capped 100-entry `authorships` or `author_list` — each with its continuation. A `slice` call always carries its window, even for a whole array; otherwise absent when every array is whole."
    • changedOutput schema / properties / windows / items / properties / possibly_capped / description
      Previous value: -"True for exactly 100 authorships on a list record (OpenAlex's cap): the list may be longer."New value: +"True for exactly 100 `authorships` or `author_list` entries on a list record (OpenAlex's cap): the list may be longer."
  4. Changed14 schema fields changed
    • changedInput schema / properties / id / description
      Previous value: -"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). Keywords are identified by slug rather than a native ID — pass either the slug (\"groundwater\") or the URL a search returns (\"https://openalex.org/keywords/groundwater\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."New value: +"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). Keywords are identified by slug rather than a native ID — pass either the slug (\"groundwater\") or the URL a search returns (\"https://openalex.org/keywords/groundwater\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). An array too long for the response budget comes back as a window (`windows`); page the rest with `slice`. To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."
    • changedInput schema / properties / per_page / description
      Previous value: -"Results per page (1-100). Default 25. Semantic search caps at 50 — when search_mode=\"semantic\", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream). The cap applies to searches only; an `id` lookup returns its one record regardless of both."New value: +"Results per page (1-100). Default 25. Semantic search caps at 50 — when search_mode=\"semantic\", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream). The cap applies to searches only; an `id` lookup returns its one record regardless of both. A budget-cut page returns fewer (`omitted`)."
    • changedInput schema / properties / select / description
      Previous value: -"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Only top-level fields project, so a nested value is requested by its parent object: bibliometrics (`h_index`, `i10_index`, `2yr_mean_citedness`) live under `summary_stats` on authors, sources, institutions, publishers, and funders, and naming a leaf returns that object. Invalid field names produce an error identifying the rejected field. Example: [\"doi\", \"authorships\", \"primary_topic\"]."New value: +"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Only top-level fields project, so a nested value is requested by its parent object: bibliometrics (`h_index`, `i10_index`, `2yr_mean_citedness`) live under `summary_stats` on authors, sources, institutions, publishers, and funders, and naming a leaf returns that object. Invalid field names produce an error identifying the rejected field. Large projections may be cut to fit the response budget (`omitted`, `windows`). Not applied with `slice`. Example: [\"doi\", \"authorships\", \"primary_topic\"]."
    • addedInput schema / properties / slice
      Added value: +{
      +  "description": "Page one array of the record at `id`: returns `id`, `display_name`, and `field` from `offset` onward, as many elements as fit the response budget (at least one), with the window and its `next` call in `windows`. Requires `id`; replaces `select`. An offset at or past the end returns an empty window. Walks any partial array, including a possibly capped 100-authorship list.",
      +  "properties": {
      +    "field": {
      +      "description": "Top-level array field to page, such as \"authorships\" or \"referenced_works\". Leave it empty to skip slicing.",
      +      "type": "string"
      +    },
      +    "offset": {
      +      "default": 0,
      +      "description": "0-based index of the first element to return.",
      +      "maximum": 9007199254740991,
      +      "minimum": 0,
      +      "type": "integer"
      +    }
      +  },
      +  "required": [
      +    "field"
      +  ],
      +  "type": "object"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `slice_without_id`: A search passed `slice` without `id` — slice pages one array of one record. `slice_field_not_array`: The `slice` field is `*`, or is not an array on the record at `id` — absent, null, a scalar, or an object. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "semantic_per_page_cap",
      -  "semantic_without_query",
      -  "semantic_with_cursor",
      -  "page_without_semantic",
      -  "sample_with_cursor",
      -  "sample_with_page",
      -  "sample_with_semantic",
      -  "sample_with_sort",
      -  "seed_without_sample",
      -  "entity_not_found",
      -  "rate_limited",
      -  "upstream_budget_exhausted",
      -  "upstream_timeout",
      -  "upstream_unavailable",
      -  "upstream_unauthorized",
      -  "upstream_forbidden",
      -  "comma_in_filter_value",
      -  "upstream_invalid_params",
      -  "upstream_invalid_id_value",
      -  "upstream_sort_requires_search",
      -  "query_too_long",
      -  "upstream_invalid_params_other",
      -  "upstream_validation_failed"
      -]New value: +[
      +  "semantic_per_page_cap",
      +  "semantic_without_query",
      +  "semantic_with_cursor",
      +  "page_without_semantic",
      +  "sample_with_cursor",
      +  "sample_with_page",
      +  "sample_with_semantic",
      +  "sample_with_sort",
      +  "seed_without_sample",
      +  "slice_without_id",
      +  "slice_field_not_array",
      +  "entity_not_found",
      +  "rate_limited",
      +  "upstream_budget_exhausted",
      +  "upstream_timeout",
      +  "upstream_unavailable",
      +  "upstream_unauthorized",
      +  "upstream_forbidden",
      +  "comma_in_filter_value",
      +  "upstream_invalid_params",
      +  "upstream_invalid_id_value",
      +  "upstream_sort_requires_search",
      +  "query_too_long",
      +  "upstream_invalid_params_other",
      +  "upstream_validation_failed"
      +]
    • changedOutput schema / properties / meta / properties / next_cursor / description
      Previous value: -"Cursor for next page. null if no more results."New value: +"Cursor for next page. null if no more results. Continues after the full upstream page, `omitted` records included."
    • changedOutput schema / properties / meta / properties / per_page / description
      Previous value: -"Page size OpenAlex echoed for this request — the requested per_page, not the number of records returned. A short or exhausted page carries fewer records than this."New value: +"Page size OpenAlex echoed for this request — the requested per_page, not the number of records returned. A short or exhausted page carries fewer records than this, as does a page the response budget cut (`omitted`)."
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance notice. Set when a first call returns no results (echoes the criteria and suggests how to broaden), when a paginated call ran past its last page (says the traversal is finished instead of advising a broader query), when an `id` lookup was passed search criteria it does not apply (names them), and on every semantic search to disclose that `meta.count` is a candidate total rather than a match total. Absent otherwise."New value: +"Guidance notice. Set when a first call returns no results (echoes the criteria and suggests how to broaden), when a paginated call ran past its last page (says the traversal is finished instead of advising a broader query), when an `id` lookup was passed search criteria it does not apply (names them), on every semantic search to disclose that `meta.count` is a candidate total rather than a match total, when the response budget cut records, windowed an array, or ran over, when a list record carries exactly 100 authorships (possibly capped), when a `slice` offset is past the end of its array, and when `slice` was passed with `select`, which it replaces. Absent otherwise."
    • addedOutput schema / properties / omitted
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Present when the response budget cut whole records from the page end; pagination continues after the full upstream page.",
      +  "properties": {
      +    "ids": {
      +      "description": "Bare IDs of the records left out, in page order (a keyword keeps its URL).",
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "next": {
      +      "additionalProperties": false,
      +      "description": "One call returning those records as a set, in OpenAlex's order, not page order. Budgeted too, so it may carry its own `omitted`.",
      +      "properties": {
      +        "arguments": {
      +          "additionalProperties": {},
      +          "description": "Arguments to pass as-is.",
      +          "properties": {},
      +          "type": "object"
      +        },
      +        "tool": {
      +          "const": "openalex_search_entities",
      +          "description": "Tool to call.",
      +          "type": "string"
      +        }
      +      },
      +      "required": [
      +        "tool",
      +        "arguments"
      +      ],
      +      "type": "object"
      +    }
      +  },
      +  "required": [
      +    "ids",
      +    "next"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / properties / over_budget
      Added value: +{
      +  "description": "True when even the least this call can return — the first record's non-array fields (its arrays windowed to empty), or one `slice` element — exceeds the budget.",
      +  "type": "boolean"
      +}
    • changedOutput schema / properties / results / description
      Previous value: -"OpenAlex entity objects. Text values are plain text — HTML entities decoded, HTML/JATS/MathML markup removed — and an abstract arrives reconstructed as `abstract`. Additional fields depend on entity_type and select."New value: +"OpenAlex entity objects. Text values are plain text — HTML entities decoded, HTML/JATS/MathML markup removed — except identifier and URL fields such as `id`, `doi`, `ids`, and `*_url`, returned exactly as OpenAlex stores them; an abstract arrives reconstructed as `abstract`. Additional fields depend on entity_type and select. Records cut by the response budget are in `omitted`; partial or possibly capped arrays are in `windows`."
    • changedOutput schema / properties / results / items / properties / id / description
      Previous value: -"OpenAlex ID (e.g., \"W2741809807\", \"A1234567890\")."New value: +"OpenAlex ID URL (e.g., \"https://openalex.org/W2741809807\"). `windows` and `omitted` name records by the bare ID (\"W2741809807\"); a keyword keeps its URL."
    • addedOutput schema / properties / windows
      Added value: +{
      +  "description": "Arrays returned in part — windowed to fit the budget, paged by `slice`, or 100 possibly capped authorships — each with its continuation. A `slice` call always carries its window, even for a whole array; otherwise absent when every array is whole.",
      +  "items": {
      +    "additionalProperties": false,
      +    "description": "An array returned in part.",
      +    "properties": {
      +      "field": {
      +        "description": "Array field.",
      +        "type": "string"
      +      },
      +      "id": {
      +        "description": "Bare ID of the record.",
      +        "type": "string"
      +      },
      +      "next": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "properties": {
      +              "arguments": {
      +                "additionalProperties": {},
      +                "description": "Arguments to pass as-is.",
      +                "properties": {},
      +                "type": "object"
      +              },
      +              "tool": {
      +                "const": "openalex_search_entities",
      +                "description": "Tool to call.",
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "tool",
      +              "arguments"
      +            ],
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "`id` + `slice` call for the next elements; null at the array end."
      +      },
      +      "offset": {
      +        "description": "Index of the first element shown.",
      +        "type": "number"
      +      },
      +      "possibly_capped": {
      +        "description": "True for exactly 100 authorships on a list record (OpenAlex's cap): the list may be longer.",
      +        "type": "boolean"
      +      },
      +      "shown": {
      +        "description": "Elements shown.",
      +        "type": "number"
      +      },
      +      "total": {
      +        "description": "Array length as returned to this call.",
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "field",
      +      "offset",
      +      "shown",
      +      "total",
      +      "possibly_capped",
      +      "next"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
  5. Changed9 schema fields changed
    • changedInput schema / properties / page / description
      Previous value: -"Page number (1-based) for semantic search, the one mode that paginates with `page` instead of `cursor`. Semantic search ranks at most 50 candidates, so the last reachable page is ceil(50 / per_page) — e.g. page 17 with per_page=3. Passing it under any other search_mode is rejected."New value: +"Page number (1-based) for semantic search, the one mode that paginates with `page` instead of `cursor`. Semantic search ranks a query-dependent candidate set whose size `meta.count` reports, so the last reachable page is ceil(meta.count / per_page) — e.g. page 14 for a count of 70 with per_page=5. Passing it under any other search_mode is rejected."
    • changedInput schema / properties / sample / description
      Previous value: -"Return a random sample of this many entities matching the filters (1-100). Single page only — neither `cursor` nor `page` pagination applies to sampling, and a search that passes either alongside it is rejected. Overrides `per_page`. Useful for unbiased exploration: spot-checking filter correctness, stratified review prompts, or generating exploration sets without bias toward most-cited."New value: +"Return a random sample of this many entities matching the filters (1-100). Single page only — neither `cursor` nor `page` pagination applies to sampling, and a search that passes either alongside it is rejected. Keyword and exact modes only: OpenAlex does not sample a semantic search, so `sample` with search_mode \"semantic\" is rejected. Cannot be combined with `sort` — a sample has no order, and a search passing both is rejected. Overrides `per_page`. Useful for unbiased exploration: spot-checking filter correctness, stratified review prompts, or generating exploration sets without bias toward most-cited."
    • changedInput schema / properties / search_mode / description
      Previous value: -"Search strategy. \"keyword\": stemmed full-text (default). \"exact\": no stemming, matches individual words (use quoted phrases for multi-word exact match). \"semantic\": AI embedding similarity, ranking at most 50 candidates at ~1 req/sec and paginated with `page` rather than `cursor`."New value: +"Search strategy. \"keyword\": stemmed full-text (default). \"exact\": no stemming, matches individual words (use quoted phrases for multi-word exact match). \"semantic\": AI embedding similarity over a query-dependent candidate set whose size `meta.count` reports, at ~1 req/sec, up to 50 per page, and paginated with `page` rather than `cursor`."
    • changedInput schema / properties / sort / description
      Previous value: -"Sort field. Prefix with \"-\" for descending. Comma-separate for a multi-key sort, applied left to right, with the \"-\" prefix set per key (\"-publication_year,cited_by_count\" sorts by year descending, then citations ascending). Common: \"cited_by_count\", \"-publication_date\", \"-relevance_score\" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use \"-relevance_score\" or omit sort to keep the most relevant results first. \"-relevance_score\" requires an active search via \"query\" or a \"filter:search\" filter — passing it without one will fail."New value: +"Sort field. Prefix with \"-\" for descending. Comma-separate for a multi-key sort, applied left to right, with the \"-\" prefix set per key (\"-publication_year,cited_by_count\" sorts by year descending, then citations ascending). Common: \"cited_by_count\", \"-publication_date\", \"-relevance_score\" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use \"-relevance_score\" or omit sort to keep the most relevant results first. \"-relevance_score\" requires an active search via \"query\" or a \"filter:search\" filter — passing it without one will fail. Not combinable with `sample` — a search passing both is rejected."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `sample_with_semantic`: A search (no `id`) provided `sample` with search_mode \"semantic\", which OpenAlex does not sample — it returns the same ranked candidates under every seed. `sample_with_sort`: A search (no `id`) provided both `sample` and `sort`, which OpenAlex refuses together. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "semantic_per_page_cap",
      -  "semantic_without_query",
      -  "semantic_with_cursor",
      -  "page_without_semantic",
      -  "sample_with_cursor",
      -  "sample_with_page",
      -  "seed_without_sample",
      -  "entity_not_found",
      -  "rate_limited",
      -  "upstream_budget_exhausted",
      -  "upstream_timeout",
      -  "upstream_unavailable",
      -  "upstream_unauthorized",
      -  "upstream_forbidden",
      -  "comma_in_filter_value",
      -  "upstream_invalid_params",
      -  "upstream_invalid_id_value",
      -  "upstream_sort_requires_search",
      -  "query_too_long",
      -  "upstream_invalid_params_other",
      -  "upstream_validation_failed"
      -]New value: +[
      +  "semantic_per_page_cap",
      +  "semantic_without_query",
      +  "semantic_with_cursor",
      +  "page_without_semantic",
      +  "sample_with_cursor",
      +  "sample_with_page",
      +  "sample_with_semantic",
      +  "sample_with_sort",
      +  "seed_without_sample",
      +  "entity_not_found",
      +  "rate_limited",
      +  "upstream_budget_exhausted",
      +  "upstream_timeout",
      +  "upstream_unavailable",
      +  "upstream_unauthorized",
      +  "upstream_forbidden",
      +  "comma_in_filter_value",
      +  "upstream_invalid_params",
      +  "upstream_invalid_id_value",
      +  "upstream_sort_requires_search",
      +  "query_too_long",
      +  "upstream_invalid_params_other",
      +  "upstream_validation_failed"
      +]
    • changedOutput schema / properties / meta / properties / count / description
      Previous value: -"Total results matching the query/filters. Under search_mode \"semantic\" it is instead the capped candidate count — at most 50 — not an exhaustive match total."New value: +"Total results matching the query/filters. Under search_mode \"semantic\" it is instead the size of the ranked candidate set — the most results `page` can reach — not an exhaustive match total."
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance notice. Set when a first call returns no results (echoes the criteria and suggests how to broaden), when a paginated call ran past its last page (says the traversal is finished instead of advising a broader query), when an `id` lookup was passed search criteria it does not apply (names them), and on every semantic search to disclose that `meta.count` is a capped candidate total. Absent otherwise."New value: +"Guidance notice. Set when a first call returns no results (echoes the criteria and suggests how to broaden), when a paginated call ran past its last page (says the traversal is finished instead of advising a broader query), when an `id` lookup was passed search criteria it does not apply (names them), and on every semantic search to disclose that `meta.count` is a candidate total rather than a match total. Absent otherwise."
    • changedOutput schema / properties / results / description
      Previous value: -"OpenAlex entity objects passed through unchanged. Additional fields depend on entity_type and select."New value: +"OpenAlex entity objects. Text values are plain text — HTML entities decoded, HTML/JATS/MathML markup removed — and an abstract arrives reconstructed as `abstract`. Additional fields depend on entity_type and select."
  6. Changed15 schema fields changed
    • changedInput schema / properties / cursor / description
      Previous value: -"Pagination cursor from a previous response. Pass to get the next page."New value: +"Pagination cursor from a previous response. Pass to get the next page. Omit it on the first call — an empty string is rejected, since a supplied-but-blank cursor is a caller mistake rather than a request for page 1. Keyword and exact modes only — semantic search walks its candidates with `page`, and a `cursor` sent with it is rejected."
    • addedInput schema / properties / cursor / minLength
      Added value: +1
    • changedInput schema / properties / filters / description
      Previous value: -"Filter criteria as field:value pairs. AND across fields (multiple keys). OR within field: pipe-separate (\"us|gb\"). NOT: prefix \"!\" (\"!us\"). Range: \"2020-2024\". Comparison: \">100\", \"<50\". AND within same field: \"+\"-separate. Use OpenAlex IDs (not names) for entity filters — resolve names first. Common keys: `openalex` (filter by entity ID, e.g. {\"openalex\": \"W123|W456\"}), `cites` (works citing a given work), `publication_year` (range \"2020-2024\"), `authorships.author.id`, `type`, `is_oa`."New value: +"Filter criteria as field:value pairs. AND across fields (multiple keys). OR within field: pipe-separate (\"us|gb\"). NOT: prefix \"!\" (\"!us\"). Range: \"2020-2024\". Comparison: \">100\", \"<50\". AND within same field: \"+\"-separate. Two keys that resolve to the same upstream field (an alias and its canonical name, e.g. `year` and `publication_year`) are both applied and AND'd, so they narrow rather than override each other. Use OpenAlex IDs (not names) for entity filters — resolve names first. Common keys: `openalex` (filter by entity ID, e.g. {\"openalex\": \"W123|W456\"}), `cites` (works citing a given work), `publication_year` (range \"2020-2024\"), `authorships.author.id`, `type`, `is_oa`."
    • changedInput schema / properties / id / description
      Previous value: -"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."New value: +"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). Keywords are identified by slug rather than a native ID — pass either the slug (\"groundwater\") or the URL a search returns (\"https://openalex.org/keywords/groundwater\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."
    • addedInput schema / properties / id / minLength
      Added value: +1
    • addedInput schema / properties / page
      Added value: +{
      +  "description": "Page number (1-based) for semantic search, the one mode that paginates with `page` instead of `cursor`. Semantic search ranks at most 50 candidates, so the last reachable page is ceil(50 / per_page) — e.g. page 17 with per_page=3. Passing it under any other search_mode is rejected.",
      +  "maximum": 9007199254740991,
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • changedInput schema / properties / query / description
      Previous value: -"Text search query. Supports boolean operators (AND, OR, NOT), quoted phrases (\"exact match\"), wildcards (machin*), fuzzy matching (machin~1), and proximity (\"climate change\"~5). Omit for filter-only queries."New value: +"Text search query. Supports boolean operators (AND, OR, NOT), quoted phrases (\"exact match\"), wildcards (machin*), fuzzy matching (machin~1), and proximity (\"climate change\"~5). Omit for filter-only queries — an empty string is rejected, since a blank search is a mistake rather than a request for the whole catalog."
    • addedInput schema / properties / query / minLength
      Added value: +1
    • changedInput schema / properties / sample / description
      Previous value: -"Return a random sample of this many entities matching the filters (1-100). Single page only — pagination via `cursor` is not supported with sampling. Overrides `per_page`. Useful for unbiased exploration: spot-checking filter correctness, stratified review prompts, or generating exploration sets without bias toward most-cited."New value: +"Return a random sample of this many entities matching the filters (1-100). Single page only — neither `cursor` nor `page` pagination applies to sampling, and a search that passes either alongside it is rejected. Overrides `per_page`. Useful for unbiased exploration: spot-checking filter correctness, stratified review prompts, or generating exploration sets without bias toward most-cited."
    • changedInput schema / properties / search_mode / description
      Previous value: -"Search strategy. \"keyword\": stemmed full-text (default). \"exact\": no stemming, matches individual words (use quoted phrases for multi-word exact match). \"semantic\": AI embedding similarity (max 50 results, 1 req/sec)."New value: +"Search strategy. \"keyword\": stemmed full-text (default). \"exact\": no stemming, matches individual words (use quoted phrases for multi-word exact match). \"semantic\": AI embedding similarity, ranking at most 50 candidates at ~1 req/sec and paginated with `page` rather than `cursor`."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `semantic_without_query`: A search set search_mode to \"semantic\" without supplying the `query` it embeds. `semantic_with_cursor`: A search set search_mode to \"semantic\" and supplied `cursor`, which OpenAlex rejects on a semantic query. `page_without_semantic`: A search supplied `page` under a search_mode other than \"semantic\". `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `sample_with_page`: A search (no `id`) provided both `sample` and `page`. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `query_too_long`: OpenAlex rejected `query` as longer than the search length it accepts (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "semantic_per_page_cap",
      -  "sample_with_cursor",
      -  "seed_without_sample",
      -  "entity_not_found",
      -  "rate_limited",
      -  "upstream_budget_exhausted",
      -  "upstream_timeout",
      -  "upstream_unavailable",
      -  "upstream_unauthorized",
      -  "upstream_forbidden",
      -  "comma_in_filter_value",
      -  "upstream_invalid_params",
      -  "upstream_invalid_id_value",
      -  "upstream_sort_requires_search",
      -  "upstream_invalid_params_other",
      -  "upstream_validation_failed"
      -]New value: +[
      +  "semantic_per_page_cap",
      +  "semantic_without_query",
      +  "semantic_with_cursor",
      +  "page_without_semantic",
      +  "sample_with_cursor",
      +  "sample_with_page",
      +  "seed_without_sample",
      +  "entity_not_found",
      +  "rate_limited",
      +  "upstream_budget_exhausted",
      +  "upstream_timeout",
      +  "upstream_unavailable",
      +  "upstream_unauthorized",
      +  "upstream_forbidden",
      +  "comma_in_filter_value",
      +  "upstream_invalid_params",
      +  "upstream_invalid_id_value",
      +  "upstream_sort_requires_search",
      +  "query_too_long",
      +  "upstream_invalid_params_other",
      +  "upstream_validation_failed"
      +]
    • changedOutput schema / properties / meta / properties / count / description
      Previous value: -"Total results matching the query/filters."New value: +"Total results matching the query/filters. Under search_mode \"semantic\" it is instead the capped candidate count — at most 50 — not an exhaustive match total."
    • changedOutput schema / properties / meta / properties / per_page / description
      Previous value: -"Results on this page."New value: +"Page size OpenAlex echoed for this request — the requested per_page, not the number of records returned. A short or exhausted page carries fewer records than this."
    • changedOutput schema / properties / notice / description
      Previous value: -"Guidance notice. Set when results are empty (echoes the criteria and suggests how to broaden) or when an `id` lookup was passed search criteria it does not apply (names them). Absent otherwise."New value: +"Guidance notice. Set when a first call returns no results (echoes the criteria and suggests how to broaden), when a paginated call ran past its last page (says the traversal is finished instead of advising a broader query), when an `id` lookup was passed search criteria it does not apply (names them), and on every semantic search to disclose that `meta.count` is a capped candidate total. Absent otherwise."
  7. Changed1 schema field changed
    • changedInput schema / properties / id / description
      Previous value: -"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), PMCID (\"PMC1234567\"), ISSN (\"1234-5678\"). When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."New value: +"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), ISSN (\"1234-5678\"). A PMCID is recognized too, bare (\"PMC1234567\") or as a PubMed Central URL, but OpenAlex indexes no PMCIDs, so it resolves nothing — pass the work's PMID or DOI instead. When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."
  8. Changed2 schema fields changed
    • changedInput schema / properties / id / description
      Previous value: -"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\"), PMCID (\"PMC1234567\"), ISSN (\"1234-5678\"). When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."New value: +"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\" or \"https://pubmed.ncbi.nlm.nih.gov/12345678\"), PMCID (\"PMC1234567\"), ISSN (\"1234-5678\"). When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."
    • changedInput schema / properties / select / description
      Previous value: -"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Invalid field names produce an error identifying the rejected field. Example: [\"doi\", \"authorships\", \"primary_topic\"]."New value: +"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Only top-level fields project, so a nested value is requested by its parent object: bibliometrics (`h_index`, `i10_index`, `2yr_mean_citedness`) live under `summary_stats` on authors, sources, institutions, publishers, and funders, and naming a leaf returns that object. Invalid field names produce an error identifying the rejected field. Example: [\"doi\", \"authorships\", \"primary_topic\"]."
  9. Changed4 schema fields changed
    • removedOutput schema / properties / meta / properties / next_cursor / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / meta / properties / next_cursor / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / results / items / properties / display_name / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / results / items / properties / display_name / type
      Added value: +[
      +  "string",
      +  "null"
      +]
  10. Changed6 schema fields changed
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedInput schema / additionalProperties
      Added value: +false
    • changedOutput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedOutput schema / anyOf
      Added value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "meta",
      +      "results",
      +      "echo",
      +      "totalCount"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / error
      Added value: +{
      +  "additionalProperties": {},
      +  "description": "Present when the call failed. Absent on success.",
      +  "properties": {
      +    "code": {
      +      "description": "JSON-RPC error code for this failure.",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "data": {
      +      "additionalProperties": {},
      +      "properties": {
      +        "reason": {
      +          "description": "Machine-readable failure mode. Declared by this tool: `semantic_per_page_cap`: A search (no `id`) set per_page above the semantic-search cap of 50. `sample_with_cursor`: A search (no `id`) provided both `sample` and `cursor`. `seed_without_sample`: A search (no `id`) provided `seed` without `sample`. `entity_not_found`: Lookup by id matched no OpenAlex entity. `rate_limited`: OpenAlex throttled the request for exceeding its per-second ceiling (HTTP 429). `upstream_budget_exhausted`: The OpenAlex daily usage budget is spent (HTTP 429). `upstream_timeout`: OpenAlex did not respond within the request deadline. `upstream_unavailable`: OpenAlex was unreachable or unusable — HTTP 503, a connection failure, or a body that was empty, HTML, or unparseable JSON. `upstream_unauthorized`: OpenAlex rejected the API key (HTTP 401). `upstream_forbidden`: OpenAlex denied access to the requested resource (HTTP 403). `comma_in_filter_value`: A filter value contains a comma, which collides with the OpenAlex filter separator. `upstream_invalid_params`: OpenAlex rejected an invalid filter, select, or sort field name (HTTP 400). `upstream_invalid_id_value`: An entity-ID filter received a value that is not an OpenAlex ID — usually a name (HTTP 400). `upstream_sort_requires_search`: sort=-relevance_score was used without an active search (HTTP 400). `upstream_invalid_params_other`: OpenAlex rejected the request (HTTP 400) for a reason other than an invalid field name. `upstream_validation_failed`: OpenAlex rejected the request as semantically invalid (HTTP 422). Other values are possible when a failure originates below the handler.",
      +          "examples": [
      +            "semantic_per_page_cap",
      +            "sample_with_cursor",
      +            "seed_without_sample",
      +            "entity_not_found",
      +            "rate_limited",
      +            "upstream_budget_exhausted",
      +            "upstream_timeout",
      +            "upstream_unavailable",
      +            "upstream_unauthorized",
      +            "upstream_forbidden",
      +            "comma_in_filter_value",
      +            "upstream_invalid_params",
      +            "upstream_invalid_id_value",
      +            "upstream_sort_requires_search",
      +            "upstream_invalid_params_other",
      +            "upstream_validation_failed"
      +          ],
      +          "type": "string"
      +        },
      +        "recovery": {
      +          "additionalProperties": {},
      +          "description": "Actionable next step for the caller.",
      +          "properties": {
      +            "hint": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "hint"
      +          ],
      +          "type": "object"
      +        },
      +        "retryable": {
      +          "description": "Whether retrying may succeed.",
      +          "type": "boolean"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "message": {
      +      "description": "Human-readable description of what went wrong.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "code",
      +    "message"
      +  ],
      +  "type": "object"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "meta",
      -  "results",
      -  "echo",
      -  "totalCount"
      -]
  11. Changed6 schema fields changed
    • changedInput schema / properties / id / description
      Previous value: -"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\"), PMCID (\"PMC1234567\"), ISSN (\"1234-5678\"). When provided, other search/filter/sort params are ignored — but `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). Use openalex_resolve_name to find the ID if unknown."New value: +"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\"), PMCID (\"PMC1234567\"), ISSN (\"1234-5678\"). When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown."
    • changedInput schema / properties / per_page / description
      Previous value: -"Results per page (1-100). Default 25. Semantic search caps at 50 — when search_mode=\"semantic\", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream)."New value: +"Results per page (1-100). Default 25. Semantic search caps at 50 — when search_mode=\"semantic\", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream). The cap applies to searches only; an `id` lookup returns its one record regardless of both."
    • changedInput schema / properties / seed / description
      Previous value: -"Deterministic seed for `sample`. Same seed + same filters = same results — pass when reproducibility matters. Has no effect (and is rejected) without `sample`."New value: +"Deterministic seed for `sample`. Same seed + same filters = same results — pass when reproducibility matters. Has no effect without `sample`, and a search that passes it alone is rejected."
    • addedOutput schema / properties / budget / properties / prepaidRemainingUsd
      Added value: +{
      +  "description": "USD left in the prepaid balance — a separate pool OpenAlex draws on only after the daily allowance runs out, and which the daily reset does not refill. Add it to `remainingUsd` for the full spendable amount. Absent when the account holds no prepaid balance.",
      +  "type": "number"
      +}
    • changedOutput schema / properties / echo / description
      Previous value: -"Compact echo of the input criteria (entity_type, query, filters, sort, search_mode) — surfaces what was actually searched when results are empty."New value: +"Compact echo of the criteria that actually ran (entity_type, query, filters, sort, search_mode) — surfaces what was searched when results are empty. An `id` lookup echoes entity_type and id alone, because the search criteria are not applied on that path."
    • changedOutput schema / properties / notice / description
      Previous value: -"Recovery guidance when results are empty — echoes the criteria and suggests how to broaden. Absent on successful result pages."New value: +"Guidance notice. Set when results are empty (echoes the criteria and suggests how to broaden) or when an `id` lookup was passed search criteria it does not apply (names them). Absent otherwise."
  12. Changed1 schema field changed
    • addedOutput schema / properties / budget
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "What this call cost against the OpenAlex daily budget and what is left of it. Price a full traversal before committing to it: `totalCount` ÷ `per_page` × `costUsd` against `remainingUsd`. Absent when OpenAlex omitted the accounting headers.",
      +  "properties": {
      +    "costUsd": {
      +      "description": "USD this call spent. 0 for an `id` lookup — OpenAlex does not bill single-entity fetches, so batching known IDs beats paging a filtered list.",
      +      "type": "number"
      +    },
      +    "remainingUsd": {
      +      "description": "USD left in today's OpenAlex budget after this call.",
      +      "type": "number"
      +    },
      +    "resetsInSeconds": {
      +      "description": "Seconds until the daily budget refills (midnight UTC).",
      +      "type": "number"
      +    }
      +  },
      +  "required": [
      +    "costUsd",
      +    "remainingUsd",
      +    "resetsInSeconds"
      +  ],
      +  "type": "object"
      +}
  13. Changed5 schema fields changed
    • changedInput schema / properties / sort / description
      Previous value: -"Sort field. Prefix with \"-\" for descending. Common: \"cited_by_count\", \"-publication_date\", \"-relevance_score\" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use \"-relevance_score\" or omit sort to keep the most relevant results first. \"-relevance_score\" requires an active search via \"query\" or a \"filter:search\" filter — passing it without one will fail."New value: +"Sort field. Prefix with \"-\" for descending. Comma-separate for a multi-key sort, applied left to right, with the \"-\" prefix set per key (\"-publication_year,cited_by_count\" sorts by year descending, then citations ascending). Common: \"cited_by_count\", \"-publication_date\", \"-relevance_score\" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use \"-relevance_score\" or omit sort to keep the most relevant results first. \"-relevance_score\" requires an active search via \"query\" or a \"filter:search\" filter — passing it without one will fail."
    • changedOutput schema / properties / results / items / description
      Previous value: -"A single OpenAlex entity record. Core `id` and `display_name` are guaranteed; additional fields vary by entity_type and `select`."New value: +"A single OpenAlex entity record. `id` is always present and `display_name` is always returned (though it may be null); additional fields vary by entity_type and `select`."
    • addedOutput schema / properties / results / items / properties / display_name / anyOf
      Added value: +[
      +  {
      +    "type": "string"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedOutput schema / properties / results / items / properties / display_name / description
      Previous value: -"Entity name or work title."New value: +"Entity name or work title. null when OpenAlex holds no title for the record (paratext works and other untitled entries) — use `id` to identify it."
    • removedOutput schema / properties / results / items / properties / display_name / type
      Removed value: -"string"
  14. Changed3 schema fields changed
    • changedInput schema / properties / id / description
      Previous value: -"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\"), PMCID (\"PMC1234567\"), ISSN (\"1234-5678\"). When provided, other search/filter/sort params are ignored. Use openalex_resolve_name to find the ID if unknown."New value: +"Retrieve a single entity by ID. Supports: OpenAlex ID (\"W2741809807\"), DOI (\"10.1038/nature12373\"), ORCID (\"0000-0002-1825-0097\"), ROR (\"https://ror.org/00hx57361\"), PMID (\"12345678\"), PMCID (\"PMC1234567\"), ISSN (\"1234-5678\"). When provided, other search/filter/sort params are ignored — but `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `[\"*\"]` for the complete record). Use openalex_resolve_name to find the ID if unknown."
    • changedInput schema / properties / select / description
      Previous value: -"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. Searches apply a curated default per entity type; pass to override. Single-entity lookups (by `id`) return the full record unless set. Invalid field names produce an error listing the valid ones. Example: [\"doi\", \"authorships\", \"primary_topic\"]."New value: +"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-entity (`id`) lookups; pass field names to override it, or `[\"*\"]` to retrieve the complete record (every field). Invalid field names produce an error identifying the rejected field. Example: [\"doi\", \"authorships\", \"primary_topic\"]."
    • changedOutput schema / properties / echo / description
      Previous value: -"Compact echo of the input criteria (entity_type, query, filters, sort, search_mode) — useful when results are empty so callers see what was actually searched."New value: +"Compact echo of the input criteria (entity_type, query, filters, sort, search_mode) — surfaces what was actually searched when results are empty."
  15. Changed6 schema fields changed
    • addedOutput schema / properties / echo
      Added value: +{
      +  "description": "Compact echo of the input criteria (entity_type, query, filters, sort, search_mode) — useful when results are empty so callers see what was actually searched.",
      +  "type": "string"
      +}
    • removedOutput schema / properties / meta / properties / echo
      Removed value: -{
      -  "description": "Compact echo of the input criteria (entity_type, query, filters, sort, search_mode) — useful when results are empty so callers see what was actually searched.",
      -  "type": "string"
      -}
    • changedOutput schema / properties / meta / required
      Previous value: -[
      -  "count",
      -  "per_page",
      -  "next_cursor",
      -  "echo"
      -]New value: +[
      +  "count",
      +  "per_page",
      +  "next_cursor"
      +]
    • addedOutput schema / properties / notice
      Added value: +{
      +  "description": "Recovery guidance when results are empty — echoes the criteria and suggests how to broaden. Absent on successful result pages.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / totalCount
      Added value: +{
      +  "description": "Total results matching the query/filters across all pages.",
      +  "type": "number"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "meta",
      -  "results"
      -]New value: +[
      +  "meta",
      +  "results",
      +  "echo",
      +  "totalCount"
      +]
  16. Changed3 schema fields changed
    • changedInput schema / properties / filters / description
      Previous value: -"Filter criteria as field:value pairs. AND across fields (multiple keys). OR within field: pipe-separate (\"us|gb\"). NOT: prefix \"!\" (\"!us\"). Range: \"2020-2024\". Comparison: \">100\", \"<50\". AND within same field: \"+\"-separate. Use OpenAlex IDs (not names) for entity filters — resolve names first."New value: +"Filter criteria as field:value pairs. AND across fields (multiple keys). OR within field: pipe-separate (\"us|gb\"). NOT: prefix \"!\" (\"!us\"). Range: \"2020-2024\". Comparison: \">100\", \"<50\". AND within same field: \"+\"-separate. Use OpenAlex IDs (not names) for entity filters — resolve names first. Common keys: `openalex` (filter by entity ID, e.g. {\"openalex\": \"W123|W456\"}), `cites` (works citing a given work), `publication_year` (range \"2020-2024\"), `authorships.author.id`, `type`, `is_oa`."
    • addedInput schema / properties / sample
      Added value: +{
      +  "description": "Return a random sample of this many entities matching the filters (1-100). Single page only — pagination via `cursor` is not supported with sampling. Overrides `per_page`. Useful for unbiased exploration: spot-checking filter correctness, stratified review prompts, or generating exploration sets without bias toward most-cited.",
      +  "maximum": 100,
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • addedInput schema / properties / seed
      Added value: +{
      +  "description": "Deterministic seed for `sample`. Same seed + same filters = same results — pass when reproducibility matters. Has no effect (and is rejected) without `sample`.",
      +  "type": "string"
      +}
  17. Changed1 schema field changed
    • changedInput schema / properties / sort / description
      Previous value: -"Sort field. Prefix with \"-\" for descending. Common: \"cited_by_count\", \"-publication_date\", \"relevance_score\" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use \"relevance_score\" or omit sort to keep the most relevant results first."New value: +"Sort field. Prefix with \"-\" for descending. Common: \"cited_by_count\", \"-publication_date\", \"-relevance_score\" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use \"-relevance_score\" or omit sort to keep the most relevant results first. \"-relevance_score\" requires an active search via \"query\" or a \"filter:search\" filter — passing it without one will fail."
  18. Changed1 schema field changed
    • changedInput schema / properties / select / description
      Previous value: -"OpenAlex top-level field names to return. Searches apply a curated default per entity type; pass to override. Single-entity lookups (by `id`) return the full record unless set. Invalid field names produce an error listing the valid ones. Example: [\"id\", \"doi\", \"display_name\", \"authorships\", \"primary_topic\"]."New value: +"OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. Searches apply a curated default per entity type; pass to override. Single-entity lookups (by `id`) return the full record unless set. Invalid field names produce an error listing the valid ones. Example: [\"doi\", \"authorships\", \"primary_topic\"]."
  19. Changed4 schema fields changed
    • changedInput schema / properties / per_page / description
      Previous value: -"Results per page (1-100). Default 25."New value: +"Results per page (1-100). Default 25. Semantic search caps at 50 — when search_mode=\"semantic\", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream)."
    • changedInput schema / properties / sort / description
      Previous value: -"Sort field. Prefix with \"-\" for descending. Common: \"cited_by_count\", \"-publication_date\", \"relevance_score\" (default when query present)."New value: +"Sort field. Prefix with \"-\" for descending. Common: \"cited_by_count\", \"-publication_date\", \"relevance_score\" (default when query present). Note: when combined with a keyword query, an explicit sort overrides relevance ranking entirely — top results may be highly cited but only tangentially on-topic. Use \"relevance_score\" or omit sort to keep the most relevant results first."
    • addedOutput schema / properties / meta / properties / echo
      Added value: +{
      +  "description": "Compact echo of the input criteria (entity_type, query, filters, sort, search_mode) — useful when results are empty so callers see what was actually searched.",
      +  "type": "string"
      +}
    • changedOutput schema / properties / meta / required
      Previous value: -[
      -  "count",
      -  "per_page",
      -  "next_cursor"
      -]New value: +[
      +  "count",
      +  "per_page",
      +  "next_cursor",
      +  "echo"
      +]
  20. Changed1 schema field changed
    • addedOutput schema / properties / results / items / description
      Added value: +"A single OpenAlex entity record. Core `id` and `display_name` are guaranteed; additional fields vary by entity_type and `select`."
  21. Changed2 schema fields changed
    • changedInput schema / properties / select / description
      Previous value: -"Fields to return. Top-level fields only. Searches apply sensible defaults per entity type; pass this to override. Single-entity lookups (by `id`) return the full record unless `select` is specified. Example: [\"id\", \"doi\", \"display_name\", \"publication_year\", \"cited_by_count\", \"primary_topic\"]."New value: +"OpenAlex top-level field names to return. Searches apply a curated default per entity type; pass to override. Single-entity lookups (by `id`) return the full record unless set. Invalid field names produce an error listing the valid ones. Example: [\"id\", \"doi\", \"display_name\", \"authorships\", \"primary_topic\"]."
    • changedOutput schema / properties / results / description
      Previous value: -"Entity objects. Additional fields depend on entity_type and select."New value: +"OpenAlex entity objects passed through unchanged. Additional fields depend on entity_type and select."
  22. First observed

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, openWorld), and the description adds substantial beyond-schema behavior: id overrides query/search_mode/filters/sort/sample/seed and the response `notice` reports them, semantic search is rate-limited to ~1 req/sec and caps at 50 per page, sample caps at 10,000 and needs `seed` to page, and cursor vs page vs slice are three distinct pagination regimes. That is real operational context, not restatement.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core routing (ID vs discovery, resolve names first) is correctly front-loaded, but the body is a dense 180-word run that repeats pagination and projection rules already documented in the parameter schemas. Sentences are individually informative, yet the aggregate duplicates structured data and is harder to scan than it needs to be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 13-parameter tool with nested objects, two enums, and an output schema, the description covers everything the agent must know to choose a mode and avoid rejection: ID-lookup precedence, the three pagination schemes, sample constraints, and the sort/relevance caveat. Return values are left to the output schema, as appropriate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the per-parameter baseline is 3; the description earns above baseline by disclosing cross-parameter interactions the schema does not state as a whole — which parameters are ignored when `id` is set, that `select` still applies to ID lookups, and that `slice` replaces `select`. It stops short of 5 because much of the filter-quoting and select-projection detail merely paraphrases what the schema properties already spell out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific set of verbs (search, filter, sort, retrieve by ID) and an explicit resource scope: all OpenAlex entity types, enumerated. It also routes the reader away from the wrong path by naming openalex_resolve_name, so the tool is distinguishable from its siblings without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States the branch condition directly: pass `id` for a single entity, otherwise use `query` and/or `filters` for discovery, and 'To filter, drop `id` and search.' It also names the prerequisite step ('Use openalex_resolve_name to resolve names to IDs before filtering'), which is exactly the alternative-selection guidance an agent needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.