# Sigma Auth (auth.sigmaidentity.com) > OAuth 2.1 + OpenID Connect authorization server where people and AI agents authenticate with Bitcoin (BAP) identity keys instead of passwords. The server verifies signatures and never holds user private keys. Identity keys are created client-side: in the browser at `https://staging.sigmaidentity.com/signup`, or locally with `@sigma-auth/cli` (`bunx @sigma-auth/cli`) or `bsv-bap` / `@bsv/sdk`, stored as `bitcoin-backup` ciphertext. This server is the recover / OAuth / approval layer for those identities — it does not create keys and does not offer agent self-signup as a product path. ## When to use this service Use https://staging.sigmaidentity.com when you need to: - Authenticate a user (or yourself, as a key-holding agent) against applications that accept Sigma Identity — standard OAuth 2.1 authorization code + PKCE. - Obtain tokens on behalf of a human via the Device Authorization Grant (RFC 8628). **Verify availability first**: check `grant_types_supported` in `https://staging.sigmaidentity.com/.well-known/oauth-authorization-server` — the deployed metadata is authoritative over this document. - Register an OAuth client programmatically via Dynamic Client Registration (RFC 7591): POST `/api/auth/oauth2/register` — no session required. - Authenticate an already-enrolled, agent-held BAP identity without a browser wallet — see "Sign in with an agent-held BAP key". Local key creation alone does not enroll an identity. - Resolve BAP identities, verify Bitcoin signatures, diagnose a BAP id (registered vs published), or check NFT-based subscription tiers via Agent Auth capabilities (see `/.well-known/agent-configuration`). Headless diagnosis without an Agent Auth JWT: `bunx @sigma-auth/cli diagnose bap --bap-id --json`. Do not use this domain for product documentation or integration guides — those live at https://sigmaidentity.com/docs. This server will never sign anything with a user's key: it does not have them. ## Discovery The well-known documents below are the source of truth for what is enabled on this deployment; when this guide and the metadata disagree, trust the metadata. - Developer and agent portal (quickstart, DCR sandbox on this host, versioning policy): https://staging.sigmaidentity.com/developers - OpenAPI schema (Better Auth surface under `/api/auth`; deployment-specific routes are documented in this file): https://staging.sigmaidentity.com/openapi.json - API catalog (RFC 9727): https://staging.sigmaidentity.com/.well-known/api-catalog - OAuth 2.0 authorization server metadata (RFC 8414): https://staging.sigmaidentity.com/.well-known/oauth-authorization-server - OpenID Connect discovery: https://staging.sigmaidentity.com/.well-known/openid-configuration - OAuth protected resource metadata (RFC 9728): https://staging.sigmaidentity.com/.well-known/oauth-protected-resource - Agent Auth discovery: https://staging.sigmaidentity.com/.well-known/agent-configuration - Agent skills index: https://staging.sigmaidentity.com/.well-known/agent-skills/index.json - MCP Streamable HTTP (handshake + read-only discovery tools): https://staging.sigmaidentity.com/.well-known/mcp - Interactive API reference: https://staging.sigmaidentity.com/api/auth/reference - Markdown homepage: send `Accept: text/markdown` to `https://staging.sigmaidentity.com/` - XML sitemap: https://staging.sigmaidentity.com/sitemap.xml ## Sign in with an agent-held BAP key A key-holding agent can authenticate here without a human browser wallet. All key material stays with the agent — the server only ever sees public keys and signatures. 1. Use a BAP identity that is already enrolled with Sigma. New identities are enrolled through the explicit browser flow at `https://staging.sigmaidentity.com/signup`; creating a key locally does not register it with this server. Keep the WIF/xprv private; never send it anywhere. If you keep a recovery backup, encrypt it locally as `bitcoin-backup` `.bep` ciphertext. 2. Create the exact compact body `const body = JSON.stringify({ bapId })`, where `bapId` is the enrolled identity key. Do not add fields or reformat the JSON between signing and sending. 3. Build a Bitcoin-Auth token with the `bitcoin-auth` npm package: `getAuthToken({ privateKeyWif, requestPath: "/api/auth/sign-in/sigma", body })`. 4. POST the exact same `body` bytes to `https://staging.sigmaidentity.com/api/auth/sign-in/sigma` with `Content-Type: application/json` and the token in `x-auth-token`. The server loads that exact profile, verifies its active status, checks the signature against `profile.account_pubkey` (using `user.pubkey` only when that exact profile has no account key), and sets a Better Auth session cookie. Unknown identities must use the explicit signup flow; ordinary sign-in never creates or changes an identity. 5. Optionally store a client-side-encrypted backup: POST `https://staging.sigmaidentity.com/api/backup` with `{ "encryptedBackup": "" }`. The server stores the ciphertext blind — encrypt with the `bitcoin-backup` npm package before sending. ## Cloud backup as a service (the recover story) What this server adds over a local-only encrypted backup: upload the ciphertext once, record a linked social account as a **recovery handle**, and recover on any machine — social login, download the blob, unlock it client-side with a passkey (WebAuthn PRF) or password. Same BAP before and after. The server stores and returns ciphertext only; it cannot decrypt backups and cannot recover lost keys — losing both the key and the backup passphrase loses the identity. Endpoints (auth per row; every body is ciphertext the server never reads): - `POST https://staging.sigmaidentity.com/api/backup` — store/replace the encrypted master backup. Auth: session cookie or OAuth Bearer. Body: `{ "encryptedBackup": "" }`. - `GET https://staging.sigmaidentity.com/api/backup` — retrieve. Auth: session (primary BAP), `?bapId=` with Bitcoin-Auth (published identities — see matrix), or `?oauthId=` with a session that owns the linked account. - `GET/POST https://staging.sigmaidentity.com/api/backup/sync?bapId=` — machine-to-machine push and pull with a Bitcoin-Auth token for `/api/backup/sync`. The pubkey is verified against the on-chain BAP overlay; rate-limited (20 req/min). - `GET https://staging.sigmaidentity.com/api/backup/status` — backup metadata (exists, lastUpdated, generation). Auth: OAuth Bearer or session. - `POST https://staging.sigmaidentity.com/api/backup/link-oauth` — record an **already-linked** provider account as a recovery handle for the BAP backup. Auth: OAuth Bearer. Body: `{ "provider", "providerAccountId" }`. The server verifies the account belongs to the token's user in Better Auth's account table and rejects anything else. The account linking itself is Better Auth's job — `authClient.linkSocial()` → `https://staging.sigmaidentity.com/api/auth/link-social`, with OAuth state, callbacks, duplicate-account checks, and `trustedProviders`/`allowDifferentEmails` policy all handled by Better Auth, not custom code. (`POST /api/backup` with `"linkAllOAuthAccounts": true` records every linked account at once.) - `GET https://staging.sigmaidentity.com/api/signer/backup?bapId=` — backup retrieval for the signer iframe (session cookie; browser flows). Social accounts are recovery handles, **not identities**: a linked Twitter/GitHub account exists so you can find your encrypted backup later — including from other devices — nothing more. It is not a login key, it never creates a key, and it is never a rotation path. The OAuth `sub` maps to the existing BAP (`bap_id` claim), and no OAuth login ever creates a parallel identity. Hanging the first recovery handle needs a signed proof with the **current member key** after a fresh wallet unlock — an unpublished BAP is fine; no on-chain publish is required to link. Explicit linking is gated: `authClient.linkSocial()` requires a one-time wallet-proof grant bound to the current session, BAP ID, and exact provider. Recovery matrix (be precise about which path needs what): - Key held, exact Sigma profile enrolled, and overlay status active: sign in via `/api/auth/sign-in/sigma`, then `GET /api/backup` with the session. This path fails closed when the overlay reports the BAP as unpublished, unknown, destroyed, or unavailable. An unpublished or unknown identity cannot currently use Bitcoin-key sign-in. - Key held, BAP published on-chain: the sessionless Bitcoin-Auth paths also work — `GET /api/backup?bapId=` or `GET/POST /api/backup/sync?bapId=`. These specific capabilities verify the pubkey against the on-chain BAP overlay, so they require a published identity by design (the server has no session to consult). - Key lost, social account linked: sign in with the linked provider (browser `/login` social-restore, or OAuth), then `GET /api/backup?oauthId=` and decrypt locally with the backup password or passkey. Recovery returns your **existing** encrypted keys — a social recovery handle never rotates or replaces the BAP key. Key rotation always requires a signature from the current member key; no publish status, social link, or server flow substitutes for it. Current Bitcoin-key sign-in requires the exact enrolled profile **and** an active overlay record; overlay 404/unknown fails closed. Publishing is therefore required for `/api/auth/sign-in/sigma` today. A separate, already-authenticated session may still use session-based backup and recovery-handle features for an unpublished profile where those routes permit it. Publishing is free via https://droplit.dev; it also enables public discovery, proves the **current controller after a key rotation** (so an old stolen key cannot keep claiming the id), and unlocks the overlay-verified sessionless backup paths above. ## Authentication surfaces (complete list) Credentials this server accepts, and how each is established: 1. **Better Auth sessions (cookies)** — established by Bitcoin key sign-in (`/api/auth/sign-in/sigma`), social providers (GitHub, Apple, X/Twitter, Discord), email OTP, or passkeys (WebAuthn); two-factor TOTP is supported. Session-cookie flows are what the browser pages (`/login`, `/signup`, `/device`) use. 2. **OAuth 2.1 bearer tokens** — authorization code + PKCE, refresh tokens; device grant per the RFC 8414 metadata. Discovery: `/.well-known/oauth-authorization-server`. 3. **Bitcoin-Auth signed requests** — a signature over the request path (and body) by a BAP member key, sent as `x-auth-token` or `Authorization: Bitcoin-Auth …`. Used by `/api/auth/sign-in/sigma`, `/api/backup`, and `/api/backup/sync`. 4. **Agent Auth capability JWTs** — short-lived scoped tokens for registered agents (Ed25519). Discovery: `/.well-known/agent-configuration`. The durable subject across all of them is the BAP identity: OAuth tokens carry `bap_id`/`pubkey` claims, Bitcoin-Auth proves a BAP member key directly, and agent capabilities act on BAP-linked data. Key on `bap_id`, not `sub`. BRC-31 and x402 are not served here. ## Agent Auth capabilities Register at `https://staging.sigmaidentity.com/api/auth/agent/register` (Ed25519 keys) and request scoped, short-lived capability JWTs. Public capabilities need no approval; user-scoped capabilities require one-time human approval via device authorization at `https://staging.sigmaidentity.com/agent/approve`. Capabilities: resolve_identity, get_bap_profile, get_oauth_client_info, verify_bitcoin_signature, diagnose_bap_id, list_identities_for_pubkey, get_last_oauth_identity, list_my_identities, list_authorized_apps, check_subscription_tier, verify_nft_ownership, register_oauth_client. Full contract: https://staging.sigmaidentity.com/.well-known/agent-configuration. Same public lookups without a JWT: `bunx @sigma-auth/cli diagnose --help`. ## Error handling API errors are JSON. Unknown API paths return RFC 9457 `application/problem+json` with recovery links. OAuth endpoints return standard OAuth error objects (`error`, `error_description`). Unknown page paths return HTTP 404 — with a markdown body when the request prefers `Accept: text/markdown`. ## Rate limits Baseline 100 requests/min per IP in production, with stricter per-route rules (device token polling: 1 request per 5 seconds — respect the `interval` from the device code response). ## Scopes `openid`, `profile`, `email`, `offline_access`. Declared machine-readably in https://staging.sigmaidentity.com/openapi.json under the `oauth2` security scheme. ## Related - Product docs and guides: https://sigmaidentity.com/docs (llms.txt: https://sigmaidentity.com/llms.txt) - Source: https://github.com/b-open-io/sigma-auth - Integration plugin: https://github.com/b-open-io/better-auth-plugin (`@sigma-auth/better-auth-plugin`)