Finish or terminate a run
const url = 'https://beta-api.plune.ai/v1/runs/example/events';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"event":"finish"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://beta-api.plune.ai/v1/runs/example/events \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "event": "finish" }'The run lifecycle. finish freezes the summary and closes the run to new results; terminate closes it with a reason and can be reopened by joining the same key again. A repeated finish is an idempotent success — the CI step that sends it may itself be retried. Terminating an already finished run is the one refusal: its summary may already have gated a release.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Request Body required
Section titled “Request Body required ”object
Examples
Close the run and freeze its summary
{ "event": "finish"}Close it without a summary, and say why
{ "event": "terminate", "reason": "CI job cancelled by a newer push"}Responses
Section titled “ Responses ”The run, its summary if it just finished, and how many cases the finish detached (configuration.full)
object
object
The value rerun is reached only by joining a terminated run again — it is never requested.
What the run expects to happen. Without it, a crashed shard is indistinguishable from a green run.
object
object
object
object
The caller states that expected is everything its source has — the whole suite, not one file or a --grep. When such a run finishes with an empty notRun, every active case that this source (the source of the run’s results) reported before and did not report in this run moves to detached, with the run named as the reason. Manual cases, other sources, in_review and deprecated cases are never touched; a later result on a detached case brings it back to active by itself. Set it only for the run that covers the whole suite (PLUNE_FULL_RUN=1 in the CLI).
Who is to run which case (B8-team T4): testCaseId → userId. Filled at launch by assignment: prefer-assignee from each expected case’s assigneeId at that moment — a snapshot, so a case that changes hands later does not rewrite a run under way. Cases nobody holds are absent. May also be sent as is.
object
object
The commit the run was made from — run.sha in GET /v1/runs/{id}/summary.
The branch it was made on — run.branch in GET /v1/runs/{id}/summary.
The CI run that produced the report — the reporter reads the runner’s own config.metadata.ci.buildHref. Sanitized before it is stored (a credential in a query parameter becomes a marker); set by the shard that opens the run and never changed by one that joins it. The run page links it from GET /v1/runs/{id}/summary.
The tool that reported the run — playwright, or the report format plune run import read — not the machine of the CI; run.runner in GET /v1/runs/{id}/summary.
The plan the run was launched from (D33), when it was — what GET /v1/runs?planId= filters by.
What to call this run in a list. The first shard to declare one wins; a shard that arrives later fills a field left empty rather than overwriting.
Where it ran — staging, prod, a preview. Filterable on GET /v1/runs, so two CI matrices read as two histories.
How the run is marked — smoke, nightly, a team name. An empty array is how every mark is removed.
What a run amounted to, frozen at finish. Distinct from RunSummary, which is the list-item shape.
object
One entry per status that occurred; an absent key means zero.
object
Expected and never reported — what keeps a dead shard from looking green.
Who launched it (B8-team T3): the userId of the session, or of the owner of the token that started or synced it — the first shard to create the run; a joiner is not recorded. null for a run from before names were kept. Resolve it to a name through GET /v1/members.
What a run amounted to, frozen at finish. Distinct from RunSummary, which is the list-item shape.
object
One entry per status that occurred; an absent key means zero.
object
Expected and never reported — what keeps a dead shard from looking green.
Cases moved to detached by this finish: those the run’s source reported before and this full run did not. 0 unless the run declared configuration.full and reported everything it expected.
Example
{ "run": { "id": "6f1c2b3a-0000-4000-8000-0000000000d1", "projectId": "u-shop::default", "kind": "automated", "externalKey": "gh-35003177826", "statusEvent": "finished", "configuration": { "full": true, "expected": [ { "testCaseId": "6f1c2b3a-0000-4000-8000-0000000000c1" }, { "externalKey": { "kind": "path-title", "value": "e2e/checkout.spec.ts > Checkout — coupon applied" } } ] }, "meta": { "sha": "3e31591", "branch": "master", "ciUrl": "https://github.com/plune-ai/plune/actions/runs/35003177826", "runner": "playwright" }, "startedAt": "2026-09-16T09:12:04.151Z", "finishedAt": "2026-09-16T09:12:09.082Z", "terminateReason": null, "summary": { "total": 2, "counts": { "passed": 1, "failed": 1 }, "notRun": [] }, "title": "plune · 2026-09-16 09:12 · ci", "environment": "staging", "labels": [ "plune", "vitest", "ci" ], "createdBy": "u-shop" }, "summary": { "total": 2, "counts": { "passed": 1, "failed": 1 }, "notRun": [] }, "detached": 0}Not a known event
object
Example generated
{ "error": "example"}No / invalid token or session
Not found or not yours
object
Example generated
{ "error": "example"}The event does not apply from this state; the body names what does
object
What WOULD apply from this state. A CI script needs this to stop retrying.
Example generated
{ "error": "example", "from": "example", "event": "example", "allowed": [ "example" ]}Rate limited