Getting started · Errors

Errors & rate limits

Every error has a consistent JSON shape: a machine-readable error code, a human message and a `request_id` you can quote when contacting support.

Error response format

All errors follow the same JSON structure regardless of which endpoint returned them:

FieldTypeDescription
errorstringA human-readable sentence. On a 502 this is the airline's own message, bracketed code included. It is NOT a stable machine-readable code — do not branch on its text.
messagestringPresent only on the structured 402 quota and 503 feature-gate responses, alongside `reason`/`docs_url`. Absent from every other error.
// Every NDC endpoint — one field, nothing else, by default.
{ "error": "missing X-API-Key" }

// A 502 carries the airline's own text.
{ "error": "airline error: [230000002] Invalid or Expired Offer" }

// An order id this account doesn't hold adds a stable code, and a
// suggestion when one of your own orders is a one-character typo away.
{
  "error": "no order with id \"AA001Y1XD7ZB6\" belongs to this account — copy the order_id field from POST /v1/orders or GET /v1/orders rather than retyping it. Did you mean AA001Y1XD7ZA6?",
  "code": "order_not_found",
  "did_you_mean": "AA001Y1XD7ZA6"
}

// The two structured exceptions: 503 feature gate and 402 quota.
{
  "error": "coming_soon",
  "message": "The booking flow is not yet available in this environment.",
  "docs_url": "https://norba.io/docs/changelog"
}

Error codes

Every error returned by the API uses one of the following codes:

HTTPClassMeaning
400ValidationThe handler rejected your body. The message names the problem: `adults must be >= 1`, `invalid departure_date`, `origin and destination are required`, `offer_id is required`, `at least one passenger is required`, `unknown airline` (an offer or order id with no recognisable airline prefix and no `?airline=`). On an order-id endpoint specifically, a supplied `?airline=` that disagrees with the order's own stored carrier answers `airline does not match the order` instead — the carrier there is inferred from the order, not from the query string. Body that is not valid JSON answers `invalid request body`.
401Auth`missing X-API-Key` when the header is absent, `invalid api key` when the key is not a valid Norba API key. `GET /v1/orders` also answers 401 when the key has no agency behind it.
402QuotaStanding check failed on a developer-portal key. `reason` is `payment_required` (free allowance exhausted), `payment_method_required` (plan requires a card, none on file), `delinquent`, or `past_due` — alongside `message`, `plan_code`, `period_start` and `period_end`. See /docs/billing.
403Unknown plan`unknown_plan` — the account's plan code matches nothing in the pricing catalogue. Same structured body as 402; a configuration problem no card on file can fix.
404Not foundOn an order id: `code: "order_not_found"` — no order with that id in your agency's store; there is no airline fallback any more, whatever `?airline=` you send. Carries `did_you_mean` when one of your agency's own orders is a one-character typo away (O/0, I/1 and the like). Or `offer no longer available at airline`, `connection offer not found or expired`.
413Body too large`request body too large` — every request body is capped at 1 MiB. A valid document over the cap gets its own status rather than being reported as malformed JSON.
429Rate limitMore than 60 searches in the last minute (`Retry-After: 60`), or `reason: allowance_burst` — more calls in two minutes than the free requests you have left, held back rather than overspent (`Retry-After: 120`). Neither means anything is owed.
502Airline errorThe carrier refused the request and the message is its own text, e.g. `[230000002] Invalid or Expired Offer`. Also `all airline connections failed — please retry later`.
503Feature gate`coming_soon` — order writes are disabled in this environment. Carries `Retry-After: 3600`. Reads keep working.

Rate limits

Rate limits are sliding-window per agency. On `429` the response carries a `Retry-After` header indicating how many seconds to wait before retrying.

LimitScopeDetails
60 req/min (default)Per agencyPOST /v1/shopping/offers and /stream. Adjustable per account; not a platform cap.
Daily/monthly ceilings, look-to-book policyPer agencyShopping and order creation, when one has been agreed for the account. Contractual limits some airlines require, not a capacity limit — most agencies have none set.
No limit at allPer agency/v1/aviation/* and /v1/reference/* — exempted outright, since they never reach an airline.
No limitPer agencyEvery remaining endpoint. Metered against your plan quota rather than rate-limited — exhausting it returns 402, not 429.
API Error Codes & HTTP Status Reference | Norba Docs