---
title: "End a session and purge its context"
method: POST
path: "/v1/sessions/{sessionID}/release"
tags: ["Sessions"]
---

# End a session and purge its context

`POST /v1/sessions/{sessionID}/release`

## Request

- Base URL: `https://api.spicrawl.com`
- URL: `https://api.spicrawl.com/v1/sessions/{sessionID}/release`
- Auth: HTTP bearer

Marks the session `released` and deletes its stored cookies and storage in the same transaction. The
browser worker's own copy of the session state in object storage is deleted too. The
session row stays readable via `GET /v1/sessions/{id}`; the context does not. This is not
"release the lease, keep the state": the state is destroyed. A later `/v1/scrape` with this
`session_id` is refused with 410 `ERR::SESSION::RELEASED` at 0 credits.

Pitfalls:
- Idempotent: releasing an already-released session returns 200 with the same body.
- Releasing an expired session succeeds and marks it `released`.
- While a render holds the lease, returns 409 `ERR::SESSION::BUSY` with `Retry-After`. Wait, or repeat
  with `force=true` to end the session and fail the running task.
- Takes no body.

## Query parameters

- `force` boolean

## Response `200`

The session, now `released`, with `context` null.

- Session: A session as every session endpoint reports it. Never contains cookies.
  - `id` string, required: Session id (26-character ULID). Pass it as `session_id` on `/v1/scrape`.
  - `project_id` string, required: Project the session belongs to (the creating key's project).
  - `engine` 'fetch' | 'js' | 'browser': The rendering tier a session is bound to for its lifetime (`fetch` is HTTP only, `js` is JavaScript rendering, `browser` is full-browser rendering). Chosen automatically when the session is created unless the create names one.
  - `status` 'active' | 'released' | 'expired', required: Derived from the clock, not a stored flag. `active` sessions can be used; `released` and `expired` ones have no context and cannot be revived. Create a new session.
  - `sticky_key` string, required: Exit pin in use. The lowercase session id when you did not set one; empty string when `rotate_ip` was set.
  - `proxy` SessionProxy, required: The session's exit intent. Not an endpoint or IP.
    - `tier` 'none' | 'datacenter' | 'residential', required: Exit tier. `residential` when created with `premium_proxy`, otherwise `datacenter`. `none` is reserved and not produced by the create endpoint.
    - `country` string: Lowercase alpha-2 exit country. Omitted when no country was requested.
    - `region_pool` string: Regional pool requested at creation. Omitted when none.
  - `created_at` string, date-time, required: Creation time, RFC 3339 UTC.
  - `last_used_at` string, date-time, nullable, required: Last time a successful scrape used the session. Null means never used.
  - `expires_at` string, date-time, required: Current expiry. Each successful scrape moves it to that time + the session's `ttl_seconds` (never earlier than it was, never past `hard_expires_at`). After it passes the context is purged.
  - `hard_expires_at` string, date-time: Fixed ceiling set at creation from the organization's maximum TTL. Plan a re-login before this time. Omitted only for legacy rows.
  - `released_at` string, date-time: When the session was released. Present only on released sessions.
  - `usage_count` integer, required: Number of successful scrapes that have used the session. Failed scrapes and `/context` reads do not count.
  - `context` SessionContextSummary, required: Non-secret counts describing the stored context.
    - `schema_version` integer, required: Stored context format version. Currently 1.
    - `cookies` integer, required: Number of cookies stored.
    - `origins` integer, required: Distinct origins across local storage, session storage and IndexedDB.
    - `bytes` integer, required: Serialised plaintext size in bytes. The limit is 1048576.
  - `retired` 'expired' | 'usage_exhausted' | 'explicit': Why a worker took this session out of service. Absent while usable. A retired session should be replaced.
  - `domain_scores` object: Per-domain block evidence the workers maintain. Omitted when empty.
  - `fingerprint` object: Browser fingerprint, as stored. Omitted when none was set.
  - `lease` SessionLease: Present only while a render holds the single-writer lease. While present, other scrapes, release and delete get 409 `ERR::SESSION::BUSY`.
    - `holder` string: Identifier of the worker holding the lease. Diagnostic only.
    - `expires_at` string, date-time, required: When the lease lapses at the latest. Retry after this time.
  - `warnings` string[]: Decisions made on your behalf at creation, e.g. a TTL reduced to the organization maximum. Only on the create response; omitted when empty.

## Other responses

- `400`: The request is invalid. Not retryable; fix the request using `code` and `diagnostics.hint`.
- `401`: Missing, invalid, revoked or expired key. Not retryable with the same key.
- `403`: The key lacks the scope this route requires (`ERR::AUTH::INSUFFICIENT_SCOPE`), or the action is not permitted.
- `404`: No such resource for this key's organization.
- `409`: Conflicts with the resource's current state, for example a session in use (`ERR::SESSION::BUSY`, retryable).
- `500`: Internal error. Retryable when `retryable` is true.

---

[API](https://skmtc.dev/spicrawl/apis/spicrawl-api.md) · [All operations](https://skmtc.dev/spicrawl/apis/spicrawl-api/llms.txt) · [OpenAPI document](https://skmtc.dev/spicrawl/apis/spicrawl-api/revisions/c4b4c6f97219?raw)
