Sandbox: real offers, real PNRs, test 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 tickets are test documents. A paid order comes back ticketed with test ticket and EMD numbers in tickets[]; a hold comes back confirmed with none. Read status and tickets[], not the HTTP code — that is the check production relies on too.
Shopping

Price offer

Confirm the price with the airline and mint the bookable offer ID. Mandatory before booking.

POST/v1/offers/priceRequires X-API-Key

This step is not optional. AirShopping returns transient offers; the airline will not book one. OfferPrice re-confirms availability and price and mints a new offer ID, and that new ID is the only one POST /v1/orders accepts.

With American Airlines the change is visible in the ID itself — the shopped offer starts AA-X… and the priced offer comes back AA-P…:

Sent (shopped)AA-X2E80EC16-51FD-48AD-924B.1-1|X2E80EC16-…-1-1
Returned (priced)AA-P2E80EC16-51FD-48AD-924B-1|P2E80EC16-…-1-1
Guaranteed until~20 minutes

Booking with the shopped ID fails with 422 airline_refused before the airline is called (the body is the generic sentence, so check the prefix yourself). This is the single most common integration mistake. An offer the carrier will no longer price answers 422 offer_expired when the connector can tell, 502 otherwise.

Routing. airline is optional — it is inferred from the offer ID prefix (everything before the first -). Send it explicitly if your offer IDs do not carry a prefix.

Caching. Priced results are cached per exact set of offer IDs, so a repeat call inside the TTL does not hit the airline again.

The response is a full Offer plus price_guaranteed_until. In practice AA returns the same total it shopped — a confirmed price is a confirmation, not usually a re-quote.

Request example

curl --request POST \
  --url https://api.norba.io/v1/offers/price \
  --header "X-API-Key: $NORBA_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "offer_ids": ["AA-X2E80EC16-51FD-48AD-924B.1-1|X2E80EC16-51FD-48AD-924B.1-1-1"], "airline": "AA" }'

Response example

Response example
{
  "offer_id": "AA-P2E80EC16-51FD-48AD-924B-1|P2E80EC16-51FD-48AD-924B-1-1",
  "airline_offer_id": "P2E80EC16-51FD-48AD-924B-1",
  "airline": "AA",
  "source": "ndc_direct",
  "source_version": "NDC-21.3",
  "currency": "EUR",
  "total_amount": 380.62,
  "base_fare": 193,
  "taxes_fees": 187.62,
  "cabin_class": "economy",
  "baggage_included": false,
  "expires_at": "2026-08-14T09:45:23Z",
  "price_guaranteed_until": "2026-08-14T09:45:23Z",
  "passengers": [
    {
      "type": "ADT",
      "count": 1,
      "base_fare": 193,
      "taxes": 187.62,
      "total": 380.62
    }
  ],
  "slices": [
    {
      "origin": "BCN",
      "destination": "JFK",
      "stops": 0,
      "segments": [
        {
          "flight_number": "AA67",
          "aircraft": "772"
        }
      ]
    }
  ]
}

Authorization

X-API-KeystringRequired

Opaque API key issued from the Norba dashboard. Sent on every request as the X-API-Key HTTP header. Missing or invalid keys are rejected with 401 Unauthorized.

Request body

offer_idsarray<string>Required

One or more offer IDs from a shopping response. Required — an empty array returns 400 offer_ids required.

Example: ["AA-X2E80EC16-…|X2E80EC16-…-1-1"]
airlinestring

IATA code. Inferred from the first offer ID's prefix when omitted.

Example: AA

Response fields

offer_idstringRequired

The bookable offer ID. Pass this — not the shopped one — to POST /v1/orders.

price_guaranteed_untilstring (RFC 3339)Required

Deadline for creating the order at this price. Roughly 20 minutes with AA. Past it, price again.

(all Offer fields)object

The response embeds the full canonical Offer — totals, slices, segments, fare family, fare rules, passenger breakdown.

Error codes

400

offer_ids required, or unknown airline (check offer_id prefix).

404

offer no longer available at airline.

422

offer_expired — the carrier will no longer price this offer; search and price again.

502

The airline rejected the offer and the connector could not say why — usually expired, or already priced once. The body is the generic sentence; the carrier's text is in Norba's logs under your X-Request-Id.

POST /v1/offers/price — Price offer | Norba API