Errors & Limits

The ApiError envelope, error codes, correlation IDs, request limits, and rate limiting.

Every non-2xx response from the API carries the same machine-readable envelope, every request gets a correlation ID you can quote to support, and the request limits are fixed and documented. This page is the reference for all three.

The ApiError envelope

1{
2 "schemaVersion": "1",
3 "code": "request.validation",
4 "message": "topK must be between 1 and 100.",
5 "correlationId": "req_0197c0a1b2c3d4e5f60718293a4b5c6d",
6 "ruleId": null
7}
  • code is the stable, machine-readable value to branch on — codes are additive-only and never renamed.
  • message is human-readable and actionable. It never leaks internals (no stack traces, no provider errors).
  • correlationId always echoes the request’s correlation ID.
  • ruleId is set when a policy rule denied the request; otherwise null.

Error codes

CodeHTTPMeaning
auth.invalid_token401Missing, malformed, expired, bad-signature, or wrong-issuer token — or a token that is not an access token
auth.insufficient_scope403Token valid, but the westiq/search scope is absent
auth.unknown_client403Token valid, but the client has no airline mapping (fail-closed — never a default tenant)
request.malformed400 / 413Body could not be accepted: not JSON, unparseable, empty (400) — or over the body-size cap (413)
request.validation422Body parsed but violates a rule (query length, topK range, batch size, {"events": null})
internal.error500Unexpected failure. The message is generic — the correlationId is the debugging handle

Unmatched routes return 401, by design: the API denies by default, so a typo’d path looks like an auth failure rather than a 404. If a request 401s unexpectedly, check the path first.

Correlation IDs

Every request is assigned a correlation ID of the form req_ followed by exactly 32 hex characters. You can supply your own on the X-Correlation-Id request header — if it matches that format it is honored verbatim; anything else (or no header) is replaced with a freshly minted ID. A bad value is never a rejection.

The established ID is echoed back on the X-Correlation-Id response header and inside every error body. Log it, and quote it in support requests — it is how we find your request in our logs, and for a 500 internal.error it is the only handle there is.

Request limits

LimitValueOver the limit
/v1/search body size64 KiB413 request.malformed
/v1/events body size2 MiB413 request.malformed
query length1000 characters422 request.validation
topK range1–100422 request.validation
Events per batch1–500422 request.validation

Rate limiting at the edge

The platform edge (WAF) applies a per-IP limit of 2000 requests per 5-minute window. Requests over the limit are blocked at the edge with a plain 403without the ApiError envelope. That is how you tell the two 403s apart:

  • 403 with a JSON body and a code — an API authorization failure (auth.insufficient_scope / auth.unknown_client). Fix the token or credential; retrying won’t help.
  • Bare 403 with no envelope — the edge rate limit. It clears on its own once your request rate drops below the limit. Back off and retry with jitter.

Two layers of “rejected” on /v1/events

/v1/events has a second, per-event rejection layer that is not an HTTP error: individually invalid events come back inside the 200 response’s rejected[] array with their own code vocabulary (event.*), while the ApiError envelope covers batch-level failures only. The split — and the five per-event codes — are covered in the behavioral events guide.