API
The archive's data as JSON, under /api/v1/. It is read from the same database, by the same queries, as the pages, so it never says something the pages do not.
The API is read-only and needs no key or account. It sends Access-Control-Allow-Origin: *, so pages on any site can read it. Version 1 keeps its fields: a field may be added, but none is renamed or removed without a new version.
A list comes a page at a time. Its answer carries next_cursor; pass that back as cursor, with the same other parameters, for the next page. A cursor is opaque and belongs to the query it came from. Every answer says in RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset how much of the limit is left.
Endpoints
| Endpoint | Returns |
|---|---|
| GET /api/v1/ | the list of endpoints |
| GET /api/v1/games | every game with runs |
| GET /api/v1/games/{game} | one game and its categories |
| GET /api/v1/categories | every category that holds runs |
| GET /api/v1/runs | runs, newest first; with a game and a goal, grouped by category and ranked within it |
| GET /api/v1/runs/{run} | one run whole |
| GET /api/v1/runs/{run}/log | the run's log, record by record, in order |
| GET /api/v1/search | the runs whose log matched, best first |
| GET /api/v1/stats | what the archive holds in numbers |
GET /api/v1/
The list of endpoints.
No parameters.
Example: GET /api/v1/
| Field in the answer | Meaning |
|---|---|
version | 1 |
documentation | this page |
endpoints[] | path, returns and params of each endpoint |
GET /api/v1/games
Every game with runs.
No parameters.
Example: GET /api/v1/games
| Field in the answer | Meaning |
|---|---|
data[].slug, id, name | the game's address on this site, its id in the tooling, its name |
data[].runs | how many runs it holds |
data[].goals[] | goal and label of each goal played |
data[].platforms[], versions[], models[] | what its runs were played on and by |
data[].latest | when its newest run was played |
data[].url | its page on this site |
GET /api/v1/games/{game}
One game and its categories.
No parameters.
Example: GET /api/v1/games/slay-the-spire
| Field in the answer | Meaning |
|---|---|
data | the game, as in /api/v1/games |
data.categories[] | as in /api/v1/categories |
GET /api/v1/categories
Every category that holds runs.
| Parameter | Meaning |
|---|---|
game | a game's slug, such as slay-the-spire |
goal | a goal id, such as act3 |
Example: GET /api/v1/categories?game=slay-the-spire
| Field in the answer | Meaning |
|---|---|
data[].game, goal, goalLabel | the game's slug and the goal |
data[].build, observation, input, timing, human | the rest of the category |
data[].runs, reached | how many runs it holds, and how many reached the goal |
GET /api/v1/runs
Runs, newest first; with a game and a goal, grouped by category and ranked within it.
| Parameter | Meaning |
|---|---|
game | a game's slug |
goal | a goal id |
observation, input, timing, human | an axis value (specification §3) |
model | a full model id |
name, variant, model_version, snapshot | a part of the model id |
effort, context, provider | as the runtime reported them |
runtime, cli | a runtime plugin id; a runtime CLI version |
version | a game version |
limit | runs per page, at most 100; 50 when left out |
cursor | next_cursor from the previous page |
Example: GET /api/v1/runs?game=slay-the-spire&goal=act3&limit=10
| Field in the answer | Meaning |
|---|---|
data[] | the items of this page |
next_cursor | pass it as cursor for the next page; null on the last page |
total | how many runs match |
data[].id | the address the run is published under; may change between revisions |
data[].uid | the run's identity, written once when the run was configured |
data[].revision | which publication of the run this is |
data[].url | the run's page on this site |
data[].game.slug, game.id, game.name, game.version, game.platform | what was played; slug is the site's address for the game, id the tooling's |
data[].category.goal, goalLabel, build, observation, input, timing, human | the category (specification §3); runs are comparable only when all of these agree |
data[].agent.models[] | the models that answered: model, parts (name, variant, version, snapshot; null where the tooling did not split the id), effort, contextWindow, maxOutputTokens, provider |
data[].agent.requested | the model that was asked for, where it differs |
data[].agent.runtime | the runtime plugin: id, name, version, and cliVersions as the session log records them |
data[].agent.harness, agent.tokens | the harness name and version; tokens total, input and output |
data[].mods[] | each loaded mod: name and version |
data[].times.igt, rta, thinking | in-game time, real time and thinking time in seconds, to the goal where toGoal is true |
data[].times.toolCalls, playbacks, segments | counts over the same span |
data[].reachedGoal | whether the run reached its declared goal; a run that did not is not ranked |
data[].place | only with a game and a goal chosen: the run's place within its category, or null when it is not ranked |
data[].example, operator | whether the archive marks it as an example; the account that submitted it |
data[].dates.started, completed, ended, submitted, zone | UTC timestamps, and the zone the run was played in |
data[].versions.spec, schema, bundle | the specification, summary schema and bundle format the run was published with |
GET /api/v1/runs/{run}
One run whole.
No parameters.
Example: GET /api/v1/runs/sts-claude-code-01
| Field in the answer | Meaning |
|---|---|
data | the run as in /api/v1/runs, and: |
data.game.settings, data.game.mods[] | the settings it was played with; each mod with version, source and sha256 |
data.agent.plugins, data.agent.tools[] | every plugin by role; the tools the agent had, by name and description |
data.seed, data.fingerprint | the game's seed; the sha256 of the run's own log, which binds a recording to it |
data.goals[], data.ends[] | the goals declared and reached; the ends the game plugin offers |
data.sections[] | label, startRta, rta, igt, splitIgt, playbacks, postCompletion |
data.attempts[], data.chapters[] | each attempt; the chapter marks in the recording |
data.events[] | the harness events, each with sequence, at, time, event and data |
data.checks[] | the archive's check of the bundle, one row per point of specification §8 |
data.signature | signed, valid, the key's fingerprint and the account that recorded it |
data.files[] | path, bytes and sha256 of every file in the bundle |
data.revisions[] | every revision held, and which is current |
data.bundle | where the zip of this revision is served, or null |
data.log | where its log is served |
GET /api/v1/runs/{run}/log
The run's log, record by record, in order.
| Parameter | Meaning |
|---|---|
limit | records per page, at most 100; 100 when left out |
cursor | next_cursor from the previous page |
Example: GET /api/v1/runs/sts-claude-code-01/log?limit=100
| Field in the answer | Meaning |
|---|---|
run | the run's address |
data[] | the items of this page |
next_cursor | pass it as cursor for the next page; null on the last page |
data[].sequence, at | the record's place in the log; seconds from the start of the recording |
data[].kind, role | message, tool_call, tool_result or event; who spoke |
data[].tool, call | for a call and its result: the tool and the call they share |
data[].event, data | for an event: its name and what the harness attached |
data[].body | what was said, the code that ran, or what came back, as the sanitised log has it |
GET /api/v1/search
The runs whose log matched, best first.
| Parameter | Meaning |
|---|---|
q | the words: "exact phrase", -leave out, prefix*; and conditions on game-state values: current_hp<10, floor>=5, screen_type=MAP, or with the path, game_state.current_hp<10 |
game, goal, model | as for runs |
what | said, prompt, code, returned or event |
limit | runs per page, at most 20 |
cursor | next_cursor from the previous page |
Example: GET /api/v1/search?q=boss&game=slay-the-spire
| Field in the answer | Meaning |
|---|---|
query, total, runs | the query as read; how many matches in how many runs |
data[] | the items of this page |
next_cursor | pass it as cursor for the next page; null on the last page |
data[].run | the run, as in /api/v1/runs |
data[].matches[] | sequence, at, kind and section of every match in that run; the record itself is in the run's log |
GET /api/v1/stats
What the archive holds in numbers.
| Parameter | Meaning |
|---|---|
game | a game's slug; tool calls per section need one |
Example: GET /api/v1/stats?game=portal
| Field in the answer | Meaning |
|---|---|
data.totals | runs, games, reached, models, runtimes, rta, igt, thinking, toolCalls, tokens |
data.games[] | per game: runs, reached, rta, igt, thinking |
data.models[] | per model and effort: runs, rta, thinking, toolCalls |
data.runtimes[] | runs per runtime |
data.sections[] | for one game, per section: runs, rta, igt, toolCalls |
Errors and limits
An error is an answer with the status and an object error holding status and message, the message in words.
| Status | When |
|---|---|
400 | a parameter or cursor that cannot be read |
404 | no such game, run or endpoint |
301 | a run that is published under another address now; location names it |
405 | anything but GET |
429 | more than 300 requests in 5 minutes from one client; retry-after says when to try again |
The shape of things
A run has three identifiers, and they are not interchangeable: uid is the identity, written once when the run was configured and never changed; id is the address it was published under and may change; revision counts publications of that same run. A series may start above 1 and may have gaps.
Timings come from the bundle's own timeline.json and are never recalculated here: rta is real time, igt the time the game actually ran, thinking the difference between real time and the time playbacks took.
No video is served. Where a recording is published is archive data added by the submitter. The fingerprint is the sha256 of the run's own session.sanitized.jsonl, which is how a recording is tied to a bundle.
The bundles themselves
Under /bundles/<run>.r<revision>.zip every revision of a bundle is served as it was uploaded. That is the evidence: anyone can recalculate the hashes against manifest.json without trusting anything this site says, on their own machine or in the browser.
The site as witness of a run
- The tooling reports the start and the end of every segment of a run to the site, which records when it received each by its own clock. Which rules a segment falls under is decided by that time, not by the version the runner reports. The site keeps these statements to check the run when it is submitted; they are never shown.
POST /witness/,text/plain: the statement. Its first line isaas-witness v1oraas-witness v2; both are accepted. Thenphase: start|end,run_uid:,segment:,tooling: <version> <commit> <clean|modified>,at:, thent0:(start) orended_at:andseconds:(end). A v2 start addsruntime: <id> <version> <ai|no-ai|ai-unknown> <sha256>,instructions: <sha256>andgoal: <id> <sha256>; a v2 end addslog: <sha256> <records>, and a field the machine cannot fill is-. Last comekey: ssh-ed25519 …andsignature:, an ed25519 signature over every line before it joined by a newline. The key must be a publisher key on an account. The answer is{received_at, statement_sha256, site_key, receipt}, the receipt being the site's signature overaas-witness-receipt v1,statement: <statement_sha256>,received: <received_at>joined by newlines. A start is taken once per run and segment, an end only after its start and from the same account.GET /witness/key/?fingerprint=SHA256:…says whether a key is on an account. The site's own key is at/.well-known/aas-witness.txt.