Skip to content

Authentication

Every Knobs API call carries Authorization: Bearer <token>. Two bearer types reach the user-facing surface:

Bearer Looks like Who uses it Reach
Session JWT opaque JWT the Knobs web/iOS apps (and anything driving a full sign-in) everything, including credential management
Personal access token knak_<keyId>_<secret> scripts, CLIs, integrations acting as you the whole product surface, except credential-management routes

A third bearer, the integration bot token knbi_…, is valid only on /v1/bot/* — see Integrations & webhooks.

If you just want to call the API programmatically, use an API key — mint one in Settings → Security and skip straight to the product pages. See API keys for the full lifecycle. The rest of this page documents the session flow and the account-security endpoints, which matter if you’re building a client that signs users in.

Knobs identity is backed by Firebase Auth, but the API never accepts a Firebase ID token directly (with one step-up exception below). Clients sign in with Firebase, then exchange:

  1. Sign in with the Firebase SDK (password, passkey bridge, or GitHub).
  2. POST /v1/auth/exchange with the Firebase ID token.
  3. Use the returned short-lived session JWT as the bearer on every call; re-exchange when it expires.
Terminal window
curl -X POST https://api.knobs.io/v1/auth/exchange \
-H "Content-Type: application/json" \
-d '{"idToken": "<firebase-id-token>"}'
{ "token": "eyJhbGciOi…", "expiresAt": "2026-07-16T18:04:05Z" }

New accounts carry an onboarding: "pending" claim until they have a workspace and a verified recovery contact; pending sessions get 403 onboarding_required outside the onboarding endpoints. Re-exchange after finishing onboarding to clear it.

These endpoints are unauthenticated and rate-limited per IP (and per identifier).

Endpoint Description
POST /v1/auth/identifier:resolve {identifier}{email, signInMethods} — maps any of your active Knobs addresses to the canonical account email. Echoes the input on a miss (no account-existence oracle)
POST /v1/auth/signup Username-first signup {subdomain, localPart, firstName, lastName, password}{email, uid} — creates the account and its workspace
Endpoint Description
GET /v1/users/me Current identity: {uid, email, secondFactor, amr, onboarding}
GET /v1/me Alias of the above
GET /v1/users/me/onboarding Onboarding-gate state: {pending, hasWorkspace, hasVerifiedContact, contacts}

Registration attaches a passkey to a signed-in account (session-only). Sign-in is usernameless: the finish-login call returns a Firebase custom token your client redeems with signInWithCustomToken, then proceeds through the normal exchange.

Endpoint Auth Description
POST /v1/auth/passkeys:begin-register session WebAuthn creation options + sessionId
POST /v1/auth/passkeys:finish-register session {sessionId, name, credential} → stored passkey
POST /v1/auth/passkeys:begin-login anonymous discoverable assertion options + sessionId
POST /v1/auth/passkeys:finish-login anonymous {sessionId, credential}{customToken, uid}
GET /v1/auth/passkeys session list my passkeys
DELETE /v1/auth/passkeys/{cid} session remove a passkey (cid = base64url credential id)

Passkey login errors are deliberately opaque (401) on the anonymous routes.

MFA enrollment (TOTP or SMS) happens client-side through the Firebase SDK — there is no enrollment REST route. The API manages enrolled factors and runs step-up:

Endpoint Auth Description
GET /v1/users/me/mfa session list enrolled second factors
DELETE /v1/users/me/mfa/{factorId} session unenroll a factor (revokes refresh tokens)
POST /v1/auth/mfa:challenge session step-up: {idToken} from a fresh second-factor re-auth → {elevated, elevatedUntil, secondFactor}

mfa:challenge is the one place besides /v1/auth/exchange that accepts a Firebase ID token.

Account recovery is Knobs-native (a code sent to a verified personal contact, never a Firebase reset email — that email would land in the locked-out Knobs mailbox).

Contact management (session-only; available pre-onboarding):

Endpoint Description
GET /v1/users/me/recovery-contacts list my recovery contacts
POST /v1/users/me/recovery-contacts add {channel: "email"|"sms", email?|phone?} → sends a verification code
POST /v1/users/me/recovery-contacts/{cid}/send-code resend the code (rate-limited)
POST /v1/users/me/recovery-contacts/{cid}/verify {code} → verified; response includes reExchange: true — re-exchange to lift the onboarding gate
DELETE /v1/users/me/recovery-contacts/{cid} remove a contact (never the last verified one)

Recovery itself (anonymous, rate-limited):

Endpoint Description
POST /v1/auth/recovery:begin {identifier}{recoveryId, contacts: [{id, channel, hint}]} — masked contacts; identical empty 200 for unknown identifiers
POST /v1/auth/recovery:send-code {recoveryId, contactId} → one-time code
POST /v1/auth/recovery:complete {recoveryId, code, newPassword} → password reset + refresh-token revocation

Recovery sessions are single-use, expire in 15 minutes, and allow 5 code attempts.

API keys (knak_…) work everywhere except credential management: the API-key routes themselves, passkeys, MFA, and recovery contacts. Those reject API keys with 401 api_key_not_allowed — a leaked key can never mint, list, or revoke credentials. See API keys.