Errors
Errors use standard HTTP status codes and an RFC 9457 application/problem+json body:
{
"type": "https://quickota.com/developers/errors/flights.price_changed",
"title": "Price changed",
"status": 409,
"code": "flights.price_changed",
"detail": "The airline repriced the fare above the accepted tolerance between search and booking. Nothing was booked.",
"suggestion": "Price the offer again (or search again), show the new price to your customer and book only after they accept it.",
"retryable": false,
"requestId": "req_7f3a2c9e41d0b8a6c5e21f90",
"context": { "offerTotal": { "amount": "180.00", "currency": "USD" } }
}
| Field | Use it for |
|---|---|
code |
Branch your code on this. Stable, never renamed. |
status |
The HTTP status, repeated. |
title, detail |
Human-readable; wording may change. Safe to log, not to parse. |
suggestion |
What to do next. |
retryable |
true when the same request may succeed later without changes. |
requestId |
Quote it to support — we can find the exact request. |
errors |
For request.validation_failed: field → messages, e.g. "passengers[0].birthDate": ["…"]. |
context |
Extra data for some codes (shortfall amounts, current status, allowed actions). |
type |
Link to this code's documentation page. |
Handling errors — a checklist
- 4xx with
retryable: false— fix the request or the flow. Retrying the same call will fail the same way. - 429 — wait
Retry-Afterseconds. See Rate limits. - 5xx or
retryable: true— retry with exponential backoff (e.g. 1 s, 2 s, 4 s … max 5 tries). For writes, send the sameIdempotency-Keyso a retry can never book or ticket twice. See Idempotency. - Timeout with no response on
issue— do not call issue with a new key.GETthe booking: if it isissuingorticketed, the first call worked. See Managing booking states. - Log
requestIdfor every error.
We never put supplier names, stack traces or internal messages in errors; support can see them from the requestId.
Machine-readable catalog
https://quickota.com/developers/errors.json— every code with status, title, description, resolution andretryable.https://quickota.com/developers/errors/{code}— one page per code.
The full list follows.
All error codes
auth.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
auth.endpoint_not_allowed |
403 | API keys can only call documented public endpoints under the public base URL. | Check the path against the API reference. | no |
auth.insufficient_scope |
403 | The key does not carry the scope this endpoint needs (for example flights:book to create bookings). |
Edit the key in the panel and add the scope named in context.requiredScope, or use a key that has it. |
no |
auth.invalid_key |
401 | The key is malformed, was mistyped, or never existed. | Copy the key again from the panel. A key is shown only once when it is created — if it is lost, create a new one and revoke the old one. | no |
auth.ip_not_allowed |
403 | The key has an IP allow-list and the request came from an address that is not on it. | Call from an allowed server, or add this server's public IP (or CIDR range) to the key's allow-list in the panel. | no |
auth.key_expired |
401 | The key had an expiry date and it has passed. | Create a new key (or rotate the expired one) in the panel. | no |
auth.key_revoked |
401 | The key was revoked by your agency or by platform support, or it was replaced by a rotation whose grace period ended. | Use the current key from the panel, or create a new one. | no |
auth.missing_key |
401 | The request has no Authorization: Bearer <key> header, or it carries a panel login token instead of an API key. The public API only accepts API keys. |
Create a key in your panel under Developers → API keys and send it as Authorization: Bearer tam_live_…. |
no |
account.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
account.agency_inactive |
403 | Your agency account is deactivated on the platform. | Contact platform support. | no |
account.api_suspended |
403 | API access for your agency was suspended — usually because usage limits or the look-to-book ratio were breached, or on request of platform support. | Contact platform support. Your panel stays available; only API access is paused. | no |
account.insufficient_balance |
402 | The wallet balance does not cover the amount due. context carries required, available and shortfall. The booking hold is kept until it expires. |
Top up the wallet (panel → Finance) and call the same endpoint again before the hold expires. Check GET /account/balance before issuing to avoid this. |
no |
account.module_not_installed |
403 | Your agency has not activated the module this endpoint belongs to, or the Developer API module itself. | Activate the module from your panel's module store, or contact your account manager. | no |
account.wallet_currency_mismatch |
409 | The booking currency differs from your wallet currency. | Search with currency set to your wallet currency (see GET /account/balance). |
no |
account.wallet_unavailable |
503 | The payment could not be taken from your wallet for a reason other than balance (wallet missing or temporarily unavailable). Nothing was charged. | Retry later; contact support if it persists. | yes |
request.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
request.conflict |
409 | Another request changed the same resource at the same moment. | Fetch the resource again and retry if the action is still needed. | yes |
request.invalid_state_transition |
409 | The action does not apply to the resource's current status (for example issuing a booking that is already cancelled). context.currentStatus and context.allowedActions describe what is possible now. |
Fetch the resource and follow its nextActions. |
no |
request.malformed_json |
400 | The body is not valid JSON or does not match the expected shape. | Send Content-Type: application/json with a body matching the API reference. |
no |
request.not_found |
404 | The id does not exist, or it belongs to another agency. | Check the id, and that it was created with a key of the same agency. | no |
request.validation_failed |
422 | One or more fields are missing or invalid. errors maps each field path to its messages. |
Fix the listed fields and send the request again. | no |
idempotency.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
idempotency.in_progress |
409 | The first request with this key has not finished yet. | Wait a few seconds and retry with the same key — you will receive the original result. | yes |
idempotency.key_required |
400 | This endpoint creates something that must never happen twice (a booking, a ticket), so it requires an Idempotency-Key header. |
Send a unique value (a UUID is ideal) in Idempotency-Key. Reuse the same value when you retry the same operation. |
no |
idempotency.key_reused |
422 | The key was already used for a request with a different method, path or body. | Generate a new key for every new operation; reuse a key only to retry exactly the same request. | no |
rate_limit.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
rate_limit.exceeded |
429 | Too many requests in a short time for this key. X-RateLimit-* headers show the limit and when it resets. |
Wait for the number of seconds in Retry-After, then retry. Spread calls out and cache search results. |
yes |
quota.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
quota.look_to_book_exceeded |
429 | Your agency searches far more than it books. Searching is paused until the ratio recovers; booking endpoints keep working. | Reduce repeated or automated searches (cache results, avoid price scraping) and convert more searches into tickets. Contact support if you believe this is wrong. | yes |
quota.searches_daily_exceeded |
429 | Your agency used all searches allowed for today. | Wait until the quota resets (00:00 UTC) or ask your account manager for a higher quota. Cache results instead of repeating identical searches. | yes |
quota.searches_monthly_exceeded |
429 | Your agency used all searches allowed for this month. | Wait until the next month or ask your account manager for a higher quota. | yes |
flights.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
flights.booking_not_serviceable |
409 | Post-booking requests need a ticketed booking that has not been fully cancelled or flown. | Check the booking's status and nextActions. |
no |
flights.cancellation_not_allowed |
409 | The fare rules do not allow cancelling or refunding at this time. | Check the fare rules. A no-show refund of taxes may still be possible through a post-booking request. | no |
flights.duplicate_booking |
409 | An identical booking already exists (same offer or same request). | Look the existing booking up with GET /flights/bookings?agencyReference=… before booking again. |
no |
flights.exchange_rate_unavailable |
503 | No exchange rate is configured between the fare currency and your wallet currency, so the fare cannot be priced safely. | Search in your wallet currency, or contact support to configure the rate. | no |
flights.fare_not_available |
409 | The seats at this fare were sold between search and booking. | Search again and pick another flight or fare. | no |
flights.hold_expired |
409 | The seats were released because the booking was not issued before deadlines.holdExpiresAt. The booking is now cancelled. |
Search and hold again, then issue before the hold expires. | no |
flights.hold_not_supported |
409 | The airline issues this fare the moment it is booked (typical of low-cost carriers), so it cannot be held and paid later. The price check shows it as hasFlightHold: false. Nothing was booked. |
Pick a fare with hasFlightHold: true, or book this one through the agency panel, which books and pays in one step. |
no |
flights.invalid_search |
422 | The search criteria are incomplete or impossible: missing airports, a date in the past or more than a year ahead, no adult, more infants than adults, more than 9 seated passengers, or more than 6 legs. | Fix the fields listed in errors and search again. |
no |
flights.itinerary_not_bookable |
409 | The airline refused the itinerary (schedule changed, flight closed or cancelled). | Search again. | no |
flights.offer_already_booked |
409 | Each offer can be held once. This one already produced a booking (see context.bookingId). A retry of the same hold should reuse the same Idempotency-Key, which returns the original booking instead of this error. |
Use the existing booking, or search again for a new offer. | no |
flights.offer_expired |
410 | The offer's validity window (expiresAt) has passed, or the airline no longer recognises it. Fares change constantly, so offers are short-lived. |
Search again. Price (POST /flights/offers/{offerId}/price) and hold soon after searching. |
no |
flights.offer_not_found |
404 | The offerId is unknown. Offers belong to the agency that searched them and are kept for at most 30 minutes. |
Search again and use an offerId from the new results. |
no |
flights.passenger_data_invalid |
422 | A passenger is invalid: no adult on the booking, a child or infant without birth date, a passenger type that does not match the age, a missing or expired passport on an international trip (a national ID is accepted on domestic trips only), or a placeholder name such as TEST. Names must be in Latin letters exactly as on the travel document. | Correct the passenger listed in detail and hold again with a new Idempotency-Key. |
no |
flights.price_changed |
409 | The airline repriced the fare above the accepted tolerance between search and booking. Nothing was booked. | Price the offer again (or search again), show the new price to your customer and book only after they accept it. | no |
flights.quote_expired |
409 | The post-booking quote was not accepted before its expiresAt. |
Wait for a new quote (support may re-quote) or create a new request. | no |
flights.schedule_change_not_supported |
409 | The airline for this booking does not accept schedule-change replies through the API. | Contact support to handle this schedule change. | no |
flights.servicing_request_open |
409 | This booking already has an open post-booking request for these passengers. | Follow the open request (openServicingRequests on the booking) to its end, or withdraw it first. |
no |
flights.supplier_timeout |
504 | The airline's reservation system was too slow. For a hold, nothing was booked unless the booking appears in GET /flights/bookings?agencyReference=…. |
Retry with the same Idempotency-Key after a few seconds. |
yes |
flights.supplier_unavailable |
503 | The airline connection is temporarily unavailable or refused the request for a reason on its side. | Retry later, or search again for other options. | yes |
flights.ticket_not_issued |
409 | The e-ticket document exists only once the booking is ticketed. |
Wait for the booking to become ticketed, then download the ticket. |
no |
flights.ticketing_pending |
409 | The airline is still issuing the tickets. This is normal for some airlines and can take up to several minutes. | Do not call issue again. Wait for the flight.booking.ticketed webhook, or poll GET /flights/bookings/{id} every pollAfterSeconds. |
yes |
flights.use_post_booking_request |
409 | A ticketed booking cannot be cancelled directly: the airline's refund or void conditions apply and must be quoted first. | Create a post-booking request (POST /flights/bookings/{id}/post-booking-requests) of type refund or void, wait for its quote, then accept it. |
no |
webhooks.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
webhooks.endpoint_not_found |
404 | The webhook endpoint id does not exist in this environment. | List your endpoints with GET /webhook-endpoints. |
no |
webhooks.url_not_allowed |
422 | Webhook URLs must be public https:// addresses. Private, loopback and internal network addresses are refused. |
Use a publicly reachable HTTPS URL. | no |
server.*
| Code | HTTP | When it happens | What to do | Retry |
|---|---|---|---|---|
server.internal_error |
500 | Something failed on our side. Nothing about your request is wrong. | Retry later. If it persists, contact support and quote the requestId. |
yes |
server.maintenance |
503 | The API is in maintenance or overloaded. | Retry after the number of seconds in Retry-After. |
yes |