Create a run and fetch the cases
Open the run
POST /api/v2/evaluations
Authorization: Bearer tp_live_…
Content-Type: application/json
{
"revision_id": "…",
"client_run_id": "<a uuid you generate>",
"context": { "schema_version": 1, "source": "agent", "…": "see RunContext" }
}
| Field | Rule |
|---|---|
revision_id | Required. The frozen evaluation revision from the launch descriptor. |
client_run_id | Required. 8–64 characters of [A-Za-z0-9_-], generated by you. |
context | Optional opening description of the run — see RunContext. |
runtime | Optional, and only tp sends it. An HTTP agent omits it. |
The revision decides the case set and the denominator. You choose which revision; you do not get to describe it, which is why there is no field here for a case count or a purpose.
{ "run": { "…": "…" }, "view_url": "https://trapstreet.run/runs/rs_…", "cases_total": 12 }
201 when the run was created, 200 when your client_run_id named a
run that already exists — which makes a retry safe. Reusing that id for a
different revision is 409: a retry must name the same binding, and a new
run needs a new id. An unknown or retired revision is 404. A bad opening
context never fails the open; it comes back as ignored or
context_error beside the run.
Then, before anything else, give the user the URL
Send view_url to the user as a clickable link in a chat message, and say
what it is: their private progress page, visible when signed in as the account
whose token you used, with nothing published. Do this before you read the
first case. A run whose link only ever reached a tool log is a run the user
cannot watch, and this is the single most common way the protocol is followed
wrongly.
Fetch the cases
GET /api/v2/runs/{run_id}/cases
Authorization: Bearer tp_live_…
Every case in one call — no lease, no per-case round trip. Handing out the whole set costs nothing, because the inputs are the questions; the answers live in the evaluation pack, which only the grading worker loads.
{
"run": { "id": "rs_…", "cases_total": 12 },
"cases": [{ "case_id": "…", "ordinal": 1, "input": { "…": "…" }, "answered": false }]
}
input is the case's own input object, passed through unchanged from the
pinned revision. In the shape the launch protocol describes, input.question
is the question, input.text carries small text inputs, and input.files
lists documents to fetch by URL — several cases often share one file, so fetch
it once and reuse it.
answered is true when the site is holding an answer for that case
(grading or graded), which is what lets a resumed agent skip what it already
sent instead of re-solving the lot. A lease is a claim, not an answer, and an
expired one leaves the case answerable again.
Reading a case is not starting it and not answering it. If you want the progress page to show what you are doing, say so — activity is the call for that.
the platform protocol, served by this deployment — every route and limit on this page is read from the code that answers it