Crossref's REST API is a keyless way to search and retrieve bibliographic metadata — titles, authors, funders, references, licensing — for the 160-million-plus scholarly works it has assigned DOIs to, spanning journal articles, books, chapters, and datasets. Anonymous requests land in a low-throughput default pool; adding a mailto contact parameter moves you into a faster, still-free "polite" pool, and a paid Metadata Plus subscription removes the concurrency cap for production integrations. It's run directly by the Crossref non-profit rather than a third party.
Crossref's REST API is a keyless way to search 160-million-plus scholarly works by bibliographic string and get back trimmed JSON metadata — title, DOI, type — via a select= parameter that shrinks the payload to only the fields asked for. A live GET this run against /works?query.bibliographic=atlas+of+api&rows=1&select=title,DOI,type returned a genuine 300 B single-result record; the byte-identical query with select= removed returned 1,915 B, so the trimming is real rather than cosmetic.
AuthenticationNone requiredCall it straight away — no key, no signup.
HTTPSSupportedTraffic is encrypted in transit.
CORSEnabledCallable directly from browser JavaScript.
PricingFreemiumA usable free tier exists, with paid plans for more volume.
FormatsJSONResponses can be requested as JSON.
GreatAPIs Score
Score91out of 100
Authentication25/25No authentication required
Pricing17/20Freemium tier available
Docs20/20Machine-readable spec file bundled
Formats9/15Single response format
Freshness20/20Verified within 6 months
Embed this badge
<a href="https://greatapis.com/api/crossref-metadata-search/"><img src="https://greatapis.com/badge/crossref-metadata-search.svg" alt="Scored 91 on greatapis.com"></a>
Auth quickstart
No API key, signup, or credit card required for the base search — a live GET this run returned real results with zero credentials. Adding a mailto= query parameter (any real contact address) moves the same request into Crossref's faster "polite pool": a live GET this run with mailto=test@example.com on the identical query returned x-rate-limit-limit: 3 / x-api-pool: polite-array, versus x-rate-limit-limit: 1 / x-api-pool: public-array on the anonymous request moments before — both still fully free, no key ever issued.
Stored keyNo key stored
Your key is stored only in this browser (localStorage) and sent directly to the API — never to greatapis.
Endpoints
Works3
Endpoints that expose works related data<a name='works'></a>
GET/works
Parameters
Name
In
Required
Type
rows
query
no
integer
order
query
no
string
facet
query
no
string
sample
query
no
integer
sort
query
no
string
offset
query
no
integer
mailto
query
no
string
select
query
no
string
query
query
no
string
filter
query
no
string
cursor
query
no
string
Responses
Status
Description
Schema
200
A list of works
WorksMessage
GET/works/{doi}
Parameters
Name
In
Required
Type
doi
path
yes
string
Responses
Status
Description
Schema
200
The work identified by {doi}.
WorkMessage
404
The work identified by {doi} does not exist.
—
GET/works/{doi}/agency
Parameters
Name
In
Required
Type
doi
path
yes
string
Responses
Status
Description
Schema
200
The agency associated with work identified by {doi}.
AgencyMessage
404
The work identified by {doi} does not exist.
—
Journals3
Endpoints that expose journal related data<a name='journals'></a>
GET/journals
Parameters
Name
In
Required
Type
cursor
query
no
string
query
query
no
string
rows
query
no
integer
mailto
query
no
string
offset
query
no
integer
Responses
Status
Description
Schema
200
A list of journals.
JournalsMessage
GET/journals/{issn}
Parameters
Name
In
Required
Type
issn
path
yes
array
Responses
Status
Description
Schema
200
The journal identified by {issn}.
JournalMessage
404
The journal identified by {issn} does not exist.
—
GET/journals/{issn}/works
Parameters
Name
In
Required
Type
rows
query
no
integer
order
query
no
string
facet
query
no
string
sample
query
no
integer
sort
query
no
string
offset
query
no
integer
mailto
query
no
string
select
query
no
string
query
query
no
string
filter
query
no
string
cursor
query
no
string
issn
path
yes
array
Responses
Status
Description
Schema
200
A list of works.
WorksMessage
Funders3
Endpoints that expose funder related data<a name='funders'></a>
GET/funders
Parameters
Name
In
Required
Type
filter
query
no
string
cursor
query
no
string
query
query
no
string
rows
query
no
integer
mailto
query
no
string
offset
query
no
integer
Responses
Status
Description
Schema
200
A list of funders.
FundersMessage
GET/funders/{id}
Parameters
Name
In
Required
Type
id
path
yes
string
Responses
Status
Description
Schema
200
The funder identified by {id}.
FunderMessage
404
The funder identified by {id} does not exist.
—
GET/funders/{id}/works
Parameters
Name
In
Required
Type
rows
query
no
integer
order
query
no
string
facet
query
no
string
sample
query
no
integer
sort
query
no
string
offset
query
no
integer
mailto
query
no
string
select
query
no
string
query
query
no
string
filter
query
no
string
cursor
query
no
string
id
path
yes
string
Responses
Status
Description
Schema
200
A list of works.
WorksMessage
Members3
Endpoints that expose member related data<a name='members'></a>
GET/members
Parameters
Name
In
Required
Type
cursor
query
no
string
filter
query
no
string
query
query
no
string
rows
query
no
integer
mailto
query
no
string
offset
query
no
integer
Responses
Status
Description
Schema
200
A list of members.
MembersMessage
GET/members/{id}
Parameters
Name
In
Required
Type
id
path
yes
integer
Responses
Status
Description
Schema
200
The prefix data identified by {id}.
MemberMessage
404
The prefix data identified by {id} does not exist.
—
GET/members/{id}/works
Parameters
Name
In
Required
Type
rows
query
no
integer
order
query
no
string
facet
query
no
string
sample
query
no
integer
sort
query
no
string
offset
query
no
integer
mailto
query
no
string
select
query
no
string
query
query
no
string
filter
query
no
string
cursor
query
no
string
id
path
yes
integer
Responses
Status
Description
Schema
200
A list of works.
WorksMessage
Prefixes2
Endpoints that expose prefix related data<a name='prefixes'></a>
GET/prefixes/{prefix}
Parameters
Name
In
Required
Type
prefix
path
yes
string
Responses
Status
Description
Schema
200
The prefix data identified by {prefix}.
PrefixMessage
404
The prefix data identified by {prefix} does not exist.
—
GET/prefixes/{prefix}/works
Parameters
Name
In
Required
Type
rows
query
no
integer
order
query
no
string
facet
query
no
string
sample
query
no
integer
sort
query
no
string
offset
query
no
integer
mailto
query
no
string
select
query
no
string
query
query
no
string
filter
query
no
string
cursor
query
no
string
prefix
path
yes
string
Responses
Status
Description
Schema
200
A list of works.
WorksMessage
Types3
Endpoints that expose type related <a name='types'></a>
GET/types
Parameters
Name
In
Required
Type
rows
query
no
integer
mailto
query
no
string
offset
query
no
integer
Responses
Status
Description
Schema
200
A collection of types.
TypesMessage
GET/types/{id}
Parameters
Name
In
Required
Type
id
path
yes
string
Responses
Status
Description
Schema
200
The type identified by {id}.
TypeMessage
404
The type identified by {id} does not exist.
—
GET/types/{id}/works
Parameters
Name
In
Required
Type
rows
query
no
integer
order
query
no
string
facet
query
no
string
sample
query
no
integer
sort
query
no
string
offset
query
no
integer
mailto
query
no
string
select
query
no
string
query
query
no
string
filter
query
no
string
cursor
query
no
string
id
path
yes
string
Responses
Status
Description
Schema
200
A list of works.
WorksMessage
Licenses1
Endpoints that expose license related data<a name='licenses'></a>
GET/licenses
Parameters
Name
In
Required
Type
query
query
no
string
cursor
query
no
string
rows
query
no
integer
mailto
query
no
string
offset
query
no
integer
Responses
Status
Description
Schema
200
A list of licenses.
LicensesMessage
Try it
Developer reference
Base URLhttps://api.crossref.org
Rate limit
1 request/second anonymously (`x-rate-limit-limit: 1`, confirmed live this run); adding a `mailto=` contact parameter to enter the free "polite pool" raises this to 3 requests/second (`x-rate-limit-limit: 3`), confirmed live the same run. A separate `/works/{doi}` single-work lookup draws from its own pool at 5 requests/second.
Key endpoints
GET/works
GET/works/{doi}
Gotchas & limits
Anonymous and polite-pool requests carry genuinely different rate limits in their own headers — confirmed live this run on the identical query: the anonymous call returned x-rate-limit-limit: 1 / x-rate-limit-interval: 1s / x-api-pool: public-array, while adding mailto=test@example.com returned x-rate-limit-limit: 3 / x-api-pool: polite-array — three times the throughput for one added query parameter, no signup required.
A single-DOI lookup (/works/{doi}) draws from a third, separate pool — a live GET this run against /works/10.1371/journal.pone.0000308 returned x-rate-limit-limit: 5 / x-api-pool: public-single, a different limit and pool name than either search variant above.
select= genuinely shrinks the payload, not just which keys are visible — confirmed live this run with a like-for-like pair on one identical query: /works?query.bibliographic=atlas+of+api&rows=1&select=title,DOI,type returned 300 B, while the same request with select= simply removed returned 1,915 B. About a 6x saving on a single-result query, and it compounds as rows= grows.
select= is rejected outright on the single-work route — a live GET this run against /works/10.1371/journal.pone.0000308?select=title,DOI,type returned a genuine HTTP 400 with a 164-byte {"status":"failed","message-type":"validation-failure","message":[{"type":"parameter-not-allowed","value":"select","message":"This route does not support select"}]}. That same record fetched the only way that route allows — no select= — ran 11,960 B live this run, so a per-DOI lookup is both fat and untrimmable. To get one work trimmed, query it through the search route instead: a live GET this run against /works?filter=doi:10.1371/journal.pone.0000308&rows=1&select=title,DOI,type returned the same work at 336 B.
A DOI's own slash (e.g. 10.1371/journal.pone.0000308) is passed as a literal, unencoded path segment after /works/ — confirmed live this run — Crossref's own route parses everything after /works/ as the DOI, so it doesn't need URL-encoding.