Changelog

What's new

Changes to the Norba API for travel agencies and booking integrations: endpoints, fields, error codes and webhooks.

Longer offer ids, schedule changes and seat maps

  • An offer_id may be up to 1,024 characters (was 512) wherever it is sent.
  • order.schedule_changed is sent only when the airline's notification moves a flight or the order's status.
  • 404 seat_map_segment_not_found carries the token in code as well as in error.

Email language on orders

  • POST /v1/orders takes an optional locale (en or es, default en): the language of the emails sent to the booking contact about that order.

Aegean Airlines and Olympic Air in the sandbox

  • Aegean Airlines (A3) is available in the sandbox for shopping, pricing, seats, services, orders and servicing. Olympic Air (OA) flights come in the same search, as Aegean offers.

Price calendar, search answers kept for the offers’ life, all-or-nothing trips

  • GET /v1/shopping/calendar (origin, destination, from, to) returns the cheapest fare per passenger your searches found on a route, per day, while the airlines still honour those offers.
  • With trip_type=round_trip and departure_date it returns prices per return day. It calls no airline; a day with no such search is absent.
  • A repeated search may be answered from cache until the search's expires_at; the stream's done event now carries it.
  • A self-transfer trip books every ticket or none: a refused payment cancels the tickets already paid. status partially_paid (HTTP 207) is gone.
  • A refused trip's trip.unreleased_orders names any ticket the airline kept. A trip not made sends no order.* webhooks.
  • A bag allowance's dimensions_cm now carries the size when the airline states it as a total linear size, where it read "0".

Self-transfer trips, fare rules as the airline states them, network airlines

  • POST /v1/shopping/offers takes self_transfer: true to also return trips across two connected airlines on separate tickets: offers with self_transfer, transfers[] and parts[].
  • max_stops counts the change of airline as a stop: max_stops 0 returns no self-transfer trips, and multi-city searches return none. A trip's fare_family name joins both airlines' families.
  • POST /v1/orders with a priced self-transfer trip answers a Trip (trip_id, status, orders[]); orders carry trip_id, and a single-order read lists the trip's tickets in trip[].
  • A trip refused part-way answers the failing ticket's error with trip.failed_ticket, failed_airline and released_orders.
  • Pricing a trip that no longer connects is 409 self_transfer_unavailable; seats or extras sent with a trip are 422 extras_not_supported.
  • fare_rules.restrictions[] lists each refund and change statement of the airline (action, stage, allowed, fee, percent, currency) as made. A stated fee is reported even beside a change marked not allowed.
  • The fare_rules headline (changeable, change_penalty_amount, refundable, refund_penalty_amount…) is the before-departure stage when the airline states stages separately, no longer the highest fee of any stage.
  • fare_family.benefits[].type can also be wifi, fast_track or other. other is a feature no type describes: show its description.
  • GET /v1/network/airlines lists the airlines a search with your key reaches, each with the carriers whose published networks are its own.

Card-only payments, Idempotency-Key on money writes, webhook envelope and replay

  • A payment must carry a card; any other method is 400 payment_method_not_allowed.
  • A paid POST /v1/orders needs the offer priced with POST /v1/offers/price in the last 30 minutes, in the same currency: 409 offer_not_priced. A hold without a payment is unaffected.
  • Seats and extras on PUT /v1/orders/{id} and …/commit need the order's seat map or service list read first: 422 extra_not_priced.
  • Contact emails on POST /v1/orders must be single plain addresses: 400 invalid_contact_email.
  • Every write that moves money needs an Idempotency-Key header: POST /v1/orders with a payment, PUT /v1/orders/{id} when it pays, …/commit, …/change/confirm, and …/seats and …/services with a payment.
  • Also DELETE /v1/orders/{id}, …/cancel/confirm and …/refund. Without the header: 400 idempotency_key_required. Use one UUID (Universally Unique Identifier) per operation and resend it on every retry.
  • An airline answer that confirms nothing is 502 *_outcome_unknown, no longer 422 airline_refused, and its Idempotency-Key stays held.
  • The API answers only at https://api.norba.io; any other host name is 421 misdirected_request.
  • Booking the same offer for the same travellers twice is 409 offer_already_ordered. A booking above the limit set for the member who places it is 403 booking_limit_exceeded.
  • New codes: 400 action_required and use_cancel_confirm on PUT /v1/orders/{id}, 409 refund_not_requested, 501 refund_not_supported and 503 ledger_unavailable.
  • Cancelling a ticketed American Airlines order with DELETE /v1/orders/{id} records the refund, penalty and currency in the order's history and in payments[].
  • Every webhook body is an envelope: id (also sent as X-Norba-Event-Id, stable across retries), type, created_at, sequence and data. The existing top-level fields are unchanged.
  • Redeliver one delivery with POST /v1/agency/{agencyID}/webhooks/{id}/deliveries/{deliveryID}/redeliver, or a period with POST …/webhooks/{id}/replay?since=….
  • Webhook deliveries list eventId, redeliveries, redeliveredAt and deadAt.
  • Status changes made by the airline now send their webhook: order.ticketed, order.cancelled, order.refunded or order.updated.

Payments never repeated, JSON errors on every route, webhook rotation and tests

  • Paying an order that is already paid is 409 already_paid; refunding one that was never paid is 409 nothing_to_refund.
  • When the airline's answer to a payment is lost, the booking comes back 201 as a hold with X-Norba-Payment-Outcome: unknown and X-Norba-Attempt-Id. Do not pay it again.
  • Until that payment is settled, writes that could charge the card answer 409 booking_outcome_unknown with Retry-After: 60.
  • POST /v1/offers/price takes at most 10 offer IDs of up to 512 characters each.
  • An unknown path is 404 not_found and a wrong method is 405 method_not_allowed with an Allow header, both in the usual JSON error shape.
  • Webhooks for the same order reach an endpoint in order. Each attempt has 10 seconds to be answered, and an endpoint is switched off after 50 failures in a row over at least a day.
  • For 24 hours after a webhook secret is rotated, X-Norba-Signature carries two v1 signatures (new and previous). Accept the delivery when either one matches.
  • A test event ("ping", test: true) can be sent to any webhook endpoint and appears in its deliveries. Webhook URLs with a user name or password are refused.

Sandbox and production kept apart

  • A key reads and services only orders of its own environment; an order from the other one is 404.
  • Webhook payloads carry environment, and each delivery an X-Norba-Environment header.
  • Idempotency-Key is scoped per environment.

Schedule changes: order.schedule_changed

  • A new webhook event, order.schedule_changed, fires when the airline retimes, renumbers or cancels a flight on a booking. It carries notification_id and changes[] (each flight before and after).
  • GET /v1/orders/{id} already shows the new flights when the event arrives.

Named refusals and safe retries on every write

  • A booking or servicing write the airline refuses is 422 with a named code or airline_refused, never 502.
  • A write that never reached the airline is 503 airline_unreachable with Retry-After: 5: resend it with the same Idempotency-Key.
  • Only one write at a time per order: another one meanwhile is 409 order_busy with Retry-After: 5.
  • Idempotency-Key is accepted on every servicing write; a repeat returns the first result with Idempotent-Replayed: true.
  • A write whose answer was lost is 502 booking_outcome_unknown (or the servicing variant, such as cancel_outcome_unknown) with attempt_id and Retry-After, and is never re-sent automatically.
  • An unpaid hold past expires_at is re-read from the airline on GET /v1/orders/{id}; paying it is 422 hold_expired.
  • Search refusals carry codes: invalid_date, date_out_of_range, return_before_departure, too_many_passengers, infants_exceed_adults, same_airport, invalid_search and passenger_cap.
  • Search dates are accepted from yesterday to 361 days ahead, in UTC (Coordinated Universal Time).
  • A seat for an infant is refused with 400. Order history events carry their payload.
  • departure_utc and arrival_utc are RFC 3339 instants in the airport's own offset.
  • A penalty the airline states as a percentage is reported as that percentage, with change_penalty_type or refund_penalty_type percentage.
  • Aviation Data lists accept page up to 1000; beyond it, 400 invalid_page.

No per-minute rate limit on API keys

  • API keys have no per-minute rate limit. rateLimitPerMinute is no longer accepted or returned on agency API keys.

Airline route networks and airport time zones

  • New Aviation Data endpoints: GET /v1/aviation/airlines/{id}/routes, /airlines/{id}/network, GET /v1/aviation/networks and GET /v1/aviation/routes?origin=&destination=.
  • Every IATA (International Air Transport Association) airport carries a timezone. Airports add metro_code, location_type, page_rank and served_by_schedule.
  • GET /v1/aviation/airports?q= ranks free-text matches, and commercial=true means served by a published airline schedule.

Offer reads take offer IDs only

  • GET /v1/offers/{offerID}/seats and /services take offer IDs only: an order ID is 404 offer_not_found. An order's seat map and extras are at GET /v1/orders/{orderID}/seats and /services.

Card-only refusals named

  • A payment the airline takes by card only, sent another way, is 422 card_required.

Seats, extras and history on orders; partial cancellations

  • New order routes: GET /v1/orders/{id}/history, and GET and POST /v1/orders/{id}/seats and /services. The POST prices only from what the GET returned: 422 extra_not_priced.
  • POST …/cancel and …/cancel/confirm take segment_ids to cancel some flights only; the order becomes partially_cancelled. When that is not possible: 422 partial_cancel_unavailable.
  • POST …/change/confirm takes a payment, for a change that costs more.
  • POST …/refund answers 202 with refund_amount, penalty_amount and currency.
  • A reshop that names only a new date keeps the order's route. No fares to change to is 422 no_alternatives.

Named refusals, order history and refund breakdown

  • A refusal the airline explains is 422 with a code (flights_unavailable or offer_expired) and a readable message, instead of 502.
  • GET /v1/orders/{id} includes the order's history[].
  • GET …/refund-eligibility adds booking_amount, not_quoted_amount, ancillaries_amount and service_fee_amount.
  • payment.card.brand is optional: when it is absent, the brand is read from the card number.

Tickets and payments on orders

  • Orders list the airline's documents in tickets[], each with type ticket or emd (Electronic Miscellaneous Document, issued for a paid seat or bag), and its payment results in payments[].

Airline-specific agreements

  • Some airlines require their own agreement before production bookings. Until it is approved, a production search leaves that airline's offers out and booking it is 403 carrier_agreement_required. The sandbox is not affected.

Orders read back from the airline

  • GET /v1/orders/{id}?refresh=true asks the airline for the order and stores what it says before answering.

Cancellation amounts

  • refund_amount on POST /v1/orders/{id}/cancel is what comes back, net of penalty_amount, as on …/refund-eligibility.
  • A cancelled order keeps the price and currency it was sold at.

did_you_mean on unknown order IDs

  • An unknown order ID is 404 order_not_found and, when one of your orders is one character away, includes did_you_mean.
  • A booking the airline confirmed but that could not be stored still answers 201, with a warning and X-Norba-Order-Persisted: false. Keep its PNR (Passenger Name Record).

Sandbox and production keys

  • API keys carry an environment: nbr_test_ keys reach the airlines' test systems, nbr_live_ keys production. A production key before go-live is 403 production_not_enabled.
  • The sandbox has the same endpoints, schema and errors as production.

Commercial airports

  • GET /v1/aviation/airports?commercial=true returns only commercial airports.

Order webhooks

  • Order webhooks: order.created, order.updated, order.ticketed, order.cancelled, order.refunded and order.ticketing_deadline_approaching. See the Webhooks guide.
  • Each delivery is signed with HMAC-SHA256 (Hash-based Message Authentication Code) in X-Norba-Signature.

Pagination, idempotent bookings and API key restrictions

  • GET /v1/orders uses cursor pagination: limit (1–100, default 20), cursor and next_cursor.
  • Order routes infer the airline from the order; an ?airline= that does not match is 400.
  • POST /v1/orders accepts an Idempotency-Key header: a repeat returns the first order with Idempotent-Replayed: true, or 409 idempotency_key_in_flight or idempotency_key_reused.
  • Seats and extras on POST /v1/orders are priced from the seat map and service list the API returned: 422 extra_not_priced otherwise.
  • API keys can be restricted by IP allowlist, allowed paths, scopes and expiry (403 outside a restriction, 401 once expired).

Orders of other agencies

  • Every order route answers 404 for an order ID your agency does not hold.

Loyalty, corporate IDs and special services

  • Passengers take loyalty_programs (frequent-flyer numbers), and POST /v1/orders takes corporate_id.
  • Seat maps and service lists take ?loyalty=; service lists take ?ssr= (special service request codes, such as WCHR) and seat maps ?segment_id=.

Basket quote

  • POST /v1/offers/quote prices an offer together with its chosen seats and extras, as the total that will be charged.

Change confirmation; two Aviation Data endpoints removed

  • POST /v1/orders/{id}/change/confirm takes offer_id, the offer chosen from …/reshop; without it, 400.
  • GET /v1/aviation/aircraft/icao24/{icao24} and GET /v1/aviation/airlines/{id}/fleet-summary are removed.

Seats and extras at booking; snake_case request bodies

  • POST /v1/orders takes seats and ancillaries, sold together with the flight.
  • The payment card and the bodies of PUT /v1/orders/{id}, …/commit and …/reshop are snake_case (segment_ids, new_departure_date, expiry_month…); multi-word CamelCase keys are no longer read.

Streaming search

  • POST /v1/shopping/offers/stream returns each airline's offers as soon as it answers, as SSE (Server-Sent Events): search, offers (repeated), then done or error.

American Airlines in the sandbox

  • American Airlines (AA) is available in the sandbox for shopping, pricing, seats, services, orders and servicing.

First release

  • REST API at api.norba.io/v1, authenticated with the X-API-Key header.
  • Aviation Data: airports (list, detail, full profile, runways, frequencies, navaids, timezone), airlines and aircraft types.
  • POST /v1/shopping/offers searches every connected airline: one-way and return, adult (ADT), child (CHD) and infant (INF) passengers, cabin and currency.
  • POST /v1/offers/price, GET /v1/offers/{offerID}/seats and GET /v1/offers/{offerID}/services.
  • Orders: create, list, retrieve, pay, change, reshop, cancel and refund.

Be the first to know when the API is ready for testing.

NORBA TRAVEL OÜ (Norba) sends your address to our team's inbox only to tell you when the API is ready for testing; it goes on no mailing list. More in our Privacy Policy.

API Changelog: Releases & Breaking Changes | Norba Docs