Error codes
Every error response from Driftstack is RFC 7807
application/problem+json. The stable identifier is
the type URI; dispatch your client logic on
type, not on the HTTP status or the human-readable
title / detail.
Response shape
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/problem+json
{
"type": "https://errors.driftstack.dev/rate-limited",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit for \"sessions:create\" exceeded for tier \"solo_manual\".",
"retry_after_seconds": 12
}
Required fields: type, title,
status. Optional: detail (human
message), instance (request id when present), and
problem-specific extensions (flat keys on the top-level, per RFC
7807 §3.2). Always check type, not the HTTP status
alone — the same status can correspond to multiple types
(e.g. 429 covers both rate-limited and
concurrency-limit; 401 covers six distinct types).
Reference
The full URI is https://errors.driftstack.dev/<slug>
where <slug> is the value in the first column.
Slugs are stable; they will not be renamed without a deprecation
window.
| Type slug | HTTP | When | Action |
|---|---|---|---|
bad-request | 400 | Generic invariant violation (e.g. capacity ≤ 0, malformed URL). | Read `detail`; the field at fault is named. |
validation-failed | 400 | Request body / params failed Zod schema validation. | Fix the field listed in the response detail. |
unauthorized | 401 | Authorization header missing or malformed. | Send `Authorization: Bearer ds_live_…` on every request. |
invalid-key | 401 | API key is malformed (wrong prefix) or unknown. | Confirm the key in your secret manager matches the dashboard. |
revoked-key | 401 | API key was revoked (manually or via rotation grace expiry). | Mint a new key from the dashboard. |
expired-key | 401 | API key passed its `expires_at` (set by rotation). | Roll to the post-rotation key; old key auto-expires after the 24h grace window. |
invalid-auth-token | 401 | Signup-verification / password-reset / magic-link token was malformed, consumed, or expired. | Request a fresh token via the dashboard. |
email-not-verified | 401 | Login attempted before the user verified their signup email. | Click the verify link in the welcome email, or use Resend verification email on /verify-email. |
invalid-credentials | 401 | Login failed (wrong password) OR account does not exist — both surface the same shape on purpose (no enumeration). | Verify credentials; do not retry-loop. |
forbidden | 403 | Key is valid but lacks the required scope for this endpoint. | Mint a key with the right scope (see /docs/api-keys). |
mfa-step-up-required | 403 | Endpoint requires a fresh MFA step-up confirmation. Extension `requires_mfa_step_up: true`. | POST a 6-digit code to /v1/auth/mfa/step-up, then retry the original request. |
legal-acceptance-required | 403 | A new ToS / Privacy revision hasn't been accepted by the calling account. | POST to /v1/account/legal/accept with the current revision id. |
not-found | 404 | Resource id does not exist OR you do not have access (both surface as 404; no enumeration). | Verify the id; cross-account lookups return 404 by design. |
conflict | 409 | State conflict (duplicate, sticky tombstone, idempotency-key replay with different body). | Read `detail` — often "mint a fresh resource instead". |
concurrency-limit | 429 | Account has hit its tier-bound concurrent-session cap. Extensions `current_sessions` + `limit`. | Wait for a running session to finish, or upgrade the tier. See /docs/concurrency. |
tier-limit | 429 | Tier cap reached on a non-session resource (e.g. profile count, API keys per account). | Upgrade tier or delete unused resources. |
rate-limited | 429 | A rate-limit bucket is empty. Extension `retry_after_seconds`; same value as `Retry-After` header. | Honour Retry-After. See /docs/rate-limits. |
session-destroyed | 410 | Tried to operate on a session that's already been destroyed. | Treat the session as gone; create a new one. |
session-timeout | 504 | A blocking session operation (e.g. wait-for-selector) ran past its timeout. | Increase the timeout if legitimate, or address the upstream slowness. |
internal | 500 | Driftstack hit an unexpected error processing the request. | Retry with exponential backoff. If persistent, email support with X-Request-Id. |
driver-error | 502 | The underlying browser driver returned a structured failure. | See `detail` for the driver-side cause. Often retryable. |
driver-not-integrated | 503 | Endpoint hit a driver path that hasn't been wired up yet. | Not retryable; contact support if a documented path lands here. |
feature-unavailable | 503 | Feature explicitly disabled at deploy time (e.g. R2 bucket unconfigured for avatar upload). | Not retryable; the operator must wire the missing dependency. |
byok-anthropic-required | 502 | Agent-sessions message turn cannot resolve an Anthropic API key. BYOK-for-v1.0 means the customer must supply their own key. | PUT /v1/account/me/byok-anthropic-key to set a stored key, or supply x-byok-anthropic-api-key on each request header. |
bundled-llm-budget-exhausted | 402 | Bundled-LLM monthly soft-cap reached on this account (default $20; configurable via PATCH /v1/account/me/bundled-llm-settings). Resets at calendar month start. | Raise the cap, wait until next month, or supply a BYOK Anthropic key (BYOK always wins per Q4=A). |
bundled-llm-consent-required | 402 | Deployment is wired for bundled-LLM but the customer has not yet ticked the consent flag. Distinct from byok-anthropic-required so dashboards can render a one-click "enable bundled-LLM" CTA. | PATCH /v1/account/me/bundled-llm-settings with bundled_llm_consent: true. |
pair-mode-conflict | 409 | Pair-mode takeover lost the SET-NX-EX race against another participant. Extension carries winner_client_id so the loser dashboard can render "user X is taking over". | Re-fetch the session; do not retry the takeover unless the winner releases the lock. |
pair-mode-invalid-transition | 409 | Pair-mode state machine refused the requested transition (e.g. a handback request before any takeover-grant). Extensions `from` + `transition`. | Re-fetch the pair-mode state; do not retry the transition without resolving the state mismatch. |
email-already-registered | 409 | Signup attempted for an email that already has an account. | Direct the user to /login or to the password-reset flow. |
Stability
The type URI slugs listed above are part of the API
contract — they will not be renamed or removed without a
deprecation window. New types may be added; defensive clients
should treat unknown types as internal
(i.e. retry with backoff and surface detail to the
operator).
OAuth token endpoint
Errors from the OAuth token endpoint follow the standard problem
shape (the server maps OAuth 2.0 errors via
UnauthorizedError / BadRequestError):
invalid_client/unauthorized_client→typeisunauthorized.invalid_request/invalid_scope/invalid_grant/access_denied→typeisbad-request.
Status outages
For platform-level outages (everyone hits 5xx), check status.driftstack.dev. Subscribe to email notifications from the same page if you'd like to be told before your dashboard refreshes.
Support
Persistent or surprising errors:
[email protected].
Include the X-Request-Id header from the response —
every Driftstack response carries one, and we use it to find the
corresponding server logs.