Neptune Flight Booking API
A clean B2B flight booking API: search → fare rules → create booking → passengers → reprice → book. One contract, sensible defaults, predictable responses.
This documentation covers every endpoint a partner integration needs, from authentication through to booking confirmation. The complete flow uses 8 endpoints; you can run a working search-to-book sequence in under a minute once your API key is set up.
Base URL
https://<your-neptune-host>Sandbox and production hosts are issued separately as part of partner onboarding.
What this API gives you
- Universal contract. One response shape across one-way, round-trip, domestic, and international.
- Stable identifiers. Once a booking is created you get a permanent
booking_idyou can reference forever. - Synchronous booking. No webhooks or polling —
bookreturns the PNR in the response. - Audit-friendly pricing. Every response carries the raw pre-markup fare under
pricing.rawso reconciliation is straightforward.
Authentication
Every request must include your API key in the Authorization header. Requests without a valid key receive 401 Unauthorized.
Authorization: Api-Key <your_raw_key>
Sample authenticated request
curl -X POST https://<host>/v1/flights/search/ \
-H "Authorization: Api-Key YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{ ... }'
Quick Start
Five steps from zero to a confirmed PNR:
- Search for flights → grab
trace_id+ aresult_index - Fare Rules (optional) → review cancellation policy
- Create Booking → lock fares, get
booking_id - Save Passengers → submit traveller details
- Reprice (optional) → confirm fare hasn't moved
- Book → issue PNR
book can be repeated. A trace_id expires after ~15 minutes; a booking_id remains addressable indefinitely.Flight Search
/v1/flights/search/
Searches for available flights. Returns up to several hundred options. The exact shape varies slightly for international round-trip — see the dedicated section below.
Common request body
| Field | Type | Description |
|---|---|---|
| direct_flight | string | "true" or "false" required |
| adult_count | string | Integer string, max 9 required |
| child_count | string | Integer string, max 9 required |
| infant_count | string | Integer string, max 9 required |
| flight_cabin_class | string | "1"=All "2"=Economy "3"=Premium Economy "4"=Business "5"=Premium Business "6"=First optional |
| journey_type | string | "1"=One-way "2"=Round-trip "3"=Multi-city required |
| preferred_departure_time | string | ISO format YYYY-MM-DDTHH:mm:ss required |
| preferred_return_departure_time | string | Required for round-trip; same format as above optional |
| origin | string | 3-letter IATA airport code (e.g. "DEL") required |
| destination | string | 3-letter IATA airport code (e.g. "BOM") required |
trace_id returned here is required for every subsequent call in the booking chain (fare-rules, create-booking). After 15 minutes the trace_id expires and you must issue a fresh search.The four scenarios
The API handles all four canonical booking scenarios. Pick the tab that matches your route:
Domestic one-way · journey_type=1 · isDomestic=true
The simplest case. Search returns a single list of outbound options.
Request
{
"direct_flight": "false",
"adult_count": "1",
"child_count": "0",
"infant_count": "0",
"flight_cabin_class": "1",
"journey_type": "1",
"preferred_departure_time": "2026-06-15T00:00:00",
"origin": "DEL",
"destination": "BOM"
}Response (abridged)
{
"trace_id": "b9754c9f-8bdc-4300-97db-696031eec680",
"currency": "INR",
"journey_type": "one_way",
"is_domestic": true,
"pax_count": { "adult": 1, "child": 0, "infant": 0 },
"outbound": [
{
"result_index": "04726d37-c4d8-40fe-850f-784c5a338ccf",
"is_refundable": true,
"stop_count": 0,
"fare_name": "Published",
"fare_class": "PUBLISHED",
"airline_remark": null,
"group_id": 74,
"pricing": {
"currency": "INR",
"base_fare": 4837.50,
"service_fee": 0.0,
"published_fare": 5375.00,
"final_fare": 5375.00,
"markup_percent": 7.5,
"raw": {
"base_fare": 4500.0,
"service_fee": 0.0,
"published_fare": 5000.0,
"final_fare": 5000.0
}
},
"segments": [
{
"airline": { "code": "AI", "name": "Air India", "flight_number": "814", "fare_class": "S" },
"origin": { "airport_code": "DEL", "airport_name": "Indira Gandhi Intl", "city_code": "DEL", "city_name": "Delhi", "country_name": "India", "terminal": "Terminal 3", "departure_time": "2026-06-15T22:30:00" },
"destination": { "airport_code": "BOM", "airport_name": "Chhatrapati Shivaji", "city_code": "BOM", "city_name": "Mumbai", "country_name": "India", "terminal": "Terminal 2", "arrival_time": "2026-06-16T00:45:00" },
"cabin_class": "economy",
"duration_minutes": 135,
"ground_time_minutes": 0,
"baggage_allowance": "25 Kg (01 Piece only)",
"cabin_baggage_allowance": "7 Kg",
"seats_available": 9
}
]
}
],
"inbound": []
}To book this flight, send result_indices: ["04726d37-c4d8-40fe-850f-784c5a338ccf"] to /v1/flights/booking/.
Domestic round-trip · journey_type=2 · isDomestic=true
Outbound and inbound options are independent lists at the top level. Pick any outbound and any inbound — they pair freely.
Request
{
"direct_flight": "false",
"adult_count": "1",
"child_count": "0",
"infant_count": "0",
"flight_cabin_class": "1",
"journey_type": "2",
"preferred_departure_time": "2026-06-15T00:00:00",
"preferred_return_departure_time": "2026-06-20T00:00:00",
"origin": "DEL",
"destination": "BOM"
}Response (abridged)
{
"trace_id": "b9754c9f-8bdc-4300-97db-696031eec680",
"currency": "INR",
"journey_type": "round_trip",
"is_domestic": true,
"pax_count": { "adult": 1, "child": 0, "infant": 0 },
"outbound": [
{
"result_index": "04726d37-c4d8-40fe-850f-784c5a338ccf",
"pricing": { "final_fare": 5375.00, "raw": { "final_fare": 5000.0 }, "...": "..." },
"segments": [ { "origin": { "airport_code": "DEL" }, "destination": { "airport_code": "BOM" }, "...": "..." } ]
}
],
"inbound": [
{
"result_index": "bc3df4cf-4d9e-4e8a-86ba-8e7defca0b4d",
"pricing": { "final_fare": 5163.18, "raw": { "final_fare": 4803.0 }, "...": "..." },
"segments": [ { "origin": { "airport_code": "BOM" }, "destination": { "airport_code": "DEL" }, "...": "..." } ]
}
]
}/v1/flights/booking/:"result_indices": ["<outbound_rI>", "<inbound_rI>"]International one-way · journey_type=1 · isDomestic=false
Structurally identical to domestic one-way. The only differences are downstream — passenger data must include passport details when you save passengers.
Request
{
"direct_flight": "false",
"adult_count": "1",
"child_count": "0",
"infant_count": "0",
"flight_cabin_class": "1",
"journey_type": "1",
"preferred_departure_time": "2026-06-15T00:00:00",
"origin": "DEL",
"destination": "DXB"
}Response (abridged)
{
"trace_id": "d380f8d1-3406-40c8-808a-618ab89f080a",
"currency": "INR",
"journey_type": "one_way",
"is_domestic": false,
"pax_count": { "adult": 1, "child": 0, "infant": 0 },
"outbound": [
{
"result_index": "eb0dd017-c953-408e-b85d-05dbf4f333f3",
"pricing": {
"currency": "INR",
"base_fare": 14818.13,
"service_fee": 0.0,
"published_fare": 17337.60,
"final_fare": 17337.60,
"markup_percent": 7.5,
"raw": { "base_fare": 13785.0, "published_fare": 16128.0, "final_fare": 16128.0, "service_fee": 0.0 }
},
"segments": [
{
"airline": { "code": "AI", "name": "Air India", "flight_number": "929", "fare_class": "T" },
"origin": { "airport_code": "DEL", "city_name": "Delhi", "country_name": "India", "terminal": "Terminal 3", "departure_time": "2026-06-15T07:00:00" },
"destination": { "airport_code": "DXB", "city_name": "Dubai", "country_name": "United Arab Emirates", "terminal": "Terminal 1", "arrival_time": "2026-06-15T09:30:00" },
"cabin_class": "economy",
"duration_minutes": 240,
"baggage_allowance": "25 KG",
"cabin_baggage_allowance": "7 Kg",
"seats_available": 9
}
]
}
],
"inbound": []
}International round-trip · journey_type=2 · isDomestic=false
Outbound and inbound are coupled. Each outbound option carries its own list of compatible return options under inbound_options. The top-level inbound[] is empty.
- Outbound pricing is "starting from" (cheapest paired inbound).
- Each
inbound_options[j]has the actual combined fare for that outbound + that specific inbound. - To book, pick one
inbound_options[j]and send only itsresult_index— both legs are reconstructed from it.
Request
{
"direct_flight": "false",
"adult_count": "1",
"child_count": "0",
"infant_count": "0",
"flight_cabin_class": "1",
"journey_type": "2",
"preferred_departure_time": "2026-06-15T00:00:00",
"preferred_return_departure_time": "2026-06-20T00:00:00",
"origin": "DEL",
"destination": "DXB"
}Response (abridged)
{
"trace_id": "7dbc004b-e2f7-4e51-b6d1-1e15b91b930b",
"currency": "INR",
"journey_type": "round_trip",
"is_domestic": false,
"outbound": [
{
"result_index": "f54e4dff-0efc-4b82-81a5-71d0fc59d6d1",
"pricing": { "final_fare": 16428.15, "raw": { "final_fare": 15282.0 }, "...": "..." },
"segments": [ { "origin": { "airport_code": "DEL" }, "destination": { "airport_code": "DXB" }, "...": "..." } ],
"inbound_options": [
{
"result_index": "f54e4dff-0efc-4b82-81a5-71d0fc59d6d1",
"pricing": { "final_fare": 16428.15, "raw": { "final_fare": 15282.0 }, "...": "..." },
"segments": [ { "origin": { "airport_code": "DXB" }, "destination": { "airport_code": "DEL" }, "...": "..." } ]
},
{
"result_index": "57e7968f-64e7-4a22-bae7-ff0391afcbb5",
"pricing": { "final_fare": 16428.15, "raw": { "final_fare": 15282.0 }, "...": "..." },
"segments": [ { "origin": { "airport_code": "DXB" }, "destination": { "airport_code": "DEL" }, "...": "..." } ]
}
]
}
],
"inbound": []
}result_index is a display-only "starting from" token (copied from the cheapest paired inbound). Do not send it alongside the inbound's result_index. Just send one entry to /v1/flights/booking/:"result_indices": ["<chosen inbound_options[j].result_index>"]Fare Rules
/v1/flights/fare-rules/
Retrieves cancellation and change rules for a specific flight result before locking the booking. Optional but recommended.
Request body
| Field | Type | Description |
|---|---|---|
| trace_id | string | From the search response required |
| result_index | string | The flight option's id required |
Sample request
{
"trace_id": "b9754c9f-8bdc-4300-97db-696031eec680",
"result_index": "04726d37-c4d8-40fe-850f-784c5a338ccf"
}Sample response
{
"trace_id": "b9754c9f-8bdc-4300-97db-696031eec680",
"result_index": "04726d37-c4d8-40fe-850f-784c5a338ccf",
"expires_at": "2026-05-14T10:35:00",
"remaining_seconds": 845,
"rules": [
{
"airline": "AI",
"origin": "DEL",
"destination": "BOM",
"rule_text": "Cancellation charges: INR 3,500 + Airline Fee. Date change: INR 3,000 + fare difference. ..."
}
]
}Create Booking
/v1/flights/booking/
Locks fares with the airline and creates a Neptune Booking record. Returns a stable booking_id used for every subsequent step.
Request body
| Field | Type | Description |
|---|---|---|
| trace_id | string | From the search response required |
| result_indices | array | List of result IDs. See per-scenario rules below. required |
How many result_indices to send
| Scenario | Count | Composition |
|---|---|---|
| Domestic one-way | 1 | The outbound's result_index |
| Domestic round-trip | 2 | [outbound.result_index, inbound.result_index] |
| International one-way | 1 | The outbound's result_index |
| International round-trip | 1 | The chosen inbound_options[j].result_index only |
Sample request (domestic round-trip)
{
"trace_id": "b9754c9f-8bdc-4300-97db-696031eec680",
"result_indices": [
"04726d37-c4d8-40fe-850f-784c5a338ccf",
"bc3df4cf-4d9e-4e8a-86ba-8e7defca0b4d"
]
}Sample response
{
"booking_id": "527668c2-9b07-47f2-9940-0d27a176acd4",
"trace_id": "b9754c9f-8bdc-4300-97db-696031eec680",
"itinerary_code": "itrjieq",
"status": "draft",
"is_domestic": true,
"pax_count": { "adult": 1, "child": 0, "infant": 0 },
"expires_at": "2026-05-14T10:35:00",
"remaining_seconds": 1798,
"pricing": {
"currency": "INR",
"base_fare": 9263.28,
"service_fee": 0.0,
"tax_and_surcharge": 1485.65,
"published_fare": 10748.93,
"final_fare": 10748.93,
"previous_total_amount": 10748.93,
"price_changed": false,
"markup_percent": 7.5,
"raw": {
"base_fare": 8617.0,
"tax_and_surcharge": 1382.0,
"published_fare": 9999.0,
"final_fare": 9999.0,
"previous_total_amount": 9999.0,
"service_fee": 0.0
}
},
"pax_rules": {
"leadPax": {
"dateOfBirth": { "isVisible": true, "isMandatoryIfVisible": true },
"contactNumber": { "isVisible": true, "isMandatoryIfVisible": true },
"email": { "isVisible": true, "isMandatoryIfVisible": true },
"passportNumber": { "isVisible": true, "isMandatoryIfVisible": false },
"nationality": { "isVisible": true, "isMandatoryIfVisible": false },
"gstNumber": { "isVisible": true, "isMandatoryIfVisible": false }
}
},
"itinerary_items": [
{
"item_code": "itmqmy2",
"result_index": "04726d37-c4d8-40fe-850f-784c5a338ccf",
"journey_type": "round_trip",
"is_refundable": true,
"fare_name": "Published",
"fare_class": null,
"airline_remark": null,
"legs": [
{ "direction": "outbound", "stop_count": 0, "segments": [ /* ... */ ] }
]
},
{
"item_code": "itmqmyc",
"result_index": "bc3df4cf-4d9e-4e8a-86ba-8e7defca0b4d",
"journey_type": "round_trip",
"is_refundable": true,
"legs": [
{ "direction": "inbound", "stop_count": 0, "segments": [ /* ... */ ] }
]
}
]
}pax_rules before submitting passengers — it tells you which passenger fields are mandatory for this specific booking. International flights, business class, and certain airline-airport pairs each have different rules.expires_at and remaining_seconds from the response to track the window. Submit passengers and book within that time.Save Passengers
/v1/flights/booking/{booking_id}/passengers/
Submits traveller details for the booking. Optionally includes SSR (Special Service Requests — meals, baggage, seats). Validation runs both at the API layer and against the airline's mandatory-field rules (returned in pax_rules from create-booking).
Passenger object
| Field | Type | Description |
|---|---|---|
| title | string | "Mr", "Ms", "Mrs" required |
| first_name | string | required |
| last_name | string | required |
| gender | string | "male", "female", "other" required |
| pax_type | string | "adult", "child", or "infant" required |
| dob | string | Date of birth, YYYY-MM-DD required |
| is_lead | boolean | Exactly one passenger per booking must be lead required |
| string | Lead passenger only — required by paxRules optional | |
| contact_number | string | Lead passenger only — required by paxRules optional |
| cell_country_code | string | e.g. "91" (no leading "+") optional |
| nationality | string | ISO country code (e.g. "IN"); often required for intl optional |
| address_line_one | string | optional |
| address_line_two | string | optional |
| city | string | optional |
| country_code | string | ISO 2-letter (e.g. "IN") optional |
| country_name | string | optional |
| passport_number | string | Required for international bookings optional |
| passport_expiry | string | YYYY-MM-DD; required for international optional |
| passport_issue_date | string | YYYY-MM-DD; often required for international optional |
| frequent_flyer_airline_code | string | If providing FF, send both fields optional |
| frequent_flyer_number | string | optional |
| gst_company_*, gst_number | string | Lead pax only; all-or-none — partial GST is rejected optional |
| ssr | object | Special Service Requests; see the With SSR tab optional |
Plain request — no SSR
The most common case. Just the passenger details.
{
"passengers": [
{
"title": "Mr",
"first_name": "Aman",
"last_name": "Sharma",
"gender": "male",
"pax_type": "adult",
"dob": "1990-01-15",
"is_lead": true,
"nationality": "IN",
"email": "aman@example.com",
"contact_number": "9876543210",
"cell_country_code": "91",
"address_line_one": "12 MG Road",
"address_line_two": "Indiranagar",
"city": "Bangalore",
"country_code": "IN",
"country_name": "India",
"passport_number": "T1234563",
"passport_expiry": "2030-12-06",
"passport_issue_date": "2020-12-06"
}
]
}Request with SSR (meal · baggage · seat)
Add an ssr object to the passenger. Each SSR type is an independent array — include only what you need.
| Field | Type | Description |
|---|---|---|
| ssr.meal[] | array | Meal preferences with cost |
| ssr.baggage[] | array | Extra baggage purchases |
| ssr.seat[] | array | Seat selection with cost |
SSR item fields
| Field | Type | Description |
|---|---|---|
| code | string | SSR code (e.g. "VCSW" for veg meal) required |
| origin | string | 3-letter IATA — the segment this applies to required |
| destination | string | 3-letter IATA — segment destination required |
| amt | integer | Cost in INR optional |
| seat | string | Seat code (e.g. "4B") — for ssr.seat[] entries only optional |
{
"passengers": [
{
"title": "Mr",
"first_name": "Aman",
"last_name": "Sharma",
"gender": "male",
"pax_type": "adult",
"dob": "1990-01-15",
"is_lead": true,
"nationality": "IN",
"email": "aman@example.com",
"contact_number": "9876543210",
"cell_country_code": "91",
"ssr": {
"meal": [
{ "code": "VCSW", "origin": "DEL", "destination": "BOM", "amt": 400 }
],
"baggage": [
{ "code": "IXBB", "origin": "DEL", "destination": "BOM", "amt": 3000 }
],
"seat": [
{ "code": "4B", "origin": "DEL", "destination": "BOM", "amt": 450, "seat": "4B" }
]
}
}
]
}Sample response
{
"booking_id": "527668c2-9b07-47f2-9940-0d27a176acd4",
"trace_id": "b9754c9f-8bdc-4300-97db-696031eec680",
"itinerary_code": "itrjieq",
"status": "passenger_saved",
"is_domestic": true,
"pax_count": { "adult": 1, "child": 0, "infant": 0 },
"pricing": { "...": "..." },
"itinerary_items": [ "..." ],
"duplicates_found": false,
"duplicates": []
}Duplicate booking detection
The system flags potential duplicates (same lead passenger name + same sector + same date) from the last 7 days. When triggered, the response includes:
{
"duplicates_found": true,
"duplicates": [
{
"pnr": "9DYP4N",
"status": "CONFIRMED",
"bms_booking_code": "tk34z",
"member_code": "mjcoq",
"origin_city_code": "DEL",
"destination_city_code": "BOM",
"created_at": "2026-05-12T18:21:32.000Z"
}
]
}The booking is not blocked — your application decides whether to proceed to /book/ or surface a warning to the user.
Reprice
/v1/flights/booking/{booking_id}/reprice/
Revalidates the price with the airline before committing. If the fare has moved since create-booking, pricing.price_changed is true and your application should confirm with the user before booking.
Request
No request body required — the booking_id is in the path.
curl -X POST https://<host>/v1/flights/booking/527668c2-.../reprice/ \
-H "Authorization: Api-Key YOUR_KEY"Sample response
{
"booking_id": "527668c2-9b07-47f2-9940-0d27a176acd4",
"status": "priced",
"pricing": {
"currency": "INR",
"base_fare": 9263.28,
"tax_and_surcharge": 1485.65,
"published_fare": 10748.93,
"final_fare": 10748.93,
"previous_total_amount": 10748.93,
"price_changed": false,
"markup_percent": 7.5,
"raw": { "...": "..." }
},
"itinerary_items": [ "..." ]
}Book
/v1/flights/booking/{booking_id}/book/
Issues the PNR. Synchronous — the response carries the confirmation. Once this returns 200, the booking is committed with the airline.
Request
curl -X POST https://<host>/v1/flights/booking/527668c2-.../book/ \
-H "Authorization: Api-Key YOUR_KEY"Sample response
{
"booking_id": "527668c2-9b07-47f2-9940-0d27a176acd4",
"status": "confirmed",
"pnr": "ABC123",
"bms_booking_code": "tk34z",
"itinerary_code": "itrjieq",
"is_domestic": true,
"pax_count": { "adult": 1, "child": 0, "infant": 0 },
"pricing": { "...": "..." },
"itinerary_items": [ "..." ]
}book fails upstream, the booking moves to status failed — not left in limbo. Inspect GET /v1/flights/booking/{id}/ for context, or retry with a fresh create-booking.Get Booking
/v1/flights/booking/{booking_id}/
Returns the current state of a booking — including the latest pricing and PNR if confirmed.
Sample response
{
"booking_id": "527668c2-9b07-47f2-9940-0d27a176acd4",
"trace_id": "b9754c9f-...",
"itinerary_code": "itrjieq",
"status": "confirmed",
"pnr": "ABC123",
"is_domestic": true,
"pricing": { "...": "..." },
"itinerary_items": [ "..." ]
}List Bookings
/v1/bookings/?limit=25&offset=0
Paginated list of your own bookings. Most recent first.
Query parameters
| Param | Type | Description |
|---|---|---|
| limit | integer | Page size, max 100 (default 25) |
| offset | integer | Skip N items |
Sample response
{
"count": 132,
"next": "https://<host>/v1/bookings/?limit=25&offset=25",
"previous": null,
"results": [
{
"id": "527668c2-9b07-47f2-9940-0d27a176acd4",
"status": "confirmed",
"pnr": "ABC123",
"trace_id": "b9754c9f-...",
"itinerary_code": "itrjieq",
"currency": "INR",
"amount_raw": 9999.0,
"amount_charged": 10748.93,
"markup_percent_applied": 7.5,
"passengers": [ "..." ],
"flight_segments": [ "..." ],
"created_at": "2026-05-14T10:12:33Z",
"modified_at": "2026-05-14T10:15:01Z"
}
]
}Booking Lifecycle
Every booking moves through a deterministic set of statuses. Each transition is recorded on the booking's event log for audit.
| Status | Set by | Meaning |
|---|---|---|
| draft | create-booking | Fares locked with the airline; no passengers yet. |
| passenger_saved | save-passengers | Travellers submitted; ready to reprice or book. |
| priced | reprice | Latest fare quote from the airline. Inspect pricing.price_changed. |
| confirmed | book | PNR issued. Terminal success state. |
| failed | book (on upstream error) | Upstream rejected the booking. Inspect the response for details. |
| cancelled | (cancellation flow — coming soon) | Booking cancelled with the airline. |
Universal Response Shape
Every successful response shares a small set of conventions. Knowing them once means you don't have to learn them per endpoint.
Pricing object
Found on every flight option, itinerary, and booking response:
{
"currency": "INR",
"base_fare": 4837.50, // post-markup
"service_fee": 0.0,
"published_fare": 5375.00,
"final_fare": 5375.00, // amount you will be charged
"markup_percent": 7.5,
"raw": { // ORIGINAL pre-markup amounts — your reconciliation source
"base_fare": 4500.0,
"service_fee": 0.0,
"published_fare": 5000.0,
"final_fare": 5000.0
}
}pricing.raw is your reconciliation source of truth — it carries the original pre-markup amounts. Top-level fields are what you will be billed.
Error envelope
All non-2xx responses share the same shape:
{
"success": false,
"errors": [
{ "detail": ["Authentication credentials were not provided."] }
]
}Error Handling
| Status | Meaning | Common causes |
|---|---|---|
| 400 | Bad Request | Invalid payload — missing fields, invalid date format, bad cabin class, or other request validation failures. |
| 401 | Unauthorized | Missing, malformed, or revoked API key. |
| 404 | Not Found | Booking ID doesn't exist or doesn't belong to your partner account. |
| 429 | Too Many Requests | You've exceeded your rate limit. Back off and retry with exponential delay. |
| 502 | Bad Gateway | Upstream provider error. Often transient — retry once. If persistent, the upstream message is in errors[0].detail. |
| 500 | Internal Server Error | Bug on our side. Save the X-Request-Id response header and reach out. |
X-Request-Id header. Include it when reporting issues — it lets us trace your request through every layer in under a minute.Rate Limits
| Endpoint | Per-partner limit |
|---|---|
/v1/flights/search/ | 60 requests/min |
/v1/flights/booking/{id}/book/ | 30 requests/min |
| All others | 120 requests/min (combined) |
Higher limits are available on request as part of partner onboarding. Long-term sustained load is typically bursty — short spikes well above these are acceptable; sustained breach triggers 429.
FAQ
How do I get an API key?
API keys are issued by Neptune after a partnership agreement is signed. Once onboarded, you receive a unique key per environment (sandbox + production). Reach out to your account contact to start the process.
Can my markup percentage be customised?
Yes — markup is configured per partner at the time of onboarding. Currently we support a single flat percentage applied uniformly to all bookings. Per-route or per-cabin-class markup is on the roadmap.
How long is a search result valid?
Each search response is valid for exactly 15 minutes. The trace_id from your search response is required for every subsequent call in the booking chain (fare-rules, create-booking). After 15 minutes the trace_id expires and you must issue a fresh search. Once you've called create-booking, the locked fares remain valid typically for another 15–30 minutes (track expires_at / remaining_seconds).
What happens if a duplicate booking is detected?
The save-passengers response includes duplicates_found: true and a list of matching recent bookings. Neptune does not block the booking — your application chooses whether to proceed to /book/ or warn the user first. Duplicate detection is based on lead passenger name + sector + date within the last 7 days.
Can I cancel a confirmed booking via the API?
Cancellation is not exposed yet via the v1 API. To cancel, contact support with the booking_id and PNR. A native cancel endpoint is planned for v1.1.
Can I see fare rules before locking a booking?
Yes — call POST /v1/flights/fare-rules/ with the trace_id and the result_index. This is a read-only check; it doesn't consume any inventory and is free of charge.
Are passport details mandatory for international flights?
Generally yes. The exact requirements are returned by create-booking under pax_rules.leadPax and pax_rules.adult. Look for fields where isVisible: true and isMandatoryIfVisible: true. For most international airlines, passportNumber, passportExpiry, and nationality are required.
How do I add a meal, seat, or extra baggage to a booking?
Include an ssr object in each relevant passenger when calling save-passengers. Meal, baggage, and seat are independent — include only what's needed. See the With SSR tab under Save Passengers.
Is there a sandbox?
Yes. Sandbox is provisioned alongside your production credentials. Bookings issued in sandbox are not real PNRs and won't be ticketed. Use sandbox to validate your integration end-to-end; production is enabled after sign-off.
What's the test card / payment flow?
Payment collection happens on your side — Neptune is a flight booking API, not a payment processor. You charge the customer on your platform and call /book/ once funds are confirmed. We bill you on a contracted schedule (typically monthly).
Is the booking id stable for refunds and disputes?
Yes — booking_id is a stable UUID issued by Neptune. It survives PNR cancellation, refund flow, and any state transition. Always store this on your side for cross-referencing.
What if an upstream provider is down?
You'll receive 502 Bad Gateway with a descriptive error message. These are typically transient (under 30 seconds). Retry with backoff. If persistent, check our status page or reach out to your account contact.