Flight API errors
The error envelope, every code and reason with what to do next, and request ids.
Every failure has the same shape:
{ "error": { "code": "unprocessable", "reason": "offer_expired", "message": "offer has expired; create a new booking", "request_id": "req_019ff4c40a7476d584985652b8e989f0" } }code is the class of failure. reason is why. Branch on reason and fall back to code. reason is present on every 422 and 502, and on 500 result_unknown, which requires the recovery steps in Retry flight calls safely; a 400, 401, 404 or 409 carries none.
Codes
| Code | Status | What happened, and what to do |
|---|---|---|
validation_error | 400 | The body, query, path or a required header is wrong. Fix the request; details names the field. At prebook it also answers a traveller or contact name the supplier refused as a placeholder, such as Test: the booking is then FAILED with failure.code passenger_name_rejected, so send the real name with a new idempotency key. |
unauthorized | 401 | Your API key is missing or invalid. Send it as x-api-key on every request. |
not_found | 404 | No such resource, or it is not yours. Do not retry. |
conflict | 409 | The booking or offer is not in a state that allows this: a book while another book is still running, a cancel on a booking that is neither confirmed nor already cancelling, bags on a booking that cannot take them, or a prebook of a Duffel offer another booking holds. Re-read the booking and act on its status; for a held offer, search again. |
unprocessable | 422 | The request cannot be fulfilled as sent. Do not retry unchanged; reason says what to change. |
internal_error | 500 | An error on our side, or a result we could not determine. Read result_unknown before doing anything else. |
supplier_unavailable | 502 | A supplier did not answer and nothing was decided. Retry with backoff. |
Reasons
| Reason | Status | What happened, and what to do |
|---|---|---|
offer_expired | 422 | Request a fresh offer when its reference has lapsed. For an existing LiteAPI checkout, contact support before replacing it because expiry does not establish whether your traveller paid. |
price_changed | 422 | The airline re-priced between the offer and prebook; the booking is FAILED. Request offers again and prebook from the fresh reference. |
passenger_mismatch | 422 | The party does not match the offer that was searched, or the Duffel offer carries loyalty accounts or names that an earlier booking of it left and that differ from yours. Search again. |
document_required | 422 | The offer's documents_required is true and a passenger has no document. Collect one for every passenger and prebook again with a new key. |
services_unavailable | 422 | An extra can no longer be bought as chosen: a bag is no longer sold or would take the booking past the supported amount, a seat is taken or priced differently, or two passengers named one seat. At prebook, list the extras again and prebook with a new key. At book the booking is FAILED and nothing is captured: search again. |
payment_method_unsupported | 422 | For LiteAPI, set use_payment_sdk: true at prebook and send {} at book. For an OEX SDK booking, omit payment_instrument_id at book. You cannot mix SDK checkout and a saved instrument. |
payment_failed | 422 | On OEX, the card was declined or the Payment SDK intent has not confirmed. At prebook the booking is FAILED: send a new key. At book, an unconfirmed intent leaves the booking READY_FOR_COMPLETE: confirm it and book again; a decline marks the booking FAILED. |
supplier_operation_unsupported | 422 | The supplier cannot do this: the requested operation or paid bags and seats are unsupported, or no routed supplier can serve the search. Choose another offer or name another supplier. |
booking_incomplete | 422 | The stored booking lacks something the operation needs, such as a bound price, a prebook hold, passenger ids or an itinerary, or it has an unsettled OEX payment that must resolve before it can be cancelled. Contact support for an incomplete LiteAPI booking before creating a replacement checkout. |
supplier_rejected | 422 | The supplier answered, and the answer was no. message and details carry what it said; do not retry unchanged. |
loyalty_membership_rejected | 422 | At book on Duffel, the airline did not recognise a loyalty number, so the booking is FAILED and no payment is captured. Search again and prebook the new offer without that number, with a new key. |
cancellation_not_quotable | 422 | A cancellation quote on a booking that is not confirmed. Cancel is accepted with 202 before the airline is asked, so an airline refusal is never an HTTP reason: read it from the booking's failure.code instead. |
supplier_unreachable | 502 | Retry with backoff when no stateful outcome is unknown. A failed Duffel prebook holds no inventory; an uncertain prebook instead returns result_unknown. |
result_unknown | 500 | Re-read your booking because the supplier may have acted. Keep the booking and request ids and contact support before sending another book request. Never create a replacement LiteAPI checkout to bypass this state. A Duffel prebook that answers it holds no inventory and has charged nothing: replay its key to read it, then search again and prebook the new offer with a new key. |
Request ids
Every response carries x-request-id, and every error body repeats it as error.request_id. Log it, and quote it when you contact support.