Zitadel Preview Docs

Exchange handoff token for a session

Consumes a one-time `handoff_token` minted by `POST /auth_attempts/{id}/handoff` and returns the resulting session and a `session_token`. The server resolves the originating `auth_attempt` from the token and then: | Originating auth_attempt | Outcome | |---|---| | No `session_id` | A new authenticated session is **created**. | | `session_id` points to an anonymous shell | Existing session is **upgraded** — user and factors written in, TTL reset to full session TTL. | | `session_id` points to an active session (step-up) | Existing session is **upgraded** — new factors merged, `assurance_levels[]` expanded. | The response shape is identical in all three cases. The `session_token` supersedes any previously issued `session_token` for the same session. Clients must replace their stored token at this point. Requires a project service key (OAuth2 client credentials).

POST
/sessions/exchange

Consumes a one-time handoff_token minted by POST /auth_attempts/{id}/handoff and returns the resulting session and a session_token.

The server resolves the originating auth_attempt from the token and then:

Originating auth_attemptOutcome
No session_idA new authenticated session is created.
session_id points to an anonymous shellExisting session is upgraded — user and factors written in, TTL reset to full session TTL.
session_id points to an active session (step-up)Existing session is upgraded — new factors merged, assurance_levels[] expanded.

The response shape is identical in all three cases.

The session_token supersedes any previously issued session_token for the same session. Clients must replace their stored token at this point.

Requires a project service key (OAuth2 client credentials).

Authorization

oauth2 session.write
AuthorizationBearer <token>

In: header

Scope: session.write

Query Parameters

project_id*string

The unique identifier of the project

Header Parameters

Idempotency-Key?string

Optional retry key for safe retries of the handoff exchange (Category B idempotency). Retries carrying the same key within the exchange window return the cached session payload without consuming the one-time handoff token again.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Request to exchange a handoff token for a session and session_token.

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/sessions/exchange?project_id=proj_01hexample" \  -H "Content-Type: application/json" \  -d '{    "handoff_token": "handoff_abc123def456xyz"  }'
{  "session": {    "session_id": "sess_01J0Z9KX7Y0Q2Y7JX5M9K2YF3C",    "project_id": "proj_01hexample",    "state": "active",    "user_id": "user_id_12345",    "name": "Ada Lovelace",    "email": "ada@example.com",    "factors": [      {        "method": "password",        "verified_at": "2026-04-28T15:32:00Z",        "payload": {          "user_id": "user_id_12345"        }      }    ],    "assurance_levels": [      "urn:nist:aal:1",      "urn:nist:aal:2"    ],    "metadata": {},    "user_agent": {      "fingerprint": "fp_abc123",      "ip": "203.0.113.42"    },    "created_at": "2026-04-29T10:00:00Z",    "expires_at": "2026-04-30T10:00:00Z"  },  "session_token": "stok_abc123def456"}
{  "code": "string",  "message": "string",  "details": {}}
{  "code": "string",  "message": "string",  "details": {}}
{  "code": "string",  "message": "string",  "details": {}}

{  "code": "att.not_found",  "message": "The auth attempt was not found."}

Create an anonymous session shell POST

Creates an anonymous session shell with no user and no factors (`state: building`). This is optional — an `auth_attempt` will create a session implicitly if none is provided. Use this explicitly when you want to: - Pre-allocate a `session_id` before the user is known, so device/telemetry signals can be correlated with the eventual authenticated session from the start. - Track anonymous state (bot detection, device fingerprint) that survives until authentication. Creating a session is an app-plane operation on the project credential (`session.write`). The returned `session_token` is a session credential for the end-user client, not a management scope: it is delivered as the `__nextgen_session` cookie and authorises the self-service operations `GET /sessions/me` and `DELETE /sessions/me` (`nextgenSession` scheme). The by-id operations `GET /sessions/{session_id}` and `DELETE /sessions/{session_id}` are operator endpoints and require `session.read` / `session.delete` instead. The `session_token` is superseded when a handoff exchange completes — clients must replace it at that point. Anonymous sessions expire aggressively (10-minute TTL). The TTL resets to the configured full session TTL when the first authentication factor is written via a completing `auth_attempt`.

Get my session state GET

Returns the current state of the current session including its factors and all currently satisfied assurance levels. `assurance_levels[]` may shrink over time as factor freshness windows expire, without the session itself expiring. Use step-up authentication (a new `auth_attempt` against the same `session_id`) to restore a dropped assurance level.