Programmatic access to N8Go's flight inventory
Search, price and book flights from N8Go's aggregated supplier network over one REST API. Every price you receive already includes your account's own markup. You never see N8Go's underlying supplier cost. Hotels are joining the API next.
Authentication
Every request carries your API key in the x-api-key header. There's no bearer token and no OAuth handshake. The key itself is the credential.
# n8go_sandbox_... or n8go_live_... curl https://api.b2b.n8go.co.uk/v1/flights/search \ -H "x-api-key: {{apiKey}}" \ -H "content-type: application/json" \ -d '{"origin":"LHR","destination":"JFK","departureDate":"2026-11-15","passengers":{"adults":1}}'
| Header | Required | Notes |
|---|---|---|
x-api-key | required | Missing or invalid keys get a 401 unauthorized. A suspended account's key is treated the same as an invalid one. |
Environments & pricing
Your key is bound to exactly one environment. There's no request field to switch it. A sandbox key only ever reaches the sandbox pool, a live key only the live one.
| Environment | Key prefix | Backing inventory |
|---|---|---|
| Sandbox | n8go_sandbox_… | Fixture offers, deterministic per route and date. The same search always returns the same results. |
| Live | n8go_live_… | N8Go's real aggregated supplier pool. |
Wholesale pricing
Every offer's totalPrice is computed off N8Go's own net supplier cost, then marked up by your account's own rule: a flat default percentage, or a route-specific override where one exists. This is independent of what N8Go charges its own retail customers on n8go.co.uk. An account with no rule configured is marked up 0%.
Errors
Every error response shares one shape, regardless of endpoint.
{ "code": "not_found", "message": "Flight offer not found" }
| Status | code | Meaning |
|---|---|---|
| 400 | validation_failed | The request body failed schema validation. Check the accompanying issues array. |
| 400 | bad_request | A business rule rejected the request (e.g. an unavailable extra id). |
| 401 | unauthorized | Missing, invalid, revoked, or suspended-account key. |
| 404 | not_found | The resource, offer id, or provider namespace doesn't exist. |
| 409 | price_changed | The supplier re-priced the offer before booking completed. Re-fetch rather than retry. |
| 409 | offer_unavailable | The supplier withdrew the offer. Search again. |
| 409 | conflict | The booking or cancellation quote is in a state that doesn't allow this action (already cancelled, quote already used or expired). |
| 429 | rate_limited | Too many requests. Wait and retry. |
| 500 | internal_error | Something failed on our side. Safe to retry. |
Searches every provider in your environment's pool in parallel and returns the combined, marked-up offers. A provider that errors is dropped silently rather than failing the whole search.
| Field | Type | Required | Notes |
|---|---|---|---|
| origin | string | required | 3-letter IATA airport code. |
| destination | string | required | 3-letter IATA airport code. |
| departureDate | string | required | YYYY-MM-DD. |
| returnDate | string | optional | YYYY-MM-DD. Omit for one-way. |
| passengers | object | required | { adults, children, infants }. |
| cabinClass | string | optional | economy, premium_economy, business, or first. Defaults to economy. |
| currency | string | optional | 3-letter ISO code. Defaults to GBP. |
{ "offers": [{
"id": "kyte:7f3a...",
"totalPrice": { "amountMinor": 24200, "currency": "GBP" },
"slices": [{ "origin": "LHR", "destination": "JFK", "segments": [ /* ... */ ] }],
"owner": { "iataCode": "BA", "name": "British Airways" },
"conditions": { "refundable": false, "changeable": false },
"baggage": { "carryOnIncluded": 1, "checkedIncluded": 0 },
"totalStops": 0, "fareBrand": "Standard"
}] }
Re-fetches one offer by its id, confirming it's still available and returning its current authoritative price.
GET /v1/flights/offers/kyte%3A7f3a...
x-api-key: {{apiKey}}
{ "offer": { /* same shape as a search result */ } }
Re-fetches the offer and, if you pass the price you were last quoted, reports whether it's changed — useful as an explicit "is this still bookable at this price" check right before checkout.
// request body (optional) { "expectedPrice": { "amountMinor": 24200, "currency": "GBP" } } // response { "offer": { /* ... */ }, "priceChanged": false, "bookable": true }
Prices ancillaries (bags, seats) against an offer before you book. Stateless: nothing is persisted until you actually create a checkout session with the same selectedExtraIds.
// request { "selectedExtraIds": ["ext_bag1"] } // response { "extras": [{ "id": "ext_bag1", "type": "baggage", "title": "1 checked bag, 23kg", "price": { "amountMinor": 2500, "currency": "GBP" } }], "updatedTotalPrice": { "amountMinor": 26700, "currency": "GBP" } }
Prices one route across a week of candidate dates around your given date, one-way or round trip — built for a fare calendar, not a single results page.
| Field | Type | Required | Notes |
|---|---|---|---|
| origin, destination, departureDate, passengers | required | Same as search. | |
| returnDate | string | optional | Required if varying “return”. |
| varying | string | optional | departure (default) or return — which date the matrix varies. |
{ "matrix": [
{ "date": "2026-11-12", "totalPrice": { "amountMinor": 20400, "currency": "GBP" } },
{ "date": "2026-11-13", "totalPrice": null }
] }
totalPrice is null for a past date or a date nothing was bookable for.Also /v1/airlines/codes (just the IATA codes) and /v1/airlines/:iataCode (a single airline).
{ "airlines": [{ "iataCode": "BA", "name": "British Airways", "website": "https://www.britishairways.com" }] }
Add ?codes=LHR,JFK for a bulk lookup by code. /v1/airports/search?q= runs a live typeahead; /v1/airports/:iataCode fetches one.
{ "airports": [{ "iataCode": "LHR", "name": "Heathrow", "city": "London", "country": "United Kingdom" }] }
Creates a pending booking and a Stripe PaymentIntent. This is where booking creation actually happens — there's no separate "create booking" call, since no money exists yet at this point. N8Go's server never touches a raw card number: your own frontend collects payment with the returned clientSecret and publishableKey, using Stripe Elements.
| Field | Type | Required | Notes |
|---|---|---|---|
| offerId | string | required | From search, get-offer, or verify. |
| passengers | array | required | Full passenger details: name, date of birth, contact, nationality. |
| selectedExtraIds | array | optional | Extra ids priced via the services endpoint. |
{ "bookingId": "73e86be1-...", "clientSecret": "pi_..._secret_...", "publishableKey": "pk_..." }
Idempotency-Key header. Retrying the same request with the same key replays the original response instead of creating a second booking.return_url when calling Stripe's confirmPayment() with this clientSecret — Stripe requires one whenever redirect-based payment methods are enabled, which they are by default here.Call this once your frontend's Stripe confirmation succeeds. N8Go verifies the payment directly against Stripe, then books and tickets the reservation with the supplier. Safe to call more than once: a booking that's already confirmed is just returned as-is.
{ "booking": {
"id": "73e86be1-...", "status": "confirmed",
"totalPrice": { "amountMinor": 20700, "currency": "GBP" },
"airlineLocator": "MOCK24B09C", "offer": { /* ... */ },
"createdAt": "2026-09-18T17:26:19.216Z", "confirmedAt": "2026-09-18T17:27:49.199Z"
} }
status is one of pending_payment, confirming, confirmed, failed, or cancelled.
Also GET /v1/bookings to list your bookings, newest first — supports limit (default 20, max 100) and a before cursor for pagination, returning nextCursor when there's another page.
Returns whichever extras were actually attached at checkout — a plain read of what's persisted on the booking, not a fresh supplier call.
Prices a cancellation before you commit to it. Returns a quoteId good for a limited time (where the supplier reports an expiry).
{ "quoteId": "35c9bd46-...", "refund": { "amountMinor": 0, "currency": "GBP" } }
Cancels the booking. Pass a quoteId from the step above to redeem that exact quoted refund, or call it with no body to cancel directly at whatever the supplier currently offers.
{ "refund": { "amountMinor": 0, "currency": "GBP" } }
Not yet available
Built server-side but not reachable over the API yet:
| Capability | Status |
|---|---|
| Webhooks | booking.ticketed, booking.schedule_changed, booking.cancelled — HMAC-signed delivery with retry. |
| Per-key rate limiting | Configurable per account; not yet enforced on /v1. |
| Kyte-sourced offers | Kyte's LCC/NDC inventory joins the live pool once N8Go completes Kyte's own supplier certification. |
| Hotels | The same wholesale model and sandbox-first workflow, extended to hotel inventory. |
Postman collection
A collection covering every endpoint on this page: flights, reference data, and the full booking lifecycle.
https://doc.sandbox.n8go.co.uk/n8go-b2b-postman-collection.json
- Open Postman, click Import, choose the Link tab.
- Paste the URL above and import.
- Set the collection's
apiKeyvariable —baseUrlis already set tohttps://api.b2b.n8go.co.uk. - Run Search Flights, Get Offer, and the booking flow in order.