API reference
The AuthenSee auth server REST API — sessions, enrollment and verification, auth results, and personas.
The AuthenSee auth server exposes a REST API for session management, persona identity, enrollment, challenge generation, and proof verification. All endpoints are prefixed with /v1/ and accept and return JSON.
Authentication
The API uses two credentials, and every endpoint accepts exactly one of them:
| Credential | Header | Who holds it | Used for |
|---|---|---|---|
Provider secret key (sk_live_... / sk_test_...) | x-api-key | Your backend only | Server-to-server calls: minting sessions, exchanging auth results |
Session token (sess_...) | Authorization: Bearer | The hosted flow, in memory | Ceremony calls scoped to one session: identify, enroll, challenge, verify |
Session tokens are minted by POST /v1/sessions (a secret-key call on your backend) and scoped by scope (enroll, authenticate, or full) and a TTL. The hosted flow obtains the token by redeeming a single-use flowCode; the token itself never travels in a URL or a cookie.
Which calls do I make?
A typical integration makes exactly two API calls from its own code: POST /v1/sessions to start a flow and POST /v1/auth-results/exchange to finish one. The session-token endpoints are called by the AuthenSee hosted flow on the user's behalf — they're documented here so you can see precisely what crosses the wire.
Endpoints
| Area | Endpoints |
|---|---|
| Sessions | POST /v1/sessions · GET /v1/sessions/current · GET /v1/sessions/result · GET /v1/sessions/:id · DELETE /v1/sessions/:id · cross-device handoff |
| Enrollment & verification | POST /v1/enrollments · POST /v1/enrollments/add · POST /v1/challenges · POST /v1/verify |
| Auth results | POST /v1/auth-results/exchange · GET /v1/auth-results/:id · JWT claims |
| Personas | POST /v1/personas/identify · GET /v1/personas/:personaId · enrollment status · provider links · unlink |
The scheme model
Every enrollment is registered against one scheme — a fixed factor combination backed by a dedicated circuit. The server stores one aggregate commitment per enrolled scheme per persona, and derives all factor and circuit semantics from the schemeId — never from client input:
| Scheme | Circuit | Factors verified | Selected by provider policy |
|---|---|---|---|
passkey_question_v1 | passkey_question_auth | Image points + passkey | passkey_and_image_points (default) |
behavior_passkey_v1 | behavior_auth_passkey | Motion gesture + passkey | passkey_and_behavior |
passkey_only_v1 | passkey_only_auth | Passkey only | passkey_only, or opportunistically alongside any other scheme |
agent_keypair_v1 | agent_keypair_auth | Raw ECDSA-P256 keypair (no WebAuthn) | Agent personas only |
Clients never send factorType or circuitType on /v1/verify — the server rejects those keys to prevent verifier downgrade or factor-claim swap.
The ceremony pipeline is always enroll → challenge → verify:
See Enrollment & verification for each step.
Legacy
/v1/factors*endpoints are deliberately disabled and return an error pointing at/v1/enrollments. There is no per-factor record on the server side, and no endpoint lists a persona's enrolled schemes — the on-device SDK is the only authoritative source for "what's enrolled on this device".
Errors
Every non-2xx response carries a machine-readable code and a human-readable message:
Classify failures by code, not by message text. Rate-limit responses add a retryAfter field (seconds).
| Code | Status | Condition |
|---|---|---|
VALIDATION_ERROR | 400 | Malformed request, failed input validation, or a value outside your provider's configuration (for example a callbackUrl origin not on your allowlist) |
INVALID_PROOF | 400 | ZK proof failed verification — distinct from VALIDATION_ERROR so clients can tell "your proof didn't verify" from "your request was bad" |
UNAUTHORIZED | 401 | Missing or invalid session token or API key |
FORBIDDEN | 403 | Scope, ownership, or session-binding mismatch — including agent personas when your policy blocks agents |
NOT_FOUND | 404 | Provider, persona, enrollment, or challenge not found |
CONFLICT | 409 | Conflicting state, such as a nullifier that has already been spent |
PERSONA_ALREADY_ENROLLED | 409 | Bootstrap enrollment attempted for a persona that already has a current enrollment — route the user to authenticate or link instead |
policy_upgrade_required | 409 | Login proved a scheme your current factor combination no longer accepts — see the upgrade ceremony |
CHALLENGE_EXPIRED | 410 | The challenge existed but its TTL elapsed — prompt the user to start again (a genuinely absent challenge returns NOT_FOUND) |
RATE_LIMITED | 429 | IP or provider quota exceeded, or an agent persona exceeding your per-agent budget (retryAfter seconds) |
INTERNAL_ERROR | 5xx | Unhandled server error |
One error deviates from the flat shape: policy_upgrade_required nests its payload under error so its two extra machine-readable fields (factorCombination, allowedSchemes) don't collide with the top-level code/message:
Signing keys
Auth-result JWTs are signed with EdDSA (Ed25519). The public key is published at:
See Auth results for the token's claims and how to verify it.
Next
- Sessions — mint, read, revoke, and poll sessions
- Enrollment & verification — the ceremony pipeline and the policy upgrade
- Auth results — exchange result codes and verify JWTs
- Personas — identity mapping, linking, and unlinking