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.
Search offers
One request searches every connected airline and returns one list of offers in one schema.
/v1/shopping/offersRequires X-API-KeySearches 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_feeis your commission, absent when none applies. - The same search is answered from cache until the top-level
expires_at: the earliest offer'sexpires_atless five minutes, between 90 s and 30 min. Empty results are never cached. - No connected airline on the route:
200withoffers: null. - Bad input gets
400with acodebefore 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.airlineslists 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
{
"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
}
}Request body
adultsintegerRequiredAge 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).
1cabin_classstringeconomy, premium_economy, business or first; anything else is 400 invalid_search.
economychildrenintegerAge 2–11.
0currencystringISO 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.
EURdeparture_datestring (YYYY-MM-DD)RequiredOutbound date, from yesterday to 361 days ahead (UTC); outside it 400 date_out_of_range. Not needed with legs.
2027-06-15destinationstringRequiredIATA airport code. Must differ from origin (400 same_airport). Not needed with legs.
JFKinfantsintegerLap infants under 2; no more than adults (400 infants_exceed_adults).
0legsarray<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_stopsintegerAbsent: any. 0: non-stop only. 1–5: up to that many connections.
0originstringRequiredIATA airport code (3 letters). Not needed with legs.
BCNreturn_datestring (YYYY-MM-DD)Makes the search a round trip. On or after departure_date (400 return_before_departure).
2026-12-22self_transferbooleanAlso 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.
trueResponse fields
expires_atstring (RFC 3339)RequiredUntil when this answer is reused for the same search: the earliest offer's expires_at less five minutes, between 90 s and 30 min.
metaobjecttotal, airlines[], sources[] and min_price of the returned offers.
offersarray | nullRequiredThe offers, cheapest first. null when no airline returned any.
offers[].airlinestringRequiredIATA airline code, e.g. AA.
offers[].airline_offer_idstringThe airline's own offer ID, unchanged.
offers[].ancillaries_included[]arrayBag 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_includedbooleanRequiredWhether a checked bag is in the fare.
offers[].base_farenumberRequiredThe airline's base fare, all passengers.
offers[].cabin_classstringRequiredMain cabin of the itinerary.
offers[].currencystringRequiredISO 4217 code of every amount on the offer.
offers[].expires_atstring (RFC 3339)RequiredAfter it the offer cannot be priced or booked.
offers[].fare_familyobjectBranded 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_rulesobjectrefundable 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_idstringRequiredOffer ID. Opaque; it can contain | and ., so percent-encode it in a URL path.
offers[].parts[]arrayThe 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[]arrayPer passenger type: type (ADT, CHD, INF), count, and per-passenger base_fare, taxes, total (before service_fee).
offers[].quality_scorenumber0.0–1.0 completeness of the offer's data. See quality scores.
offers[].self_transferbooleanA 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_feenumberYour commission (Dashboard → Commissions), included in total_amount. Absent when no rule applies.
offers[].slices[]arrayRequiredOne per direction or multi-city bound: id, origin, destination, departure_utc, arrival_utc, duration_minutes (layovers included), stops, segments[].
offers[].slices[].segments[]arrayRequiredFlights: 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[].sourcestringRequiredndc_direct, gds_fallback or lcc_direct.
offers[].source_versionstringNDC (New Distribution Capability) schema version behind the offer, e.g. NDC-21.3.
offers[].taxes_feesnumberRequiredThe airline's taxes and fees, all passengers.
offers[].total_amountnumberRequiredTotal for all passengers: base_fare + taxes_fees + service_fee.
offers[].total_priceobjecttotal_amount as {amount, currency}.
offers[].transfers[]arrayEach 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_idstringRequiredId of this search (srch_…).
Error codes
400Invalid 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.
429daily_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.
500search failed. Safe to retry.
502all airline connections failed — please retry later: no airline could be reached.