Finish the session: drain, checkpoint, close for good
Ends a session whose work is done. Unlike terminate, nothing is killed: the running turn finishes, its checkpoint commits, and the session ends closed, which nothing reopens. Unlike a recovery close (POST /v1/sessions/{id}/recovery-decisions, decision: close), it is the owner's call on a healthy session, needs sessions:control rather than sessions:recover, and its receipt operation is close rather than recovery_decision. The API guide compares the three.
activewith a live worker: the worker drains as for a pause and the session goesclosing. The receipt staysaccepteduntil the execution is observed gone with a checkpoint covering every turn that ran; then the session isclosedand the receiptsucceeded. A drain over 60 seconds showsattention.code: CLOSE_BLOCKEDwith the reason; nothing is killed for it. An open question or approval holds the close until it is answered or the turn interrupted. If the drain cannot commit, the receipt fails and the session goes back toactive(orrecovery_requiredfor a turn whose outcome is unknown), with its queued input intact; a terminate overtakes it (CONTROL_SUPERSEDED).activewith no live worker,paused, or astoppedsession that can be resumed: closed in this request. With an execution still bound the receipt isaccepteduntil it is observed gone; otherwisesucceededin the response.
Queued input is cancelled in the transaction that closes the session (each input's receipt fails with SESSION_CLOSED). Push requests are left alone; request a push of the result after the close has succeeded, since a push requested inside the last turn is refused CHECKPOINT_MOVED once the close commits that turn's checkpoint. The receipt result is {resulting_admission_state, checkpoint_revision, cancelled_turn_count}. The stream carries a status event for closing and for closed, and a system event {subtype: session_closed, closed_by: close, close_receipt_id, checkpoint_revision, cancelled_turn_count}. A caller closing its own session from inside a turn must not wait on the receipt: the drain waits for that turn to end. After the close, messages and controls answer 409 SESSION_CLOSED.
Scope: sessions:control (API key or cookie session). Idempotency-Key: required.
AuthorizationBearer <token>For servers and scripts: an API key sent as an HTTP bearer token, Bearer <key>. It holds exactly the scopes it was issued with; a key issued before scopes existed holds none. It never needs the CSRF header, and it cannot manage agents, members, teams or API keys. Issue one in the web console under Settings → API keys; each operation names the scope it needs.
Scope: sessions:control
id*stringThe session's id.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidIdempotency-Key*stringRequired, 1–255 characters; a UUID per user intent works. A retry with the same key and the same body returns the original response and runs the command once; the same key with a different body answers 409 IDEMPOTENCY_CONFLICT. A missing or invalid key answers 400 BAD_REQUEST, after the body is read.
1 <= length <= 255X-Requested-With?"werft-web"Required with a cookie session: the literal werft-web.
"werft-web"application/json- body
expected_revision*integer0 <= value <= 2147483647reason?string1 <= lengthThe close is stored (accepted), or the session had nothing to wait on and is closed already (succeeded).
application/json- response
receipt_id*string^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidreceipt_status*string"accepted""succeeded""failed""unknown"curl -X POST 'https://example.com/v1/sessions/497f6eca-6276-4993-bfeb-53cbbbba6f08/close' \ -H 'Idempotency-Key: string' \ -H 'Content-Type: application/json' \ -d '{ "expected_revision": 7, "reason": "Work is done"}'{ "receipt_id": "c4a1e9d2-7b3f-4e8a-a6c5-1d2e3f4a5b6c", "receipt_status": "accepted"}