AtomicDB

Sign in Register

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

EndpointWhat it does
GET /atomicdb/api/query?fen=...Read what the database knows about one position.
POST /atomicdb/api/requestAsk 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/rootsThe roots page (start position, first moves, named openings) as data.

Example

curl "https://belzedar.duckdns.org/atomicdb/api/query?fen=rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR%20w%20KQkq%20-%200%201"
curl --data-urlencode "fen=rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" https://belzedar.duckdns.org/atomicdb/api/request

Rate limit

  • The endpoints a script can call (query, request, live-request, frontier, my-queue, queue-bump, queue-cancel and map) take 5 requests a second from one address, with bursts of up to 50.
  • Past that the reply is 429 with a JSON body and a Retry-After header. 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.

FieldMeaning
status, closureThe verdict and the gate it closed through (see Position rows).
scoreThe best known value, the one the page headlines, from the side to move.
pointThe position's own engine evaluation (its point eval).
backed_pliesHow deep the backing chain runs.
best_moveThe move score comes from, the one the board arrow shows.
trust fieldsHow much the result can be trusted (see Method).
movesOne entry per move; see below.
analysisThe 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), or null where no search has reached it.
  • visits: the number of passes it has received.
  • floor: true for a number the position does not use (see below).
  • borrowed: true for 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
  • lines is the current pass. Each line carries its move, cp or mate (from the side to move, like the rest of the reply), the PV, the depth, seldepth and nodes the engine reported, and the untouched raw UCI info string it came from.
  • budget_nodes is the node budget of the pass that produced lines.
  • previous is the pass before it, kept whenever it was wider than the one that replaced it.
  • passes is 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_complete is 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.

FieldMeaning
fenRequired. The position.
username, passwordOptional. 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.
budgetOptional. 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=1Optional. 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-analyzed with completed_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

FieldMeaning
statusWhat happened; see the next table.
key, fenThe position key and the canonical FEN.
task, budget_nodesThe live request on that position and its budget, when there is one.
task_statewaiting while it is in the queue, searching once an engine has it.
aheadHow many requests are served first.
backedtrue when you joined a request somebody else had already made.
seededThe FEN was not in the tree and was added by this call.
statusMeaning
queuedA new request was queued.
already-queuedThe position was already waiting or being searched.
already-solvedThe position is closed; nothing to ask.
already-analyzedOnly with only_if_missing: a search already covers the target.
expandedThe work went to the positions below, with a message saying what was queued.
saturatedNothing left to buy here or below.
already-requestedYour address already has a live request on this position; wait for it to land.
queue-full-accountYou already have the maximum number of requests waiting (HTTP 503); let some land or clear your queue.
refusedNot 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 a POST.
  • 429: too many calls.
  • 503: your queue is full (queue-full-account), or the database is busy (refused with a Retry-After).

Your queue

  • POST /atomicdb/api/my-queue/ with username and password (your OpenBench credentials, same as a worker) returns your PENDING and LEASED requests: task ids, position keys, budgets, state, and the bump / cancel paths each row accepts. A logged-in browser can simply GET it.
  • The ids plug into POST /atomicdb/api/queue-bump/<id>/ and POST /atomicdb/api/queue-cancel/<id>/, with the same credentials (form or JSON). undo=1 on 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 its value now, then best_value and worst_value with their best_at and worst_at times (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 score of query is from the side to move instead.)
  • since is when tracking began.

← Contributing The position page →