CrydenSync
HTTP API

Error Codes

Every code value httpapi/errors.go's mapError can return, its HTTP status, and what it means. Branch on code, never on message.

Every error response is {"error": {"code": "...", "message": "..."}}. Every code below comes from mapError, with exactly two exceptions written directly: bad_request (request parsing, before any engine call) and db_unreachable (/v1/health only).

Authentication and authorization

HTTPCodeMeaning
401missing_auth_headerNo Authorization header, or it isn't Bearer ...
401invalid_access_tokenAccess token invalid or expired
401invalid_tokenRefresh token invalid
401token_reusedReuse detected — the whole session family is already revoked server-side. Discard every stored token and force a full login; retrying is pointless.
403not_operatorThe token is valid but carries no role: "admin" claim
501not_implemented_on_sqliteThis deployment runs on SQLite — the entire admin console is unavailable. Returned before the token is even examined, so it can't be confused with not_operator.
429rate_limitedEither limiter — the engine's own per-user limiter or this API's per-IP edge limiter both surface this same code
401invalid_api_keyMapped, but no endpoint currently authenticates a request by API key, so nothing returns it yet — see Endpoints

Account and signup

HTTPCode
401invalid_credentials
409user_exists
403account_locked
400password_policy_violation (carries error.details, an array of stable rule codes — the only error with a details array)
400password_breached
400verification_token_invalid
400verification_token_expired
403session_not_owned
404not_found

Second factor

HTTPCode
400totp_not_enabled
409totp_already_enabled
401invalid_totp_code
401invalid_pending_login
400no_passkeys_enrolled
401invalid_passkey_response
400invalid_ceremony_token
404totp_not_configured
404passkeys_not_configured
404magic_link_not_configured
404recovery_codes_not_configured

API keys

HTTPCode
404api_key_not_found — an unowned key, a nonexistent key, and an already-revoked key all answer this identically, so a caller can never learn whether someone else's key exists
400invalid_api_key_scope
400invalid_api_key_ttl
404api_keys_not_configured

OAuth

HTTPCode
404oauth_provider_not_configured
400oauth_state_mismatch
400oauth_email_not_available
400oauth_identity_verification_failed
500oauth_link_not_configured
400oauth_link_session_missing
409oauth_email_conflict — see OAuth for how to resolve it
409oauth_identity_already_linked

Metadata, review, and settings

HTTPCode
400reserved_metadata_key
400invalid_metadata_key
404metadata_key_not_found
404audit_event_not_found
400invalid_review_status
400invalid_review_note
400invalid_llm_provider
400invalid_database_provider
400invalid_ask_ai_widget
400database_role_not_read_only — the supplied database credential could write; refused rather than stored
400database_role_unverified — the write-check itself failed to reach a conclusion (timeout, connection error); treated as a failure, never as a pass
409setting_undecryptable — the row exists but can't be decrypted with the current SETTINGS_ENCRYPTION_KEY, most likely because that key changed since it was written; clearing the setting still works without the old key

The Ask-AI widget

HTTPCode
404not_configured
404ask_ai_widget_disabled
403origin_not_allowed
400invalid_question (empty, or over 500 characters)
400question_not_answerable — covers two different internal causes (a scope the deployment configured, or the engine's own fail-closed default for an entity it won't bind to one user) and deliberately never says which, since naming either would describe this deployment's schema to whoever is asking

Generic

HTTPCodeNotes
400bad_requestRequest parsing only, before any engine call
404not_configuredHeavily overloaded on purpose — the three AI settings endpoints, the ask-ai widget, and the digest/webhook/logging history endpoints all use this same code when their underlying feature is simply off. The convention throughout this API: a feature the deployment hasn't enabled answers 404, never 500, so a client can hide the option instead of reporting a fault.
503db_unreachable/v1/health only
500internal_errorAnything unmapped. Deliberately vague — the real error is logged server-side, never echoed to the caller

Three codes worth building specific client behavior around

token_reused — the session family is already gone. Clear every locally stored token and force a fresh login; there is no other token from that login that will still work.

account_locked — unlike invalid_credentials, this confirms the account exists. Accepted as a deliberate tradeoff for clearer lockout messaging; don't build a UI that treats this as equivalent to a wrong password.

not_operator vs not_implemented_on_sqlite — both mean "this admin request didn't work," but they mean different things. The second is returned before the token is even checked, so it can never be confused with the first: not_operator says this caller lacks access on a deployment that has a console; not_implemented_on_sqlite says the deployment has no console at all, regardless of who's asking.