Skip to content

Force-terminate an active or pending shell session (TASK-S20-004)

DELETE
/api/sessions/{session_id}
curl --request DELETE \
--url 'https://example.com/api/sessions/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0?organization_id=2489E9AD-2EE2-8E00-8EC9-32D5F69181C0' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "reason": "example" }'

Force-terminate a shell session immediately.

Auth

Caller must be authenticated and an org member. Authorisation is then:

  • Session owner (user_id matches): always allowed, regardless of role. Rationale: a user can always close their own session. Documented explicitly because the role-separation invariant otherwise rejects OWNER/ADMIN without a grant.
  • Org OWNER: allowed without a separate role grant (per project convention — OWNER is the unconditional administrative bypass for cross-cutting lifecycle ops like force-terminate).
  • Caller holds ShellRole.SESSION_TERMINATE (“device.shell.terminate”): allowed. This is the normal path for admins who have been granted the terminate role.
  • Any other caller: 403.

Cross-org isolation: the session must belong to organization_id; a session in a different org returns 404 (not 403) to avoid leaking existence information — matches the convention established in approve/deny.

Status guard: only ACTIVE or PENDING sessions can be force-terminated. An already-ENDED session returns 409 Conflict.

Behaviour

On success:

  1. Row-locks the ShellSession row (serialised against concurrent mutations).
  2. Flips status → ENDED, sets ended_at = now, exit_reason = “forced_close”.
  3. Appends a session.ended audit event in the same transaction.
  4. Commits.
  5. Calls gateway_client.kill_session() OUTSIDE the transaction (best-effort WS close — if the gateway is down the backend has already flipped the row).

Errors

404 — session not found, or belongs to a different org. 409 — session is already ENDED or DENIED. 403 — caller lacks terminate permission. 204 — success.

session_id
required
Session Id
string format: uuid
organization_id
required
Organization Id
string format: uuid
Media type application/json
Any of:
ForceTerminateBody

Optional request body for DELETE /sessions/{session_id}.

object
reason
Any of:
string
>= 1 characters <= 1000 characters
Example generated
{
"reason": "example"
}

Successful Response

Validation Error

Media type application/json
HTTPValidationError
object
detail
Detail
Array<object>
ValidationError
object
ctx
Context
object
input
Input
loc
required
Location
Array
msg
required
Message
string
type
required
Error Type
string
Example generated
{
"detail": [
{
"ctx": {},
"input": "example",
"loc": [
"example"
],
"msg": "example",
"type": "example"
}
]
}