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).
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).
oauth2 session.writeAuthorizationBearer <token>Scope: session.write
project_id*stringThe unique identifier of the project
^[a-zA-Z0-9_-]+$Idempotency-Key?stringOptional 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.
application/json- body
Request to exchange a handoff token for a session and session_token.
handoff_token*stringThe one-time handoff token minted by POST /auth_attempts/{id}/handoff.
Single-use — replaying it after a successful exchange returns 410 Gone.
ttl?stringOptional session lifetime after exchange as an ISO-8601 duration. When omitted, the server uses the configured default. Must not exceed the configured maximum.
durationHandoff exchanged successfully. Returns the session and new session_token.
application/json- response
Response for session creation and handoff exchange. Includes the session state and the session_token required to manage it.
session*The current state of a session.
A session is the durable post-auth primitive. It accumulates verified authentication
factors and exposes assurance_levels[] — the set of assurance profiles its current
factors satisfy. Sessions are read-only from the client's perspective: factors flow
in through auth_attempts.
session_token*stringBearer token authorising management of this session (GET and DELETE). For anonymous sessions this token is superseded when a handoff exchange completes — clients must replace their stored token at that point.
curl -X POST 'https://example.com/sessions/exchange?project_id=string' \ -H 'Content-Type: application/json' \ -d '{ "handoff_token": "string"}'{ "session": { "session_id": "string", "project_id": "string", "state": "building", "user_id": "string", "user": { "user_id": "string", "identifier": "string", "identifier_property": "string", "display": "string" }, "factors": [ { "method": "identifier", "verified_at": "2019-08-24T14:15:22Z", "payload": { "method": "identifier", "user_id": "string" } } ], "assurance_levels": [ "string" ], "metadata": {}, "user_agent": { "fingerprint": "string", "ip": "string" }, "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z" }, "session_token": "string"}