Skip to main content
The Calm API has two security schemes. The session endpoint takes a Privy bearer token; every wallet endpoint takes the calm_session cookie.

Schemes

bearerAuth
header
Authorization: Bearer <privy_identity_token> — Privy’s identity-token JWT. Carries the Privy app id in aud and the user’s verified email + wallet(s) in linked_accounts. Only POST /v1/session/{address} accepts this.
calm_session — short-lived (1h) HS256 JWT issued by POST /v1/session/{address}. Sent automatically by the browser; the SDK uses credentials: "include". All /v1/wallets/* endpoints require this.

Per-endpoint matrix

EndpointAuth
POST /v1/session/{address}Bearer (Privy identity token)
GET /v1/wallets/{address}Cookie
POST /v1/wallets/{address}Cookie
POST /v1/wallets/{address}/terms-of-serviceCookie
POST /v1/wallets/{address}/identity-verificationCookie
POST /v1/wallets/{address}/virtual-accountCookie
GET /v1/wallets/{address}/virtual-accountCookie

Refresh

There’s no separate refresh endpoint. To extend an expiring session, call POST /v1/session/{address} again with a fresh Privy identity token — the cookie is overwritten.

Address binding

Every URL {address} is cross-checked against the auth artifact:
  • With bearer: the address must appear in the JWT’s linked_accounts as a wallet, else wallet_not_linked (403).
  • With cookie: the address must equal the cookie’s bound wallet, else wallet_token_mismatch (403). Defense-in-depth — a stolen cookie can’t be replayed against a different wallet’s URL.

Errors you’ll see at the auth layer

CodeStatusWhen
token_required401Missing Authorization: Bearer … on POST /v1/session/{address}.
invalid_token401Privy JWT signature/claims invalid, malformed, or missing aud.
token_expired401Privy JWT past its exp.
wallet_not_linked403URL {address} isn’t in the Privy user’s linked_accounts.
session_required401Cookie missing on a wallet route.
session_invalid401Cookie failed verification or expired.
wallet_token_mismatch403URL {address} doesn’t match the cookie’s bound wallet.
See Errors for the full table including non-auth codes.