Errors

The Ray9 error envelope, the full `code` reference, and per-status retry guidance.

Every 4xx and 5xx response from Ray9 ships in the same envelope. Build your error handling against the code field — it's the stable contract. Treat message as a human-readable string we may rephrase; never branch on it.

Envelope

{
  "requestId": "req_4f3a2c1b9e8d",
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Try again in a moment.",
    "details": {
      "retryAfterMs": 1200
    }
  }
}
  • requestId — Fastify-issued per-request id. Mirrors the x-request-id response header so you can correlate a body to logs without parsing headers.
  • error.code — stable, snake_case, machine-readable. The list below is the complete set.
  • error.message — short human-readable string. May change for clarity; do not parse.
  • error.details — optional, code-specific. Open object — read only the keys you recognise; we may add more.

Headers that travel with errors

HeaderWhen
x-request-idEvery response, success or error. Always echo this in support tickets.
Retry-AfterOn 429 and 503. Integer seconds. Honour it before retrying.

Code reference

Every code Ray9 emits, with the HTTP status, the trigger, and what your client should do.

HTTPcodeWhenWhat to do
400bad_requestCaller body fails JSON-schema validation (missing field, wrong type, out-of-range value).Fix the request and resend.
400query_rejectedThe search engine refused the query (e.g. malformed for the target engine, location/language combination unsupported).Adjust the query, country, or language. Don't retry blindly.
401unauthenticatedNo key, malformed Authorization header, unknown / expired / revoked key, or session signed-out.Re-authenticate. Never retry blindly. See Authentication.
402out_of_creditsOrg credit balance can't cover the request cost.Surface to the user; the details payload carries { action: "topup", topup_url }. See Billing.
402plan_requiredThe route isn't included in the org's current plan (Free covers SERP only; PayG unlocks the rest).Surface to the user; the details payload carries { required_plan, current_plan, action: "topup", topup_url } to drive the upgrade flow.
404not_foundRoute doesn't exist.Check the URL and method. Don't auto-create.
404no_resultsThe query ran successfully but returned zero results.Treat as a valid empty result, not an error condition. Adjust the query if you expected hits.
429rate_limitedPer-org inbound rate limiter tripped (60/min, shared across all keys + sessions).Honour Retry-After and details.retryAfterMs. Back off with jitter. See Rate limits.
501not_implementedFeature not wired in this version (e.g. engine: bing in v1).Don't retry. Use a supported value.
502service_unavailableA dependency we call to fulfil the request failed after retries.Retry with exponential backoff and jitter. Alert if it persists.
503service_busyWe're throttling outbound traffic to protect the service; your request couldn't be queued within budget.Retry per Retry-After.
504service_timeoutThe request exceeded our end-to-end time budget.Retry; if it recurs, the query is likely too heavy — narrow it (depth, location).

Per-status guidance

400 — caller-side fix. Re-read the schema for the endpoint and resend.

401 / 403 — re-authenticate. Never retry the same request — you'll just 401 again. Check that the Authorization: Bearer rk_… header is present and unmangled.

402 — payment / plan. Surface to the user; programmatic retries don't help. Useful details keys: required_plan, current_plan, action, topup_url.

404 — the resource doesn't exist (not_found) or the search returned no hits (no_results). Treat no_results as a valid empty result; don't paper over not_found with auto-create logic.

429 — back off. Honour Retry-After (seconds) or details.retryAfterMs (milliseconds, takes precedence). Use exponential backoff with jitter for repeated breaches.

5xx — transient by definition. Retry with exponential backoff and jitter. Alert if a single endpoint stays in 5xx for more than a few minutes — check the status page; we'll usually have an incident posted by then.

Credits and errors

You're not billed for failed calls. Both 4xx and 5xx are auto-refunded — the credit debit fires on entry to the handler and is reversed on any non-2xx response. Cached 200s and live 200s are the only billing events. See Billing for the full meter rules.

Idempotent retries

The Ray9 API is read-only, so retries are at-most-once safe by construction.

Logging the right things

When something goes wrong, log:

  1. The full requestId (or the x-request-id header).
  2. The code, the HTTP status, and the request URL + method.
  3. Whatever in details is structured (e.g. retryAfterMs).

Don't log the message as your primary error key — branch on code. Don't log the request body if it might contain user content; log a hash or the request URL plus parameter shape.

On this page