Sandbox: real offers, real PNRs, no tickets
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 orders are never ticketed. A paid order comes back
confirmedwith an emptytickets[]and a warning saying why. Readstatusandtickets[], not the HTTP code — that is the check production relies on too.
Servicing
Add extras to an order
Buy bags, meals or other ancillaries against an existing booking, at the airline's price.
POST
/v1/orders/{orderID}/servicesRequires X-API-KeyAdds ancillaries to an existing order. Each entry names a
service_id from GET /v1/orders/{orderID}/services as ancillary_offer_id, and the passenger it is for; the price is the catalogue's, never the client's — an extra this API never showed for the order is refused with 422 extra_not_priced before the airline is called.Payment follows the booking's state. A held order simply gains the extras, settled with the fare at issuance. A ticketed order has to pay for them here, in payment, and the airline issues the EMDs in the same call. payment.amount must cover the catalogue price of what is being added (400 otherwise); anything above it is the agency's margin.The response is the updated canonical Order, persisted, with ancillaries[] as the airline now holds them; the timeline records services_added. Norba's post-sale commission applies to the increase in the airline's confirmed total. Refused 409 illegal_transition on a cancelled, refunded or failed order.Request example
curl --request POST \
--url https://api.norba.io/v1/orders/{orderID}/services \
--header "X-API-Key: $NORBA_KEY" \
--header "Content-Type: application/json" \
--data '{ "ancillaries": [ { "ancillary_offer_id": "AA-SVC…|ITEM…", "passenger_id": "PAX96101" } ], "payment": { "method": "CARD", "currency": "EUR", "amount": 45.00, "card": { "number": "4111111111111111", "holder": "AGENCY TRAVEL SL", "expiry_month": 11, "expiry_year": 2029 } } }'Response example
Response example
{
"order_id": "AA001Y1XD7ZA6",
"status": "ticketed",
"total_amount": 425.62,
"ancillaries": [
{
"ancillary_offer_id": "AA-SVC…|ITEM…",
"type": "bag_checked",
"name": "1st checked bag",
"price_amount": 45,
"currency": "EUR",
"passenger_id": "PAX96101"
}
]
}Path parameters
orderIDstringRequiredCanonical order ID.
Example:
AA001Y1XD7ZA6Request body
ancillariesarray<object>RequiredEach with
ancillary_offer_id (the service_id shown), passenger_id (the order's passenger id), optional segment_ids.paymentobjectRequired on a ticketed order:
method, currency, amount, card — the same shape as POST /v1/orders.Error codes
400ancillaries is required, a payment.card.… validation message, or payment.amount is less than the airline's price for these extras.409illegal_transition — the order can no longer be serviced.422extra_not_priced — the extra was not in the catalogue this API served for the order.502The airline refused the change.
503coming_soon — order writes disabled.