API reference

A small JSON API. Reading needs nothing; writing needs a key, and an account whose edits have been reviewed. The numberdb package is the supported way to use all of this — it handles the response format, the retries and the crash-safety — and this page is what it is built on.

Keys, limits and permission Reading /api/search /api/lookup /api/table /api/tag /api/table/<tid>/audit /api/docs Writing /api/tables /api/table/<tid> /api/table/<tid>/entries /api/table/<tid>/offer /api/table/<tid>/lease /api/table/<tid>/file/<name>

Keys, limits and permission

Anonymous use is limited to 60 requests per hour; a key raises it to 1000. Send it as a bearer token, and keep it out of a worksheet you might share:

export NUMBERDB_API_KEY=...
curl -H "Authorization: Bearer $NUMBERDB_API_KEY" https://numberdb.org/api/table?id=T7

Exceeding the limit returns 429 with a Retry-After header. Keys are not self-service yet: write to info@numberdb.org.

Writing needs more than a key. It opens once 5 of the account's edits have been reviewed and accepted — edits made on the site count. A person editing one table exercises judgement about it; a program exercises none and writes faster than anyone can read, so the gate is a track record that a script cannot manufacture for itself.

Writing also enforces the size limits rather than warning about them, and refuses a document whose type or parameters do not make sense. A person over a soft limit is told and their edit is saved; a program is refused until the reason is in the document, because a warning shown to nobody is not a limit.

Reading

GET/api/lookup

Look up a number whose type you already know, given its components, or several at once.

kindZZ, QQ, RIF, RBF, CIF, Qp, polynomial
returnsthe matching entries, keyed by position when several were asked about

At most 100 numbers per call. Every position asked about is present in the answer, so a number that matched nothing maps to an empty list rather than being absent.

GET/api/table

One table, whole.

idits T-number, e.g. T7
urlor its slug
returnsthe document: its prose, its parameters and its entries

A draft answers as though it did not exist. Saying “you may not see this” would confirm that a table of that name is there, which is the one thing a private draft should not tell a stranger.

GET/api/tag

One tag, and what carries it.

urlthe tag, e.g. elliptic+curves
returnsits name, how many tables and numbers carry it, and which tables

GET/api/table/<tid>/audit

The checks a table is read against, run on one table.

tidin the path, e.g. T7
returnsfindings, a list of sentences, and clean, true when that list is empty

These are the same checks as manage.py audit_table, through the same implementation, for a machine that computes a table but holds no copy of the database. A finding is a thing to look at, not a verdict: the rules are rules, and one of them can be right in form and wrong here. Outward links are not followed — that is the command’s --links, and it needs a database. A draft answers to a key that may see it, and otherwise as though it did not exist.

GET/api/docs

This page.

Writing

POST/api/tables

Add a table. The body is the whole document, as YAML or JSON.

X-Draftyes creates the table unpublished and in progress, ready creates it and offers it for review at once: invisible, in no listing, answering no search, and publishable later by somebody who has looked at it. Creating a published table with a program is board-only, because a table is a permanent number and a parameter order that citations resolve on; a draft is neither yet. An account may hold a few drafts at once, and the answer reports drafts_held and drafts_remaining
X-Numberdb-Clientwhich client this is, as name/version. The numberdb package sends numberdb-python/<version> and the revision records that it arrived through the package; a request without it is recorded as having come through the API directly. A different fact from X-Produced-By, which says what made the values: the same generator run from a laptop and pasted into the form on the site agree about what, and differ about how
X-Produced-Bywhat made this, recorded on the revision and shown in its history. Defaults to api. The numberdb package sends the generator and the versions of the package and of Sage, and — when NUMBERDB_ASSISTED_BY is set — whatever ran the publish: Zeta (numberdb=0.1.2, sage=10.9), assisted by Claude Code, table-build@8390298. Name each layer that can change without you noticing, at a version: there is rarely one thing that made a revision, and numberdb=0.1.2, sage=10.9 is the same habit applied to the generator. Do not name a model you have not checked — a runner usually cannot know which one answered until afterwards — and do not cite anything a reader here cannot follow: table-build@8390298 is a commit in a public repository, while a run number is a line in somebody’s private log. At most 100 characters, so about 50 are left after the generator; the author is still the person whose key it was
X-Edit-Messagewhat changed, for the history
X-Run-Id, X-Pipeline,
X-Stage, X-Engine,
X-Model, X-Effort,
X-Session, X-Campaign,
X-Batch, X-Machine
what ran, as a record rather than a sentence. Writes naming one X-Run-Id land on one run, so a stage that sends fifty entries is one producer and not fifty. X-Pipeline is name@major.minor+digesttable-build@2.7+9f3ac1d2 — where the digest covers the files that pipeline declares in agents/pipelines/; a label carrying one is taken as written by a runner that knows what it ran, and one without as a claim typed by hand. An interactive session (Claude Code, Codex CLI, a person’s own script) sends X-Pipeline: interactive with its engine, model and session: the author stays the account whose key it is, and the tool is disclosed beside it. All optional; a write that sends none of them is recorded as a person at a keyboard, which is what the absence honestly means
returns201 with the allocated tid and the first revision
400no title, no entries, or a document that is not a table
413over a size limit

A new table needs a title and at least one entry. One with none is a draft, and a published draft holds a permanent T-number, appears in the listings and answers nothing. The identifier is allocated here and ignored if you send one.

POST/api/table/<tid>

Replace a table's document. PUT does the same.

X-Base-Revisionthe revision you edited from. A concurrent change is then merged where it can be and refused where it cannot, instead of silently overwritten
X-Produced-Byas above
409a stale edit whose changes overlap, or a change to the table's parameters
413over a size limit

The whole document, so anything the body omits is removed. A program that computes values wants /entries instead: assembling a document from a title, the parameters and the numbers would delete the definition, the comments and the references, and the result would look perfectly ordinary.

A table's parameters cannot be changed once it is published. Every entry is identified by its parameter values, so changing them reassigns every identity at once and existing citations resolve to different numbers.

POST/api/table/<tid>/entries

The entries alone, leaving the prose untouched. This is what a generator wants.

X-Entries-Modereplace (default) or upsert. Upsert merges what you send into what is stored, by identity, so sending one entry leaves the other thousand alone
X-Run-Idnames one run. Submissions carrying the same run grow a single revision instead of adding one each
X-Rigourhow well these digits are known, recorded on the table: exact, proven, assumed-bound, heuristic (agreement-checked) or heuristic. Anything else is refused — the value of the field is that the words mean the same on every table. The only table metadata a program may set here: everything else under Data properties is somebody's prose or, in the case of repeats, somebody's judgement about two tables, and this is a fact about the computation. Both are written by sending a whole document
X-Produced-Byas above
returnsthe revision, and for an upsert how many entries were added and updated
429another write to this table held the lock for longer than 15s. Nothing was written; send it again

Every revision holds the complete document, so a revision per entry would be a thousand copies of the whole table. A run amends its own revision instead — which works only while that revision is still the latest, so a run that will take a while should hold a lease.

POST/api/table/<tid>/offer

Put a filled draft into the review queue. The last step of making one with a program: create the draft, fill it, then ask for a person.

returnsthe table number and ready_for_review
404no such table, or a draft this account did not make — a draft it may not see does not answer differently for existing
409the table is already published, or holds no numbers yet: there is nothing to review until it does

Offering is not publishing. The draft stays invisible and unpublished; what changes is that somebody is now being asked to read it. Publishing remains a person's act, on the site.

X-Draft: ready on /api/tables does the same thing at creation, which is before a generator has put anything in the table. Use that when the document arrives complete with its numbers, and this when it does not.

POSTDELETE/api/table/<tid>/lease

Claim a table for the length of a run, refresh the claim, or drop it.

X-Run-Idthe run the claim belongs to, so a resumed run keeps it
X-Lease-Notewhat is being done, shown to whoever is turned away
returnswhether it is held, and when it expires
409somebody else holds it, with their name and its expiry

It lasts 20 minutes and is refreshed by each submission, so a run whose entries are quicker than that needs no heartbeat. It expires because a generator that dies must not lock a table for good, and it never refuses a person editing on the site: a generator's claim is against other generators.

POST/api/costs

Report what a machine’s runs cost, and what they were. The body is the run ledger as TSV; the rows are aggregated by table, model and stage and replace what is stored, so sending the same ledger twice is not paying twice. Each row also becomes a run, which is how the work that produced no table — a screening run, a triage run, a build that declined — is recorded at all.

X-Attributionbase64 of a two-column TSV, run stamp and table, for runs whose own row never said what they were about
X-Machinewhich machine the ledger came from. Two machines ran campaigns with separate ledgers and the run stamp does not say which
?dry=1report what would change and write nothing
returnshow many rows, tables and runs it touched, and what it could not attribute
403the key may write numbers but may not report costs: that is for the accounts that run campaigns, and the board

POST/api/table/<tid>/file/<name>

Attach a file — usually the code that produced the numbers. The body is the file.

X-Run-Idcarrying the run of the entries puts the file on the same revision as them
X-Rigourhow well these digits are known, recorded on the table: exact, proven, assumed-bound, heuristic (agreement-checked) or heuristic. Anything else is refused — the value of the field is that the words mean the same on every table. The only table metadata a program may set here: everything else under Data properties is somebody's prose or, in the case of repeats, somebody's judgement about two tables, and this is a fact about the computation. Both are written by sending a whole document
400a name with a directory in it, a hidden name, or no body
413over 2048 KB, or over 8192 KB for the table's files together

A table's files are flat: no directories, so there is one place to look and no question about where a path leads. They are recorded, never executed.

What the answers look like

$ curl -X POST https://numberdb.org/api/table/14/entries \
    -H "Authorization: Bearer $NUMBERDB_API_KEY" \
    -H "X-Entries-Mode: upsert" -H "X-Run-Id: zeta-2026-08" \
    --data-binary @entries.yaml

{"tid": "T14", "url": "Values_of_the_Riemann_zeta_function_at_rational_numbers",
 "revision": "9f2c...", "unchanged": false, "amended": true,
 "entries": 1000, "added": 1, "updated": 0, "reviewed": true}

Every refusal carries an error saying what is wrong and, where there is more to say, a detail saying what to do about it.