Crossref's REST API, served from one commodity server
A compatible implementation of the API's read paths (single works, filters, facets, sorts, and cursors) over the works in Crossref's March 2026 public data file. One process on one rented machine. Every figure on this page was measured on it.
Measured throughput and latency
Requests come from lists drawn at random from the index itself: real DOIs, members, ISSNs, ORCID iDs, funders, and dates. The load generator runs on the same machine, so it uses some of the CPU being measured. The server's own CPU time per request is recorded too, which gives the rate the machine sustains when every thread serves requests.
| Request | Example | Requests/s | p50 | p99 | CPU per request | Response | zstd | Full CPU | At 1 Gbit, zstd |
|---|
Requests/s, p50, and p99: measured at 64 connections, uncompressed. Response and zstd: average bytes on the wire without and with zstd compression. Full CPU: the rate with all 12 hardware threads serving, from the server's CPU time per request. At 1 Gbit, zstd: the most compressed responses of that size the machine's network port carries in a second.
Requests a month one server can answer
By kind of request: the lower of the rate measured with zstd responses and what the 1 Gbit port carries at that size. The dashed line is Crossref's whole API today.
Throughput as connections rise
Mixed traffic, requests a second.
p99 latency as connections rise
Mixed traffic, milliseconds.
From outside, through Cloudflare
| Test | Machines | Connections | Requests/s | p50 | p99 | Errors |
|---|
Mixed traffic
The mixed run sends single lookups, filtered and sorted lists, facet counts, and cursor pages in the proportions below.
| Share | Request |
|---|---|
| 60% | Single work by DOI |
| 15% | Filtered list, 20 rows |
| 10% | Sorted list, 20 rows |
| 10% | Cursor page, 100 or 1,000 rows |
| 5% | Facet counts |
Crossref describes its public traffic as mostly single lookups with heavy use of filters. A day of its access logs would replace these shares with measured ones.
Compression, memory, and harvesting
- Results appear here after the measurement suite runs.
What a fleet for Crossref's traffic would rent for
Crossref reports serving 2 to 3 billion API requests a month, and about $2 million a year in AWS fees, most of it from the REST API. Servers like this one can carry that load several times over.
Server rent only. Not included: ingest (the XML-to-JSON pipeline and enrichment), text search, which this server doesn't answer yet, bandwidth beyond the servers' included traffic, and the people who run it.
The same requests and responses as api.crossref.org
The routes, parameters, and response envelope are Crossref's. Each filter's meaning was read from the source of Crossref's own API (the cayenne service): same names OR their values, different names AND, dates widen to the period named, and identifiers are normalised the way Crossref normalises them. Work records are served as the public data file has them.
| Route | Status | Notes |
|---|---|---|
| /works/{doi} | Served | The work as the data file has it; select supported. |
| /works/{doi}/agency | Served | |
| /works | Served | filter, facet, sort, order, rows, offset, cursor, sample, select. Limits as Crossref's: 1,000 rows, offset to 10,000, sample to 100. |
| /members/{id}/works, /prefixes/{prefix}/works, /funders/{id}/works, /journals/{issn}/works, /types/{id}/works | Served | /works with that filter added. |
| query, query.bibliographic, query.author, … | Not yet | Text search. Returns 501 with a message. |
| /members, /journals, /funders, /prefixes, /types, /licenses | Not yet | Metadata about members, journals, and funders rather than works. |
Filters
Not yet:
Facets
Not yet:
affiliationSorts
Not yet: citation. score and relevance are accepted and constant, as they are in Crossref's API without a text query.
Differences to know
total-resultsand facet counts are exact.- Without a sort or cursor, items come in creation order. Crossref leaves that order unspecified.
- Cursors follow Crossref's default order, created ascending. Works created in the same second are ordered by the builder, not by DOI.
- The index is a snapshot of the March 2026 public data file. Live counts include works registered or changed since.
Checked against the live API
A comparison with api.crossref.org runs as part of the measurement suite.
Send it requests
Any request that works against api.crossref.org can be sent here by changing the host.
Round trips include the network between you and the server. Time in the server comes from each response's Server-Timing header. This public instance has a rate limit; to load-test properly, run your own (below).
From a bare server to a measured API
Everything here is one program and a few scripts. The runbook in the repository (ops/deploy.md) has every command.
- Rent a server with 64 GB of memory and 1 to 2 TB of NVMe storage. Any recent 6 to 8 core machine will do.
- Provision it:
just provisioninstalls build tools, a firewall that admits only ssh, and the service. - Download Crossref's public data file, 208 GB by torrent:
just fetch-dump. - Build the index:
just build-index full, a few hours on six cores. - Serve it:
just use-index full. The server maps the index into memory and starts in seconds. - Measure it with the load generator and request lists from your own index:
crossref-api urls --index /data/index/current --mix default --count 100000 --out urls.tsv crossref-api bench --urls urls.tsv --concurrency 64 --duration 60