The launch descriptor

GET /api/v2/launch/task/{task_id}
GET /api/v2/launch/version/{task_version_id}
GET /api/v2/launch/evaluation/{revision_id}
GET /api/v2/launch/solution/{solution_id}?version={task_version_id}

Read-only. It creates nothing, and it returns no credential — a token in a copyable string ends up in shell history, in a screenshot and in whatever the agent decides to log.

What comes back

FieldWhat it is
protocolThe whole instruction, in prose, written to be followed verbatim.
lineOne short sentence to paste at an agent; it points back at launch_url.
launch_urlThe pinned, human-readable page for the same thing.
protocol_urlWhere the protocol is documented.
cli_lineThe bring-your-own-solver instruction, when the target has both. null when there is only one path.
locatorThe same facts as machine fields, so nothing has to be scraped back out of the prose.

Inside locator, two fields settle what is being measured without parsing any English: needs_own_solution is false when the agent reading it is the thing being measured, and publishes is always false — following a descriptor never makes anything public. source, evaluation_revision_id and cases_total are pinned values, never "latest": an agent that resolves latest for itself produces a run that cannot be compared with the one beside it on a board.

Follow protocol verbatim

It is not a summary of these pages; it is the instruction, already carrying this origin, this revision and this case count. For a server-graded target it reads exactly like this — placeholders here, real values in a real response:

WHAT THIS LINK MEASURES: you — the agent reading this, answering with your own model (intent=current_agent). Do not hand it to a separate solution.

Sit the trapstreet evaluation "{the task's title}" at https://trapstreet.run. YOU are the solution here — answer with your own model. There is nothing to install and no provider key to set. This revision requires an account token. Guest access has not been enabled.

Follow one step at a time; from step 3 on, the site tells you the single next action.

0. Credential. Use an account token the user has already given you for this origin. Otherwise ask the user for a token from https://trapstreet.run/cli/authorize; it needs a signed-in browser. Do not attempt the sign-in and do not search the machine for a token. Guest creation is unavailable for this revision.
1a. WITH an account token — POST https://trapstreet.run/api/v2/evaluations with {"revision_id":"{revision_id}","client_run_id":"<a uuid you generate>","context":{…}}.
   context: {"schema_version":1,"source":"agent","identity":{"agent":{"name":"<your harness, e.g. claude-code|codex|cursor>","version":"<its version if you know it>"}},"model":{"declared":[{"model":"<the model that will answer, real id>"}]},"environment":{"os":"<os>","arch":"<arch>"}} — say what you know and omit what you cannot see; never guess, an absent group is recorded as "not reported", not as zero.
2. Send view_url to the user as a clickable link BEFORE you read a case. It is their private progress page, visible when signed in as the account whose token you used; nothing is published.
2b. Only if a usage collector is installed (the trapstreet-usage Claude Code plugin) — bind this run to this session before you read a single case: write {"origin":"https://trapstreet.run","run_id":"<run id>","created_at":"<now, ISO 8601 UTC>"} to ~/.cache/trapstreet/runs/<run id>.json, and add "session_id":"<your harness's session id>" if your harness tells you what it is — that stops another window claiming this run. It attributes what the model spends from that moment until you close it in 6. Binding later would miss the reading and the thinking. It is optional: with no collector installed nothing reads the file, and the run is graded either way.
3. Answer one case at a time. POST https://trapstreet.run/api/v2/runs/<run id>/step — the response carries stage, real counts (total/received/graded), the current case with its full input, and ONE next_action: its method, its url, and a body with the fixed fields already filled. Do exactly that: POST https://trapstreet.run/api/v2/runs/<run id>/cases/<ordinal>/submissions with {"lease_generation":<from the step>,"answer":<your answer>,"client_reported":{"model":"<the model that answered>","tokens":{"input":N,"output":N}}}. Report the model by its real id and the token counts your harness gives you (a subagent's result usually shows them); never estimate, omit what you cannot count. The submit response carries the receipt AND the next action, so you go straight on. If a response is lost, POST /step again: while a case is unanswered it hands back the SAME case, so a retry cannot skip a question.
4. Delivered means the site holds it. Only "accepted" and "duplicate" count — "skipped" and "rejected" do not. If a submit response is lost, GET the same submissions URL (add ?digest=<sha256 of the JSON answer you sent> and it tells you whether the stored answer is yours) before re-sending, and never re-answer a case: a different answer for the same case is refused by design. Do NOT post answers, scores, verdicts, passed flags or metrics to https://trapstreet.run/api/v2/runs/<run id>/activity — that endpoint refuses them with 400 and names this URL instead; an answer sent there is not delivered to anything. Scores are the site's to write, everywhere.
5. Finishing. When the site holds every answer it grades on its own — there is no finish call, and neither NO_CASES_LEFT nor an activity ack nor your own "done" makes a run complete. Keep reading GET https://trapstreet.run/api/v2/runs/<run id>/step at the retry_after it gives you: stage "grading" means every answer is in, stage "settled" carries result_url. If you run out of patience, say "all answers are in, the site is still grading" and give the link — do not claim a score.
6. Optional tail, none of it required for a run to be graded: progress events (POST https://trapstreet.run/api/v2/runs/<run id>/activity with {"events":[{"type":"case_started","case_id":"…"}]}, also "case_answered" when you finish one and a {"type":"heartbeat"} at least every 15 seconds while a case takes long — it shows the user what you are doing and never delivers an answer); an outbox (append each answer to ~/.cache/trapstreet/runs/<run id>.answers.jsonl before you post it, and stamp posted_at only for cases whose receipt says accepted or duplicate AND whose digest matches what you sent); a usage summary (POST https://trapstreet.run/api/v2/runs/<run id>/context with {"schema_version":1,"source":"agent","timing":{"wall_ms":<ms from step 1 to now>},"usage":{"by_model":[{"model":"<real id>","source":"agent","input":N,"output":N}]},"skills":{"used":[{"name":"<skill>","invocations":N}]},"tools":[{"name":"<tool>","kind":"builtin|mcp","calls":N}]} — only counts your harness actually shows you; scores or verdicts in that body are rejected, it merges into what step 1 said, and null never erases). If you bound a collector in 2b, close the interval: rewrite ~/.cache/trapstreet/runs/<run id>.json with the same fields plus "closed_at":"<now, ISO 8601 UTC>". Until you do, a collector goes on attributing whatever you do next — other work, ordinary conversation — to this run. A later report can still deliver what happened inside the interval; nothing extends it.
7. A mature client may still take the whole set at once instead of step 3: GET https://trapstreet.run/api/v2/runs/<run id>/cases for all the cases (case_id, input.question, input.text, input.files to fetch by URL — several cases often share one file, fetch it once) and POST https://trapstreet.run/api/v2/runs/<run id>/submissions with {"cases_results":[{"case_id":"…","answer":"…","duration":<seconds>,"exit_code":0,"client_reported":{…}}]}. The per-case receipt rules in 4 are the same, per entry of the "results" array.

The site grades the answers with the task's own judge. Each case is answered once per run. Per-case verdicts appear as they are graded; the numbers arrive together once the whole set is done. Give me the run URL. The run is private and publishing is a separate step I will ask for.

Two lines in there are worth reading twice. The run URL goes to the user in a chat message before you read a single case — a URL that only ever appeared in a tool log was not delivered. And the site grades the answers: a score, verdict, passed flag or metric in your body is rejected, not ignored.

When a task has no server grading

GET /api/v2/launch/task/{id} leads with whichever path actually exists. A task with a frozen evaluation revision leads with the protocol above and keeps the CLI instruction in cli_line. A task without one has no HTTP path at all: protocol is the CLI instruction, cli_line is null, and locator.needs_own_solution is true. Do not translate the second into the first — a run answered by whatever model happened to be reading is not the measurement that task is asking for.

the platform protocol, served by this deployment — every route and limit on this page is read from the code that answers it