Errors

The canonical error envelope and the full /v1 error catalog.

Every /v1/* error returns the same JSON shape, regardless of HTTP status. Switch on error.code (stable, machine-readable), never on the human message, which we may rephrase between releases.

Envelope

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": {
      "errors": [
        { "loc": ["body", "name"], "msg": "Field required", "type": "missing" }
      ]
    }
  },
  "request_id": "01HZK3X4P5MQR8AY..."
}
FieldDescription
error.codeStable identifier from the catalog below. Switch on this.
error.messageHuman-readable summary. Safe to display to end users.
error.detailsCode-specific structured data (validation paths, retry hints, etc). Optional.
request_idEchoes the X-Correlation-ID response header. Quote this when you contact support.

Examples

A couple of the codes you’re most likely to hit during integration:

# Missing or wrong scope (403)
$ curl -i https://api.rankprompt.com/v1/brands \
    -H "Authorization: Bearer rp_live_READ_ONLY_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -X POST -d '{"name":"Acme Co"}'
HTTP/1.1 403 Forbidden
{
  "error": {
    "code": "insufficient_scope",
    "message": "This endpoint requires the 'write:brands' scope",
    "details": { "required_scope": "write:brands", "key_scopes": ["read:brands"] }
  },
  "request_id": "01HZK3..."
}

# Cross-tenant brand access (403)
$ curl https://api.rankprompt.com/v1/brands/other-workspace-brand-id \
    -H "Authorization: Bearer rp_live_YOUR_KEY"
{ "error": { "code": "brand_not_authorized", "message": "..." }, "request_id": "..." }

# Quota exhausted (429)
$ curl -i https://api.rankprompt.com/v1/brands -H "Authorization: Bearer rp_live_YOUR_KEY"
HTTP/1.1 429 Too Many Requests
{ "error": { "code": "request_quota_exceeded", "message": "Monthly request quota exceeded" }, "request_id": "..." }

Status code conventions

/v1 follows a small, deliberate set of status codes. Catalog entries are the canonical source; these conventions explain why each maps the way it does:

  • 400 validation_error: caller mistake in the URL or service-layer logical validation: malformed query string, bad path param, exceeded a cap, or invalid state transition. details.errors[*] carries the field-level breakdown when produced by Pydantic.
  • 422 body_validation_error: the JSON body parsed but didn’t match the endpoint’s schema (missing required field, wrong type, range violation, unknown extra field on extra="forbid" schemas). details.errors[*] has the per-field breakdown. This is the RFC 7231 standard for schema-mismatched payloads, so generated SDKs should map it to their “Unprocessable Entity” exception class.
  • 422 invalid_*: the body parsed cleanly but a value is semantically invalid in a way the SDK should be able to branch on (currently only invalid_frequency for unsupported Schedule.frequency values). Treat these like a typed enum violation, not a generic validation failure: the error.code is stable, so SDKs can map it to a typed exception.
  • 409 *_conflict / *_in_flight: request shape is fine, but it conflicts with current state (idempotency replay race, schedule already at cap, etc).
  • 402 insufficient_credits: auth and validation passed, but the call cannot be billed against the customer’s plan.
  • 429 request_quota_exceeded / rate_limit_exceeded: the call was rejected without running. Retryable after the window resets.
  • 5xx: server-side. Safe to retry with the same Idempotency-Key (see the Idempotency page).

Catalog

The full list of codes the /v1 surface can return. New codes will only ever be added; existing codes will not change their HTTP status or stable id.

HTTP Code Message
400 idempotency_key_required This endpoint requires an `Idempotency-Key` header to safely retry on network failures
400 invalid_cursor Pagination cursor is malformed; pass back the value from a previous response unchanged
400 validation_error Request validation failed
401 expired_api_key API key has expired
401 invalid_api_key API key is invalid or revoked
401 unauthorized Missing or invalid credentials
402 insufficient_credits Not enough credits to complete this operation
403 api_subscription_past_due API subscription payment is past due. Update billing to continue.
403 api_subscription_required An active API SKU subscription is required to call this endpoint
403 brand_not_authorized API key is not authorized for this brand
403 browser_calls_forbidden The Public Developer API is server-to-server only. Browser CORS calls are blocked; call /v1 from your backend with a bearer API key.
403 forbidden You do not have permission to access this resource
403 insufficient_scope API key does not have the required scope
404 facts_not_generated Brand facts have not been generated yet; POST to /v1/brands/{id}/facts to create them
404 not_found Resource not found
409 alias_already_accepted Alias is already part of the brand's `brand_aliases` array
409 conflict Resource conflict
409 idempotency_key_in_flight A previous request with this `Idempotency-Key` is still being processed; retry shortly
409 idempotency_key_mismatch An `Idempotency-Key` was reused with a different request body; pick a new key or send the original body
409 report_has_no_prompts Report has no prompts; add prompts before triggering an analysis run
409 report_not_modifiable Report is in a state that does not allow this operation (e.g. processing or already completed)
409 schedule_has_no_regions Scheduled report has no region configurations linked; add at least one before triggering a run
409 schedule_inactive Scheduled report is inactive; activate it before triggering a run
410 gone Resource is no longer available
422 body_validation_error Request body did not match the expected schema
422 brand_facts_required This action requires the brand to have facts (a non-empty `description`). Either POST /v1/brands/{brand_id}/facts to research them asynchronously, or PATCH /v1/brands/{brand_id}/facts with `{"description": "..."}` to set them manually.
422 invalid_frequency Schedule frequency must be one of: daily, weekly, monthly
429 rate_limit_exceeded Per-minute rate limit exceeded
429 request_quota_exceeded Monthly request quota exceeded
500 internal_error An unexpected error occurred
503 service_unavailable Service temporarily unavailable, please retry
504 external_timeout Upstream service timed out, please retry

Need help? Capture the failing request_id and the time of the request, then email support@rankprompt.com. We can trace any single call end-to-end from that pair.