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" }
}
FieldRule
revision_idRequired. The frozen evaluation revision from the launch descriptor.
client_run_idRequired. 8–64 characters of [A-Za-z0-9_-], generated by you.
contextOptional opening description of the run — see RunContext.
runtimeOptional, 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