AuthenSeeDocs

Sessions

Mint hosted-flow sessions with your secret key, read and revoke them, and poll for a result — including the cross-device handoff.

A session scopes one enrollment or authentication flow. Your backend mints it with your secret key; the browser only ever sees the single-use flowCode (inside hostedUrl), which the hosted flow redeems for an in-memory session token.

POST /v1/sessions

Create a session. This is a server-to-server call from your backend, and usually the first API call in every flow.

Auth: provider secret key (x-api-key)

Request

curl -X POST https://api.authensee.com/v1/sessions \
  -H "x-api-key: sk_live_9aB3xK7mQ2wE5rT8yU1iO4pL6s" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "full",
    "externalUserId": "user_12345",
    "callbackUrl": "https://app.example.com/authensee/callback",
    "ttl": 3600
  }'
FieldTypeRequiredDescription
scope"enroll" | "authenticate" | "full"YesWhat the session can do. full allows both enrollment and authentication.
externalUserIdstringNoYour user ID. Binds the session to that account and is echoed on the auth result.
providerSubjectstringNoYour stable alias for the persona, from a previous auth result. Must already be known for your provider; rejected otherwise.
callbackUrlstring (URL)NoWhere the hosted flow returns the result. The origin must be on your provider's allowed-callback-origins list — an empty list rejects every callback URL.
enrollMode"new_persona"NoForce a fresh-persona enrollment (skips the "log in with your existing persona" branch). Requires enroll or full scope. Rejected if the external user is already enrolled.
ttlnumberNoSession lifetime in seconds, 1–86,400. Default 3,600.

Response

{
  "sessionId": "9182e25e-0b7b-4d92-ae98-a3c710c905c1",
  "sessionToken": "sess_aG25OA7mEnbOSX2J8UwnEE-GhoRdTLxN2hVeFxhWons",
  "flowCode": "flow_UbxOJPFbQYyQ3wNhb1LEXBWGqXWKFxfvBjUKY9GmJao",
  "hostedUrl": "https://auth.authensee.com/flow/flow_UbxOJPFbQYyQ3wNhb1LEXBWGqXWKFxfvBjUKY9GmJao",
  "scope": "full",
  "expiresAt": "2026-07-09T12:00:00.000Z"
}
FieldTypeDescription
sessionIdstring (UUID)The session's ID — use it for correlation and for GET /v1/sessions/:id
sessionTokenstringsess_-prefixed token. For direct session-token calls only — never expose it to the browser.
flowCodestringSingle-use flow_-prefixed code the hosted flow redeems for the token
hostedUrlstringReady-to-use hosted-flow URL (https://auth.authensee.com/flow/{flowCode}) — hand this to the browser
scopestringEchoed scope
expiresAtstringISO 8601 expiration timestamp

Send the user the hostedUrl, never the token

The flowCode inside hostedUrl is the only credential that should ever reach a browser, and it can be redeemed exactly once. If you use @rebellion-systems/authensee-sdk, its createSession() deliberately omits sessionToken from the return value.

Errors

CodeStatusWhen
VALIDATION_ERROR400callbackUrl origin not on your allowlist, unknown providerSubject, enrollMode with authenticate scope, or TTL out of range
UNAUTHORIZED401Missing or invalid secret key
NOT_FOUND404scope: "authenticate" with an externalUserId that has no AuthenSee enrollment — enroll first, then authenticate
CONFLICT409scope: "authenticate" for an account whose enrollment is incomplete, or enrollMode: "new_persona" for an account that is already enrolled

GET /v1/sessions/current

Read the current session using its own token. Unlike GET /v1/sessions/:id, this is authenticated by the session token itself — it's how the hosted flow validates a session for any provider without holding that provider's secret key.

Auth: session token (Authorization: Bearer sess_...)

Request

curl https://api.authensee.com/v1/sessions/current \
  -H "Authorization: Bearer sess_aG25OA7mEnbOSX2J8UwnEE-GhoRdTLxN2hVeFxhWons"

Response

{
  "sessionId": "9182e25e-0b7b-4d92-ae98-a3c710c905c1",
  "providerId": "01906dd2-4c8a-7bb3-b7f0-5e12c9a4d833",
  "scope": "full",
  "externalUserId": "user_12345",
  "providerSubject": null,
  "callbackUrl": "https://app.example.com/authensee/callback",
  "theme": { "displayName": "Acme", "accent": "#7c5cff", "mode": "dark" },
  "factorCombination": "passkey_and_image_points",
  "ceremony": {
    "requiresHandoff": false,
    "requiredCapabilities": []
  },
  "expiresAt": "2026-07-09T12:00:00.000Z",
  "revokedAt": null,
  "createdAt": "2026-07-09T11:00:00.000Z"
}

The response carries everything the hosted flow needs to run the right ceremony: the provider's current factorCombination, the co-brand theme (when configured — see theming), and a ceremony descriptor stating whether the flow needs a cross-device handoff and which device capabilities it requires (for example, passkey_and_behavior requires an accelerometer and sets requiresHandoff: true). Branding is read live, so a mid-flight brand change shows up on the next refresh.


GET /v1/sessions/result

Long-poll for the session's outcome. The hosted flow uses this on the device that initiated a cross-device handoff: the desktop polls its own session while the user completes the ceremony on their phone.

Auth: session token (Authorization: Bearer sess_...)

Request

curl "https://api.authensee.com/v1/sessions/result?waitMs=25000" \
  -H "Authorization: Bearer sess_aG25OA7mEnbOSX2J8UwnEE-GhoRdTLxN2hVeFxhWons"
Query parameterTypeRequiredDescription
waitMsnumberNoHow long the server may hold the request while the result is pending, 0–25,000 ms. Default 25,000.

The server holds the request only while the status is pending. Every other status returns immediately, so the polling device reflects state changes without waiting out the timer.

Response

One of four statuses:

{ "status": "pending" }
{ "status": "in_progress" }
{
  "status": "failed",
  "code": "INVALID_PROOF",
  "message": "Proof verification failed"
}
{
  "status": "complete",
  "authResultCode": "ar_bxvS6dFBzoJKW3BQxYtDm4qGh8N2LcVp",
  "factorsVerified": ["image_points", "passkey"]
}
StatusMeaning
pendingNothing has happened yet (for a handoff: the phone hasn't redeemed the code)
in_progressThe second device connected but hasn't finished the ceremony
failedThe second device reported a terminal failure; code and message are present when it supplied them
completeThe ceremony finished — for a login, authResultCode is present for the server-side exchange

The linked completion. When the flow completed by linking an existing persona instead of a fresh login, the complete response carries linked: true and no authResultCode:

{
  "status": "complete",
  "linked": true,
  "providerSubject": "sub_9f3kQzWx7RmT2yLpB8vN4cD6",
  "factorsVerified": []
}

Distinguish the two by the presence of linked — a link marker is never exchangeable for a token. An enroll-only completion returns complete with an empty factorsVerified and no code.


Cross-device handoff

When the ceremony must run on a different device than the one that opened the flow — for example, a motion-gesture enrollment started on a desktop — the hosted flow mints a single-use handoff code, shows it as a QR code, and long-polls GET /v1/sessions/result while the phone completes the ceremony. These endpoints are driven by the hosted flow itself; a typical integration never calls them directly.

POST /v1/sessions/handoff

Mint a single-use handoff code for the current session. Does not invalidate the initiating device's token.

Auth: session token (Authorization: Bearer sess_...)

{
  "handoffCode": "handoff_Xk2mP9qTvW4yBzR7cN5jL8fA3hD6uE1s",
  "expiresAt": "2026-07-09T11:04:00.000Z"
}

The code expires after 2 minutes (or at session expiry, whichever comes first); the initiating device can mint a fresh one.

POST /v1/sessions/handoff/redeem

Anonymous: the second device redeems the handoff code for its own device-scoped session token. The response is the minimal session shape (sessionId, sessionToken, scope, expiresAt) — no flowCode or hostedUrl is exposed here.

POST /v1/sessions/handoff/fail

The second device reports a terminal ceremony failure (with optional code and message, up to 120 and 500 characters), so the initiating device's poll resolves to failed instead of spinning. Idempotent.

Auth: the second device's session token (Authorization: Bearer)


GET /v1/sessions/:id

Read a session you minted. Returns the same shape as GET /v1/sessions/current.

Auth: provider secret key (x-api-key)

curl https://api.authensee.com/v1/sessions/9182e25e-0b7b-4d92-ae98-a3c710c905c1 \
  -H "x-api-key: sk_live_9aB3xK7mQ2wE5rT8yU1iO4pL6s"
CodeStatusWhen
UNAUTHORIZED401Missing or invalid secret key
FORBIDDEN403The session belongs to a different provider
NOT_FOUND404No session with this ID

DELETE /v1/sessions/:id

Revoke a session before it expires. Subsequent session-token calls fail with UNAUTHORIZED.

Auth: provider secret key (x-api-key)

curl -X DELETE https://api.authensee.com/v1/sessions/9182e25e-0b7b-4d92-ae98-a3c710c905c1 \
  -H "x-api-key: sk_live_9aB3xK7mQ2wE5rT8yU1iO4pL6s"

Returns 204 No Content on success. Errors match GET /v1/sessions/:id.


The flow-code redemption

For completeness: the hosted flow turns flowCode into a session token via POST /v1/hosted/flow-code/redeem. The code is single-use — a mid-flow full-page refresh restarts the flow because the code can't be redeemed twice — and the endpoint serves the AuthenSee hosted origin, so it's not part of your integration surface. See hosted flow surface security for why the token lives only in memory.

Next