Error codes
Every Culprit API error response has the shape:
{
"error": {
"type": "authentication_error",
"code": "token_invalid",
"message": "...",
"doc_url": "https://docs.theculprit.ai/api/errors#token_invalid"
}
}Switch on error.code, never on the human error.message. The doc_url field links directly to the anchor below.
| Code | Type | Status | Meaning |
|---|---|---|---|
| missing_authorization | authentication_error | 401 | No `Authorization` header was sent. Fix: Add `Authorization: Bearer culprit_live_...` to the request. |
| invalid_authorization_scheme | authentication_error | 401 | The `Authorization` header was present but did not use the `Bearer` scheme. Fix: Use `Authorization: Bearer <token>` — Basic, Digest, and other schemes are not supported. |
| token_invalid | authentication_error | 401 | The bearer token is malformed, revoked, or its creator was deleted. The same code is returned for all four cases (malformed shape, no matching prefix, hash mismatch, orphaned by user deletion) to avoid leaking which. Fix: Mint a new token at /settings/api-tokens. |
| unauthenticated | authentication_error | 401 | A downstream Postgres RPC reported an unauthenticated session. Should never reach the wire — the auth middleware guarantees a JWT — and is included as a defense-in-depth surface. Fix: Retry; if it persists contact support@theculprit.ai with the request id. |
| token_scope_insufficient | permission_error | 403 | A read-only token attempted a mutating request (POST / PATCH / PUT / DELETE). Fix: Use a read-write token. Read-only tokens cannot mint other tokens (no scope escalation). |
| forbidden | permission_error | 403 | The authenticated user lacks the workspace capability required for this operation (e.g. only owners can delete services or manage tenant members). Fix: Have a workspace owner perform the action, or have your role elevated. See /concepts/auth-and-scopes for the capability matrix. |
| rate_limit_exceeded | rate_limit_error | 429 | Per-token rate limit (1000 req/min sliding window) exceeded. Fix: Honor the `Retry-After` response header. Different tokens have independent budgets. |
| invite_resend_rate_limited | rate_limit_error | 429 | Resending a tenant invitation more often than the per-invite cooldown allows. Fix: Wait for the cooldown to expire and retry. Honor the `Retry-After` header. |
| param_invalid_format | invalid_request_error | 400 | A path, query, or body parameter did not match the operation's schema. The `param` field on the error body names the offending key. Fix: See the `param` field in the error response and fix the offending value. |
| id_malformed | invalid_request_error | 400 | A resource id in the URL path was not in the expected `<resource>_<base32>` shape. Fix: Always use the `id` value returned by the API — never hand-construct ids. See /concepts/ids-and-times. |
| limit_invalid | invalid_request_error | 400 | The `limit` query parameter was outside the allowed range for the endpoint. Fix: Use a `limit` between 1 and 100 (default: 25). |
| cursor_malformed | invalid_request_error | 400 | The `cursor` query parameter is malformed, expired, or from a different sort key. Fix: Always use the `next_cursor` value from the previous response — never construct cursors by hand. |
| no_fields_to_update | invalid_request_error | 400 | A PATCH request was sent with an empty body, so there is nothing to update. Fix: Send at least one mutable field in the request body. |
| pattern_invalid | invalid_request_error | 400 | A submitted PII regex pattern failed the re2js compile gate (linear-time safety check). Fix: Adjust the pattern to be RE2-compatible (no backreferences, no lookaround). See /concepts/redos-safety. |
| service_name_in_use | invalid_request_error | 400 | A service with this name already exists in this workspace. Fix: Pick a different name; service names must be unique per workspace. |
| api_token_name_in_use | invalid_request_error | 400 | An API token with this name already exists in this workspace. Fix: Pick a different name; API token names must be unique per workspace. |
| dispatch_target_name_in_use | invalid_request_error | 400 | A dispatch target with this name already exists in this workspace. Fix: Pick a different name; dispatch target names must be unique per workspace. |
| pii_pattern_name_in_use | invalid_request_error | 400 | A PII pattern with this name already exists in this workspace. Fix: Pick a different name; PII pattern names must be unique per workspace. |
| duplicate_pending | invalid_request_error | 400 | A pending invitation for this email address already exists in this workspace. Fix: Resend or revoke the existing invitation instead of creating a new one. |
| already_member | invalid_request_error | 400 | The invited email belongs to a user who is already a member of this workspace. Fix: Promote / demote the existing member via the tenant_members API instead of inviting them again. |
| invite_already_accepted | invalid_request_error | 400 | This invitation was already accepted and cannot be acted on again. Fix: No action required — the invitee is now a tenant member. |
| invite_revoked | invalid_request_error | 400 | This invitation was revoked and cannot be resent. Fix: Create a fresh invitation instead. |
| cannot_change_self | invalid_request_error | 400 | You cannot change your own role via the API — guard against self-lockout. Fix: Have another owner change your role. |
| cannot_remove_self | invalid_request_error | 400 | You cannot remove yourself via the API — guard against self-lockout. Fix: Have another owner remove you, or transfer your work and have them remove your seat. |
| cannot_promote_to_owner | invalid_request_error | 400 | Promotion to `owner` must go through the dedicated transfer-ownership flow, not a generic role update. Fix: Use the ownership-transfer flow in /settings/team rather than PATCHing the member role. |
| last_owner | invalid_request_error | 400 | The action would leave the workspace with zero owners (e.g. removing or demoting the last owner). Fix: Promote another member to owner first, then retry. |
| idempotency_key_reused | idempotency_error | 422 | An `Idempotency-Key` was reused with a different request body. Fix: Use a fresh idempotency key when the body changes; reusing a key is only valid for retrying the IDENTICAL request. |
| route_not_found | not_found_error | 404 | The path/method combination is not part of the API surface. Fix: Check /api for the canonical endpoint list. |
| service_not_found | not_found_error | 404 | No service with this id exists in your workspace. Returned with status 400 when supplied as a foreign key on another resource (e.g. notification rule), and 404 when fetching `/v1/services/<id>` directly. Fix: Verify the id, or list /v1/services to find the right one. |
| incident_not_found | not_found_error | 404 | No incident with this id exists in your workspace. Fix: Verify the id, or list /v1/incidents to find the right one. |
| audit_event_not_found | not_found_error | 404 | No audit event with this id exists in your workspace. Fix: Verify the id, or list /v1/audit_events to find the right one. |
| api_token_not_found | not_found_error | 404 | No API token with this id exists in your workspace. Fix: Verify the id, or list /v1/api_tokens to find the right one. |
| dispatch_target_not_found | not_found_error | 404 | No dispatch target with this id exists in your workspace. Fix: Verify the id, or list /v1/dispatch_targets to find the right one. |
| notification_rule_not_found | not_found_error | 404 | No notification rule with this id exists in your workspace. Fix: Verify the id, or list /v1/notification_rules to find the right one. |
| pii_pattern_not_found | not_found_error | 404 | No PII pattern with this id exists in your workspace. Fix: Verify the id, or list /v1/pii_patterns to find the right one. |
| tenant_member_not_found | not_found_error | 404 | No tenant member with this id exists in your workspace. Fix: Verify the id, or list /v1/tenant_members to find the right one. |
| tenant_invitation_not_found | not_found_error | 404 | No tenant invitation with this id exists in your workspace. Fix: Verify the id, or list /v1/tenant_invitations to find the right one. |
| tenant_not_found | not_found_error | 404 | The tenant context for this token could not be resolved. Fix: Mint a fresh token; if the issue persists contact support@theculprit.ai. |
| internal_error | api_error | 500 | Something failed on Culprit's side and was logged. Fix: Retry with backoff. If the error persists, contact support@theculprit.ai with the request id. |
| email_send_failed | api_error | 500 | A transactional email (e.g. tenant invitation, invite resend) could not be delivered to Resend. Fix: Retry the operation; persistent failures usually indicate a Resend outage — check status.resend.com and contact support@theculprit.ai if it persists. |