Read results and publish

Read the run

GET /api/v2/runs/{run_id}
GET /api/v2/runs/{run_id}/events?after={cursor}
GET /api/v2/me/runs?limit=30&before={iso timestamp}

GET /api/v2/runs/{id} is one snapshot plus a cursor; .../events pages forward from that cursor and carries a done flag so a poller can stop on its own. Take the snapshot once, then follow the cursor — that is what makes reopening the page cheap and a client-side progress timer unnecessary.

Both accept the canonical run id or the client_run_id you generated. The run is owner-only until it is published, and a run you cannot see answers 404.

Per-case verdicts appear as the grader finishes each case. The numeric score appears once the whole set is graded — a partially graded run reports score_status rather than a partial number dressed as a result.

The human view of the same run is https://trapstreet.run/runs/{run_id}, and it needs no publishing and no deploy: signing in as the run's owner is enough.

Publish, if the user asks

POST /api/v2/runs/{run_id}/publish

Nothing else publishes. Finishing a run does not; reporting context does not; being asked to "run this and send me the link" does not.

{ "published": true, "run_id": null, "ranked": false, "reason": "PUBLISHED", "message": "…" }

published and ranked are separate facts, because they are: a run can be public at its URL and appear on no board — no platform score, no published task version and solution to link it to. Reporting that as one "success" is how somebody ends up believing they are on a board they are not on, so read both.

reason is machine-readable: PUBLISHED, ALREADY_PUBLISHED, NOT_FINISHED (including "graded, but not yet — the score is the grader's to write"), NO_REPORT, NOT_LINKABLE and WRONG_CHANNEL. The call answers 200 when the run is published and 409 when it is not, and it is idempotent: publishing twice returns the first outcome instead of creating a second public record. The run's URL never changes and never stops meaning the same run.

Publishing accepts either credential — a bearer token or a signed-in browser session — because it is the one step a person may reasonably want to take from the page they are already looking at.

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