Documentation · v1

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_id you can reference forever.
  • Synchronous booking. No webhooks or polling — book returns the PNR in the response.
  • Audit-friendly pricing. Every response carries the raw pre-markup fare under pricing.raw so 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>
🔑
API keys are shared separately after contract sign-off. You'll receive a unique key per environment (sandbox and production). Treat the raw key like a password — it's shown to you only once and cannot be retrieved later. Rotation is supported on request.

Sample authenticated request

curl -X POST https://<host>/v1/flights/search/ \
  -H "Authorization: Api-Key YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ ... }'
⚠️
Never expose the key in browser-side code. All calls must originate from your server. If you suspect a key is compromised, request a rotation immediately.

Quick Start

Five steps from zero to a confirmed PNR:

  1. Search for flights → grab trace_id + a result_index
  2. Fare Rules (optional) → review cancellation policy
  3. Create Booking → lock fares, get booking_id
  4. Save Passengers → submit traveller details
  5. Reprice (optional) → confirm fare hasn't moved
  6. Book → issue PNR
Every step except the final book can be repeated. A trace_id expires after ~15 minutes; a booking_id remains addressable indefinitely.

Fare Rules

POST /v1/flights/fare-rules/

Retrieves cancellation and change rules for a specific flight result before locking the booking. Optional but recommended.

Request body

FieldTypeDescription
trace_idstringFrom the search response required
result_indexstringThe 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

POST /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

FieldTypeDescription
trace_idstringFrom the search response required
result_indicesarrayList of result IDs. See per-scenario rules below. required

How many result_indices to send

ScenarioCountComposition
Domestic one-way1The outbound's result_index
Domestic round-trip2[outbound.result_index, inbound.result_index]
International one-way1The outbound's result_index
International round-trip1The 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": [ /* ... */ ] }
      ]
    }
  ]
}
ℹ️
Always inspect 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.
Once a booking is created, the locked fares typically remain valid for 15–30 minutes. Use expires_at and remaining_seconds from the response to track the window. Submit passengers and book within that time.

Save Passengers

POST /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

FieldTypeDescription
titlestring"Mr", "Ms", "Mrs" required
first_namestringrequired
last_namestringrequired
genderstring"male", "female", "other" required
pax_typestring"adult", "child", or "infant" required
dobstringDate of birth, YYYY-MM-DD required
is_leadbooleanExactly one passenger per booking must be lead required
emailstringLead passenger only — required by paxRules optional
contact_numberstringLead passenger only — required by paxRules optional
cell_country_codestringe.g. "91" (no leading "+") optional
nationalitystringISO country code (e.g. "IN"); often required for intl optional
address_line_onestringoptional
address_line_twostringoptional
citystringoptional
country_codestringISO 2-letter (e.g. "IN") optional
country_namestringoptional
passport_numberstringRequired for international bookings optional
passport_expirystringYYYY-MM-DD; required for international optional
passport_issue_datestringYYYY-MM-DD; often required for international optional
frequent_flyer_airline_codestringIf providing FF, send both fields optional
frequent_flyer_numberstringoptional
gst_company_*, gst_numberstringLead pax only; all-or-none — partial GST is rejected optional
ssrobjectSpecial 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.

FieldTypeDescription
ssr.meal[]arrayMeal preferences with cost
ssr.baggage[]arrayExtra baggage purchases
ssr.seat[]arraySeat selection with cost

SSR item fields

FieldTypeDescription
codestringSSR code (e.g. "VCSW" for veg meal) required
originstring3-letter IATA — the segment this applies to required
destinationstring3-letter IATA — segment destination required
amtintegerCost in INR optional
seatstringSeat 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" }
        ]
      }
    }
  ]
}
ℹ️
SSR codes are airline-specific. Reach out for the canonical code list per airline if you need it — a dedicated SSR-catalog endpoint is planned.

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

POST /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

POST /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.

⚠️
This is the only irreversible step. Make sure your application confirms the user's intent before calling.

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": [ "..." ]
}
!
If 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

GET /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

GET /v1/bookings/?limit=25&offset=0

Paginated list of your own bookings. Most recent first.

Query parameters

ParamTypeDescription
limitintegerPage size, max 100 (default 25)
offsetintegerSkip 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.

StatusSet byMeaning
draftcreate-bookingFares locked with the airline; no passengers yet.
passenger_savedsave-passengersTravellers submitted; ready to reprice or book.
pricedrepriceLatest fare quote from the airline. Inspect pricing.price_changed.
confirmedbookPNR issued. Terminal success state.
failedbook (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

StatusMeaningCommon causes
400Bad RequestInvalid payload — missing fields, invalid date format, bad cabin class, or other request validation failures.
401UnauthorizedMissing, malformed, or revoked API key.
404Not FoundBooking ID doesn't exist or doesn't belong to your partner account.
429Too Many RequestsYou've exceeded your rate limit. Back off and retry with exponential delay.
502Bad GatewayUpstream provider error. Often transient — retry once. If persistent, the upstream message is in errors[0].detail.
500Internal Server ErrorBug on our side. Save the X-Request-Id response header and reach out.
ℹ️
Every response carries an X-Request-Id header. Include it when reporting issues — it lets us trace your request through every layer in under a minute.

Rate Limits

EndpointPer-partner limit
/v1/flights/search/60 requests/min
/v1/flights/booking/{id}/book/30 requests/min
All others120 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.

Neptune Flights · B2B Booking API · v1