API
Scripts can read the database and ask for analysis over plain HTTP. Every endpoint takes positions as FEN and answers in JSON, and reading and requesting work without an account.
Endpoints at a glance
| Endpoint | What it does |
|---|---|
GET /atomicdb/api/query?fen=... | Read what the database knows about one position. |
POST /atomicdb/api/request | Ask for analysis of a position, exactly like the explorer button. |
POST /atomicdb/api/my-queue/ | List your own waiting and running requests (credentials needed). |
POST /atomicdb/api/queue-bump/<id>/POST /atomicdb/api/queue-cancel/<id>/ | Move one of your requests to the front, or withdraw it. |
GET /atomicdb/api/roots | The roots page (start position, first moves, named openings) as data. |
Example
Rate limit
- The endpoints a script can call (
query,request,live-request,frontier,my-queue,queue-bump,queue-cancelandmap) take 5 requests a second from one address, with bursts of up to 50. - Past that the reply is
429with a JSON body and aRetry-Afterheader. Nothing is lost: wait and send it again. - The worker endpoints are not limited.
- The pace is about calls a second, not about how much you may ask for (see the request endpoint below).
Query a position
GET /atomicdb/api/query?fen=... returns the position row and its move list. Scores are centipawns from the side to move (the chessdb.cn convention). A position the tree does not have returns 404; add it with the request endpoint below, the FEN box or the explorer.
| Field | Meaning |
|---|---|
status, closure | The verdict and the gate it closed through (see Position rows). |
score | The best known value, the one the page headlines, from the side to move. |
point | The position's own engine evaluation (its point eval). |
backed_plies | How deep the backing chain runs. |
best_move | The move score comes from, the one the board arrow shows. |
| trust fields | How much the result can be trusted (see Method). |
moves | One entry per move; see below. |
analysis | The engine's own output on this position, or null where no engine has looked yet. |
Each entry of moves carries, among others:
searched_nodes: the node budget of the deepest search that move's position has had (the depth column of the explorer), ornullwhere no search has reached it.visits: the number of passes it has received.floor:truefor a number the position does not use (see below).borrowed:truefor a number nothing has searched and none of this position's own lines gives: it was written by another position's search (the explorer's borrowed chip).
Details: floor and searched_nodes
floor is true for a move nobody has searched, outside the position's current lines, whose number would beat the last of those lines. The position does not use that number, and best_move never points at such a move. A client that picks from moves itself should skip those rows the same way (the rule is on Backed values).
searched_nodes counts the same searches only_if_missing does, so a request for a rung at or below searched_nodes with that flag comes back already-analyzed.
Details: the analysis block
linesis the current pass. Each line carries its move,cpormate(from the side to move, like the rest of the reply), the PV, thedepth,seldepthandnodesthe engine reported, and the untouchedrawUCI info string it came from.budget_nodesis the node budget of the pass that producedlines.previousis the pass before it, kept whenever it was wider than the one that replaced it.passesis one compact summary per pass, newest first: budget, width, evaluation, move, depth and whether that pass is the one on display. It shows how the evaluation moved as the search went deeper.- Passes have only been recorded since 15 August 2026, so a position analysed before then has a series that starts partway through:
passes_completeis false there, and no attempt is made to invent the missing entries.
Request analysis
POST /atomicdb/api/request takes exactly the path the explorer button takes: the same budget ladder, the same deduplication, the same queue cap and the same joining rule. If the position is already queued, you are recorded as another requester instead of queueing a duplicate. A FEN the tree does not have is seeded (added) rather than refused.
| Field | Meaning |
|---|---|
fen | Required. The position. |
username, password | Optional. Put the request on your account, which is what buys the notification when it lands, the affinity with your own workers and your place in the fair share. Without them the request is anonymous, which works, and is served like a click without a session. |
budget | Optional. Picks a rung of the ladder. Honoured for the accounts the explorer offers the depth slider to. budget=10000000000 is the slider's last stop: up to 78 searches of 128M below the position. |
only_if_missing=1 | Optional. Only make sure a search exists; see below. |
Session cookies are deliberately ignored here, so a page open in your browser cannot spend your queue behind your back.
There is no hourly limit on this door, same as the explorer button. What governs both is the per-person cap on queued requests and the per-account sharing of the service order (see The queue). Every call still leaves a receipt.
Asking again. Asked again after the first search has landed, a request buys the next rung of the ladder, exactly as a second click does. Once the 2B search has run, it buys the searches below the position. Those replies say expanded, or saturated when there was nothing left to queue, with searches_queued, subtree_plies (how deep the deepest one is) and a message saying what was done.
Only if missing. A script that only wants a position to have a search adds only_if_missing=1. The budget becomes a target (the rung you picked, or the first one):
- If a search that ran already covers it, the reply is
already-analyzedwithcompleted_budget, and nothing is queued. - Otherwise the request is queued as usual, but never above the target.
- The check and the purchase happen together, so two scripts asking for the same position at the same moment cannot push it to the next rung.
- It asks about the position itself, so it cannot be sent with
budget=10000000000.
The reply
| Field | Meaning |
|---|---|
status | What happened; see the next table. |
key, fen | The position key and the canonical FEN. |
task, budget_nodes | The live request on that position and its budget, when there is one. |
task_state | waiting while it is in the queue, searching once an engine has it. |
ahead | How many requests are served first. |
backed | true when you joined a request somebody else had already made. |
seeded | The FEN was not in the tree and was added by this call. |
status | Meaning |
|---|---|
queued | A new request was queued. |
already-queued | The position was already waiting or being searched. |
already-solved | The position is closed; nothing to ask. |
already-analyzed | Only with only_if_missing: a search already covers the target. |
expanded | The work went to the positions below, with a message saying what was queued. |
saturated | Nothing left to buy here or below. |
already-requested | Your address already has a live request on this position; wait for it to land. |
queue-full-account | You already have the maximum number of requests waiting (HTTP 503); let some land or clear your queue. |
refused | Not accepted; reason says why (see the HTTP codes below). |
Every answer that is not accepted carries a reason. Refusals by HTTP code:
400: an unreadable FEN or a bad budget.403: wrong credentials.405: anything but aPOST.429: too many calls.503: your queue is full (queue-full-account), or the database is busy (refusedwith aRetry-After).
Your queue
POST /atomicdb/api/my-queue/withusernameandpassword(your OpenBench credentials, same as a worker) returns your PENDING and LEASED requests: task ids, position keys, budgets, state, and thebump/cancelpaths each row accepts. A logged-in browser can simplyGETit.- The ids plug into
POST /atomicdb/api/queue-bump/<id>/andPOST /atomicdb/api/queue-cancel/<id>/, with the same credentials (form or JSON).undo=1on cancel restores the row. - The profile-page buttons are separate, session-guarded routes. Withdraw (on the position and on every waiting row of your contributor page) and Clear my queue (at the foot of that list) are signed-in controls rather than credential calls: they post to
/atomicdb/queue/cancel/<task>/and/atomicdb/queue/clear/from a session. - A request a worker has already started cannot be withdrawn, because the search is already running. Withdrawing a request somebody else also asked for only takes your name off: the request stays in the queue under them.
Roots
GET /atomicdb/api/roots returns the roots page as data.
positions: the start position, its first moves and the replies to 1.Nf3.openings: every named opening.- Each row has its
key,label,line(the moves from the start position, in UCI) and itsvaluenow, thenbest_valueandworst_valuewith theirbest_atandworst_attimes (UTC) and their statuses. - The values are centipawns from White's side, as on the page, with a proven win as +10000 or -10000 and a proven draw as 0. (The
scoreofqueryis from the side to move instead.) sinceis when tracking began.