Sandbox orders are test bookings

Sandbox keys reach the airlines’ test systems: nobody flies on these orders and no booking fee is charged. Check status and tickets[], not the HTTP code.

Shopping

Search offers

One request searches every connected airline and returns one list of offers in one schema.

POST/v1/shopping/offersRequires X-API-Key

Searches every connected airline that serves the route, in parallel, and returns one list of offers, cheapest first.

  • Each offer reconciles: base_fare + taxes_fees + service_fee == total_amount. service_fee is your commission, absent when none applies.
  • The same search is answered from cache until the top-level expires_at: the earliest offer's expires_at less five minutes, between 90 s and 30 min. Empty results are never cached.
  • No connected airline on the route: 200 with offers: null.
  • Bad input gets 400 with a code before any airline is called.
  • Times are RFC 3339 in each airport's own offset: show the clock part as local time.
  • Your key's environment decides which airline systems answer. In production, an airline that needs its own agreement is left out until it is approved.
  • There is no airline filter; meta.airlines lists who answered.
  • Some airlines do not sell connections with a leg another airline operates; those offers are left out (see the airline pages).
  • A search takes 10–20 s. Set a client timeout of at least 60 s, or use the streaming search.

Limits. No rate limit; every request is metered. A 429 comes only from a volume ceiling agreed for your account or the free-allowance guard. A high look-to-book ratio adds an X-Norba-L2B header and may be surcharged (pricing).

Request example

curl --request POST \
  --url https://api.norba.io/v1/shopping/offers \
  --header "X-API-Key: $NORBA_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "origin": "BCN", "destination": "JFK", "departure_date": "2027-06-15", "adults": 1, "cabin_class": "economy", "currency": "EUR", "max_stops": 0 }'

Response example

Response example
{
  "search_id": "srch_01K2M8ZC3AC5N8XWQ7R4YB",
  "expires_at": "2026-08-14T10:14:52Z",
  "offers": [
    {
      "offer_id": "AA-X2E80EC16-51FD-48AD-924B.1-1|X2E80EC16-51FD-48AD-924B.1-1-1",
      "airline_offer_id": "X2E80EC16-51FD-48AD-924B.1-1",
      "airline": "AA",
      "source": "ndc_direct",
      "source_version": "NDC-21.3",
      "currency": "EUR",
      "total_amount": 380.62,
      "base_fare": 193,
      "taxes_fees": 187.62,
      "total_price": {
        "amount": 380.62,
        "currency": "EUR"
      },
      "expires_at": "2026-08-14T09:44:52Z",
      "cabin_class": "economy",
      "baggage_included": false,
      "fare_family": {
        "id": "AAAT-BASIC",
        "name": "Basic Economy",
        "brand_id": "AAAT-BASIC",
        "benefits": [
          {
            "type": "checked_bag",
            "included": false,
            "description": "Pay more to check bags"
          },
          {
            "type": "seat_selection",
            "included": false,
            "description": "Pay more for seat selection"
          },
          {
            "type": "changes",
            "included": false,
            "description": "No changes allowed"
          },
          {
            "type": "refund",
            "included": false,
            "description": "No refunds allowed"
          }
        ]
      },
      "fare_rules": {
        "refundable": false,
        "changeable": false,
        "raw_text": "No changes allowed / No refunds allowed",
        "restrictions": [
          {
            "action": "refund",
            "allowed": false
          },
          {
            "action": "change",
            "stage": "before_departure",
            "allowed": false
          }
        ]
      },
      "passengers": [
        {
          "type": "ADT",
          "count": 1,
          "base_fare": 193,
          "taxes": 187.62,
          "total": 380.62
        }
      ],
      "slices": [
        {
          "id": "OD1",
          "origin": "BCN",
          "destination": "JFK",
          "departure_utc": "2027-06-15T10:30:00+02:00",
          "arrival_utc": "2027-06-15T12:59:00-04:00",
          "duration_minutes": 509,
          "stops": 0,
          "segments": [
            {
              "segment_id": "SGM010012275a20c",
              "origin": "BCN",
              "destination": "JFK",
              "departure_utc": "2027-06-15T10:30:00+02:00",
              "arrival_utc": "2027-06-15T12:59:00-04:00",
              "duration_minutes": 509,
              "flight_number": "AA67",
              "marketing_carrier": "AA",
              "operating_carrier": "AA",
              "aircraft": "772",
              "departure_terminal": "1",
              "arrival_terminal": "8",
              "cabin_class": "economy",
              "booking_class": "B",
              "fare_basis": "OKX5C5BW"
            }
          ]
        }
      ],
      "quality_score": 0.95
    }
  ],
  "meta": {
    "total": 1,
    "airlines": [
      "AA"
    ],
    "sources": [
      "ndc_direct"
    ],
    "min_price": 380.62
  }
}

Authorization

X-API-KeystringRequired

Your API key from the dashboard. A missing or invalid key answers 401; see Authentication.

Request body

adultsintegerRequired

Age 12+, at least 1. Adults, children and infants total at most 9 (400 too_many_passengers); your agency may set a lower cap (400 passenger_cap).

Example: 1
cabin_classstring

economy, premium_economy, business or first; anything else is 400 invalid_search.

Example: economy
childreninteger

Age 2–11.

Example: 0
currencystring

ISO 4217 code: EUR, GBP, USD, CHF, SEK, NOK, DKK, PLN, CZK, HUF, RON, BGN, CAD, AUD, NZD, JPY, SGD, HKD, AED or MAD. The airline may still quote in its own currency: read currency on each offer.

Example: EUR
departure_datestring (YYYY-MM-DD)Required

Outbound date, from yesterday to 361 days ahead (UTC); outside it 400 date_out_of_range. Not needed with legs.

Example: 2027-06-15
destinationstringRequired

IATA airport code. Must differ from origin (400 same_airport). Not needed with legs.

Example: JFK
infantsinteger

Lap infants under 2; no more than adults (400 infants_exceed_adults).

Example: 0
legsarray<object>

Multi-city: 2–5 bounds of {origin, destination, departure_date}, in date order (400 invalid_date otherwise). Replaces origin, destination, departure_date and return_date.

max_stopsinteger

Absent: any. 0: non-stop only. 1–5: up to that many connections.

Example: 0
originstringRequired

IATA airport code (3 letters). Not needed with legs.

Example: BCN
return_datestring (YYYY-MM-DD)

Makes the search a round trip. On or after departure_date (400 return_before_departure).

Example: 2026-12-22
self_transferboolean

Also return self-transfer trips: two connected airlines on separate tickets (offers with self_transfer: true). One-way and round trips only; ignored with legs or max_stops: 0. Not available yet: accepted and ignored.

Example: true

Response fields

expires_atstring (RFC 3339)Required

Until when this answer is reused for the same search: the earliest offer's expires_at less five minutes, between 90 s and 30 min.

metaobject

total, airlines[], sources[] and min_price of the returned offers.

offersarray | nullRequired

The offers, cheapest first. null when no airline returned any.

offers[].airlinestringRequired

IATA airline code, e.g. AA.

offers[].airline_offer_idstring

The airline's own offer ID, unchanged.

offers[].ancillaries_included[]array

Bag allowances and extras listed with the fare: type (e.g. bag_checked, bag_carry_on), name, included_in_fare, and for bags weight_kg and dimensions_cm (length + width + height). A bag that is not included still shows its limits.

offers[].baggage_includedbooleanRequired

Whether a checked bag is in the fare.

offers[].base_farenumberRequired

The airline's base fare, all passengers.

offers[].cabin_classstringRequired

Main cabin of the itinerary.

offers[].currencystringRequired

ISO 4217 code of every amount on the offer.

offers[].expires_atstring (RFC 3339)Required

After it the offer cannot be priced or booked.

offers[].fare_familyobject

Branded fare: id, name, brand_id, description and benefits[]. Each benefit has type (carry_on, checked_bag, seat_selection, changes, refund, lounge, priority_boarding, miles_accrual, meal, wifi, fast_track or other), included, description and optional value (e.g. 23kg). For other, show the description.

offers[].fare_rulesobject

refundable and changeable (null when the airline does not say); penalties refund_penalty_amount/_currency/_type and change_penalty_amount/_currency/_type (type fixed, percentage or none); optional change_allowed_before_departure, change_allowed_after_departure, min_stay, max_stay, advance_purchase_required, advance_purchase_days, raw_text. restrictions[] lists each statement as the airline made it: action (refund or change), stage (before_departure, after_departure or absent), allowed, fee, percent, currency. The headline fields summarise the before-departure stage, or the only stage stated.

offers[].offer_idstringRequired

Offer ID. Opaque; it can contain | and ., so percent-encode it in a URL path.

offers[].parts[]array

The airline offer behind each ticket (offer_id, airline), in flying order, for reference. Price and book the trip by its own offer_id.

offers[].passengers[]array

Per passenger type: type (ADT, CHD, INF), count, and per-passenger base_fare, taxes, total (before service_fee).

offers[].quality_scorenumber

0.0–1.0 completeness of the offer's data. See quality scores.

offers[].self_transferboolean

A trip on separate tickets from two airlines (only when the search sent self_transfer). The traveller re-checks bags and no airline protects the connection: show this before purchase.

offers[].service_feenumber

Your commission (Dashboard → Commissions), included in total_amount. Absent when no rule applies.

offers[].slices[]arrayRequired

One per direction or multi-city bound: id, origin, destination, departure_utc, arrival_utc, duration_minutes (layovers included), stops, segments[].

offers[].slices[].segments[]arrayRequired

Flights: segment_id, origin, destination, departure_utc, arrival_utc, duration_minutes, flight_number, marketing_carrier, operating_carrier, aircraft, departure_terminal, arrival_terminal, cabin_class, booking_class, fare_basis, optional meal_service.

offers[].sourcestringRequired

ndc_direct, gds_fallback or lcc_direct.

offers[].source_versionstring

NDC (New Distribution Capability) schema version behind the offer, e.g. NDC-21.3.

offers[].taxes_feesnumberRequired

The airline's taxes and fees, all passengers.

offers[].total_amountnumberRequired

Total for all passengers: base_fare + taxes_fees + service_fee.

offers[].total_priceobject

total_amount as {amount, currency}.

offers[].transfers[]array

Each change of a self-transfer trip: airport, arrival_utc, departure_utc, layover_minutes (180–1440), overnight, from_airline, to_airline, recheck_baggage (always true).

search_idstringRequired

Id of this search (srch_…).

Error codes

400

Invalid input, with a code: invalid_date, date_out_of_range, return_before_departure, too_many_passengers, infants_exceed_adults, same_airport, passenger_cap or invalid_search. A body that is not JSON is 400 invalid request body; over 1 MiB is 413.

429

daily_search_limit_exceeded / monthly_search_limit_exceeded (a ceiling agreed for your account) or allowance_burst (free allowance, no card on file). Always with Retry-After.

500

search failed. Safe to retry.

502

all airline connections failed — please retry later: no airline could be reached.

POST /v1/shopping/offers — Search offers | Norba API