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.
The session exchange flow
Section titled “The session exchange flow”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:
- Sign in with the Firebase SDK (password, passkey bridge, or GitHub).
POST /v1/auth/exchangewith the Firebase ID token.- Use the returned short-lived session JWT as the bearer on every call; re-exchange when it expires.
POST /v1/auth/exchange
Section titled “POST /v1/auth/exchange”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.
Sign-in support endpoints (anonymous)
Section titled “Sign-in support endpoints (anonymous)”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 |
Session state
Section titled “Session state”| 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} |
Passkeys (WebAuthn)
Section titled “Passkeys (WebAuthn)”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.
Multi-factor authentication
Section titled “Multi-factor authentication”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.
Recovery contacts & account recovery
Section titled “Recovery contacts & account recovery”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.
Which routes are session-only?
Section titled “Which routes are session-only?”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.