Get started
Errors & limits
Every 4xx/5xx response is application/problem+json with a stable string code. Branch on the code — never on the human-readable title or detail.
Error codes
Every 4xx/5xx response carries application/problem+json with a stable string code extension. Branch on the code — never on title or detail strings.
| Code | HTTP | Meaning | Remediation |
|---|---|---|---|
| auth.unauthorized | 401 | Unauthorized The request did not include valid credentials. | Send an `Authorization: Bearer <jwt>` header, or `X-Api-Key: pa_…` (secret) / `X-Api-Key: pk_…` (publishable) for an API key. |
| auth.forbidden | 403 | Forbidden The credentials are valid but lack permission for this operation. | Confirm the user owns the resource or has the required role. |
| auth.invalid_credentials | 401 | Invalid credentials Email/password combination did not match any account. | Verify the credentials. Repeated failures are throttled by the per-IP login limit. |
| auth.api_key_invalid | 401 | API key invalid The provided API key did not match a known prefix, or its hash did not validate. | Re-copy the key from the API keys page. If you regenerated the key, the old value is permanently invalid. |
| auth.api_key_disabled | 401 | API key disabled The API key has been disabled by an administrator. | Regenerate the key from the persona's settings, or contact support if the disable was unexpected. |
| auth.publishable_key_forbidden | 403 | Publishable key not allowed Publishable keys (pk_…) may only call chat, search, widget meta, subscription limits, and widget session start. This endpoint requires the persona's secret key. | Call this endpoint server-side with the secret key (pa_…). Never embed the secret key in public HTML. |
| auth.not_owner | 403 | Not the resource owner The authenticated account is not the owner of the requested resource. | Log out and sign in with the account that owns this resource. |
| auth.origin_not_allowed | 403 | Origin not allowed The request originated from a domain that is not in the account's allowlist. | Add the calling domain to the account's allowed origins, or contact support. |
| auth.consent_required | 403 | Consent required The end user has not granted the consent required for this operation. | Prompt the end user to complete the consent flow before retrying. |
| auth.user_not_initialized | 403 | User not initialized The authenticated account is missing required setup data. | Complete account onboarding (profile, billing, etc.) and retry. |
| limits.rate_exceeded | 429 | Rate limit exceeded Too many requests. Check the `Retry-After` header for the recommended wait time. The `extensions.scope` field hints at the partition (`user`, `ip`, `session`, `api_key_slot`). | Honor `Retry-After`. If `scope` is `api_key_slot`, switching to the other key slot may unblock immediately. |
| limits.concurrent_session | 429 | Concurrent session limit The account already has the maximum number of active sessions. | Close another active session or upgrade the subscription tier. |
| limits.waiting_room_full | 503 | Waiting room full The waiting room queue is at capacity. | Back off and retry after a short delay. |
| limits.quota_exceeded | 402 | Quota exceeded The account has consumed its allotted quota (minutes, knowledge units, storage, etc.). | Upgrade the subscription tier or wait for the next billing cycle. |
| limits.subscription_required | 402 | Subscription required This operation requires an active paid subscription. | Purchase a subscription on the pricing page. |
| limits.session_time_limit | 409 | Session time-of-day limit Sessions cannot start at the current time per the persona's schedule. | Retry within the persona's configured availability window. |
| limits.session_interaction_limit | 429 | Session interaction limit (WebSocket close 4005) Returned only as WebSocket close 4005, never as HTTP. The live session reached the persona's per-session interaction cap (PublishSettings.SessionInteractionLimit, counted in billing units — Full mode = 2 per turn, Chat mode = 1). | End-user UI should show a session-ended message; starting a new session is allowed (subject to cross-session limits). |
| limits.session_duration_limit | 409 | Session duration limit (WebSocket close 4006) Returned only as WebSocket close 4006, never as HTTP. The live session exceeded the persona's per-session duration cap (PublishSettings.SessionDurationLimitSeconds, wall-clock — includes Standby/idle time). | End-user UI should show a session-ended message; starting a new session is allowed (subject to cross-session limits). |
| upload.file_too_large | 413 | File too large The uploaded file exceeds the per-endpoint size limit. | Compress the file or split it. See the endpoint reference for per-endpoint size caps. |
| upload.invalid_signature | 400 | Invalid file signature The file content does not match the declared content type (e.g. an `.exe` renamed to `.pdf`). | Verify the file is genuinely the type its extension claims. |
| upload.unsupported_type | 415 | Unsupported file type The file's content type is not accepted by this endpoint. | Convert the file to one of the supported types listed in the endpoint reference. |
| upload.page_limit_exceeded | 413 | Page limit exceeded The document exceeds the maximum page count for ingestion. | Split the document into smaller files. |
| resource.not_found | 404 | Resource not found No resource exists at the addressed URL, or it is not visible to the caller. | Verify the ID, then the caller's permissions. |
| resource.conflict | 409 | Resource conflict The operation conflicts with the resource's current state (duplicate, version skew, etc.). | Re-read the resource and retry with current state. |
| validation.failed | 400 | Validation failed One or more request fields failed validation. The `errors` extension contains per-field details. | Inspect `extensions.errors` and correct the highlighted fields. |
| server.error | 500 | Server error An unexpected server-side error occurred. | Retry with backoff. If the failure persists, contact support with the response's `traceId` extension if present. |
Rate limits
When a limit is exceeded the response is 429 Too Many Requests with a Retry-After header (seconds) and ProblemDetails body code = limits.rate_exceeded. The extensions.scope field tells you whether the partition was per-user, per-IP, or per-API-key-slot.
| Policy | Limit | Window | Partition | Purpose |
|---|---|---|---|---|
| AuthSensitive | 5 | 30 seconds | per IP scope: ip | Login, register, password reset, email confirmation. Strict per-IP limit so credential-stuffing campaigns from a single source get throttled fast. |
| AuthRefresh | 30 | 1 minute | per IP scope: ip | Refresh-token exchange. Looser than AuthSensitive but still per-IP to bound rotation abuse. |
| PersonaUploads | 20 | 1 minute | persona_or_user_or_ip scope: persona | Persona texture/asset uploads. Per-user when authenticated, per-IP otherwise. |
| KnowledgeUploadsRate internal services bypass | 60 | 1 minute | persona_or_user_or_ip scope: persona | Knowledge document uploads (PDF/DOCX/etc.). Limit is sourced from KnowledgeAcceptorSettings.RateLimit so the same knob controls accept-time check and throttle. |
| SubscriptionChange | 5 | 10 minutes | persona_or_user_or_ip scope: persona | Subscription tier changes and cancellations. Tight to prevent oscillation abuse. |
| MutationDefault internal services bypass | 300 | 1 minute | persona_or_user_or_ip scope: persona | Generic per-user safety net for state-changing endpoints (POST/PUT/PATCH/DELETE) without a stricter category-specific policy. Loose by design — never bites a real user, catches scripted abuse. |
| Global internal services bypass | 300 | 1 minute | per IP scope: ip | Per-IP safety net applied to every request. Loose by design — never bites a real user, catches scripted abuse on untagged endpoints. Stricter category-specific policies apply on top. |
| SessionSnapshot | 30 | 1 minute | per session scope: session | Snapshot uploads per active session (matches expected camera-capture cadence with headroom). Partitioned by sessionId. |
| SessionRead | 240 | 1 minute | per session scope: session | Per-session image / snapshot reads. Looser than upload — the same client may pull many cached assets. |
| SessionWebSocket | 30 | 1 minute | per IP scope: ip | WebSocket upgrade attempts per source IP. Bounds concurrent-connection abuse before a session is fully established. |
| V1Search | 120 | 1 minute | api_key scope: api_key | Knowledge Search (POST /v1/search) per persona API key. Quota (0.5 interaction per call) bites first for sustained traffic; this bounds bursts. |
| V1Chat | 60 | 1 minute | api_key scope: api_key | Headless chat (POST /v1/chat) per persona API key. Quota (1 interaction per call) and the per-session turn lock bite first for sustained traffic; this bounds bursts. |
| WaitingRoomQueue | 30 | 1 minute | per user (per IP if anonymous) scope: user | Queue join / finalize / cancel actions. Per-user when authenticated, per-IP for anonymous guests so shared egress (corporate proxy, mobile CGN) doesn't share a bucket between legitimate users. |
| WaitingRoomQueueWs | 60 | 1 minute | per IP scope: ip | WebSocket upgrade attempts for queue position polling. Per-IP so one host can't open thousands of long-lived sockets to exhaust capacity. |