In the sandbox these endpoints work against real carrier test inventory: a search returns real offers and you can create a real booking with a carrier PNR. Two things are worth knowing before you build against them.
- Sandbox orders are test reservations. Nobody flies on them, and they raise no fee. Cancel what you create; anything left over expires on its own payment time limit.
- Sandbox tickets are test documents. A paid order comes back
ticketedwith test ticket and EMD numbers intickets[]; a hold comes backconfirmedwith none. Readstatusandtickets[], not the HTTP code — that is the check production relies on too.
Confirm a change
Apply the quoted change and return the updated order.
/v1/orders/{orderID}/change/confirmRequires X-API-KeyAccepts a reshop offer and reissues the order against it, returning the updated canonical Order, which is also persisted.
This is step 2 of 2. Call POST /v1/orders/{orderID}/reshop first, pick an option, and pass its offer_id here:
```
POST /v1/orders/{orderID}/reshop → offers[], each with an offer_id
POST /v1/orders/{orderID}/change/confirm
{"offer_id": "<the option you are accepting>"}
```
NDC does not edit an order in place — it replaces it with a specific offer, which is why the offer reference is required. Omitting offer_id returns 400 naming …/reshop as the source.
change_quote_id is accepted but advisory: the airline reprices at acceptance regardless of what the earlier quote said. Re-read the order afterwards — GET /v1/orders/{orderID}?refresh=true — to see what actually changed, including any reissued tickets.
Add-collect. A reshop offer's total_amount is what the carrier will collect when you accept it. When it is positive, send a payment block for that amount here and the carrier is paid in the same call — an unpaid add-collect is a change the carrier will not make. An even exchange needs no payment. The card is validated (400) before the airline is called, and never stored.
The status on this response is the one the carrier reports at acceptance, which can be transitional (a reissue has been seen to answer partially_cancelled while the new documents are issued). Re-read the order — GET /v1/orders/{orderID}?refresh=true — for the settled state and the new tickets.
The host may refuse an alternative its own reshop produced — a flight that turns out not to operate on the date, for instance — with 422 airline_refused (the carrier's text is in Norba's logs). The order is left untouched; pick another offer from the reshop response.
?airline= is optional: the airline comes from your stored order, not from this parameter. A value that disagrees with the persisted carrier is refused with 400 airline does not match the order.
Idempotency. Send an Idempotency-Key (up to 128 characters, one per action) and keep it across retries: a repeat returns what the first call produced with Idempotent-Replayed: true, a key still in flight is 409 idempotency_key_in_flight, and another write still running on the same order is 409 order_busy — both with Retry-After, after which you send the same request again with the same key. A refusal from the airline is 422 (a named code, or airline_refused), never a 502; a write that never reached the airline is 503 airline_unreachable with Retry-After. A 502 with code: "change_outcome_unknown", attempt_id and Retry-After means the write reached the airline and its answer was lost — do not send it again; a retry with the same key is 409 with the same code until a person has settled it. A change the airline made but Norba could not store is still a 200 with X-Norba-Order-Persisted: false and a warning.
Request example
curl --request POST \
--url https://api.norba.io/v1/orders/{orderID}/change/confirm \
--header "X-API-Key: $NORBA_KEY" \
--header "Content-Type: application/json" \
--data '{ "offer_id": "OFFER-AA-RESHOP-1", "payment": { "method": "CARD", "currency": "EUR", "amount": 59.63, "card": { "number": "4111111111111111", "holder": "AGENCY TRAVEL SL", "expiry_month": 11, "expiry_year": 2029 } } }'Response example
{
"order_id": "AA001Y1XD7ZA6",
"pnr": "JBQGEY",
"status": "confirmed",
"currency": "EUR",
"total_amount": 380.62
}Path parameters
orderIDstringRequiredCanonical order ID.
AA001Y1XD7ZA6Query parameters
airlinestringIATA code. Optional — inferred from your stored order. A value that disagrees with the persisted carrier is refused with 400 airline does not match the order.
AARequest body
offer_idstringRequiredRequired. The offer_id of the reshop offer being accepted.
offer_idsarray<string>Additional reshop offers, when the change spans more than one.
change_quote_idstringAdvisory. Accepted, but the airline reprices at acceptance.
paymentobjectThe add-collect, when the offer's total_amount is positive: method, currency, amount, card — the same shape as POST /v1/orders.
Error codes
400invalid json, airline does not match the order when a supplied ?airline= disagrees with the persisted carrier, offer_id is required when no reshop offer was supplied, or a payment.card.… message when the card fails local validation.
409illegal_transition — a cancelled, refunded or failed order cannot be changed; order_busy or idempotency_key_in_flight (with Retry-After); idempotency_key_reused; or change_outcome_unknown (code, with attempt_id) — an earlier request with this key reached the airline and its answer was lost; do not reissue again.
422airline_refused — the airline rejected the change and the connector could not name why; or a named refusal. The order is untouched.
502Only with code: "change_outcome_unknown", attempt_id and Retry-After: the reissue reached the airline and its answer was lost — do not send it again.
503coming_soon — order writes disabled; or airline_unreachable with Retry-After — nothing reached the airline.