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.

Already have credentials? Sign in to issue real bookings from the browser — no curl required. New here? Ask your admin to bind a User row to your Partner in Django admin.

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://dev.neptune.tarzanway.com

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.
  • Prepaid billing. Top up your wallet in advance; bookings deduct from it automatically and your live balance is always visible.

Partner Portal

Prefer a UI over the API? Your account ships with a hosted partner portal — a browser app for searching, booking, and managing travel without writing a single line of code. Same backend, same booking lifecycle, same wallet, one login across both verticals.

ℹ️
Login credentials are issued separately from your API key. If you don't have a portal login yet, reach out to your account contact. Portal users and API consumers share the same partner account, the same prepaid wallet, and the same booking history — bookings made through one are visible in the other.

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://dev.neptune.tarzanway.com/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.

Try in Postman

A ready-to-run Postman collection covers every endpoint with sample requests, example responses, and auto-chaining test scripts (you click through the requests in order; trace_id / booking_id are captured for you).

How to use

  1. Download both files above.
  2. Open Postman → Import → drop both JSONs.
  3. Select the imported environment Neptune Flights — Sandbox from the environment dropdown (top-right of Postman).
  4. Edit the environment and set api_key to the key Neptune shared with you. The base_url is preconfigured to https://dev.neptune.tarzanway.com.
  5. Open 01. Flight Search and click Send. The test script saves the trace_id and chosen result_index automatically. Walk through the remaining requests in order.
ℹ️
Each endpoint with multiple scenarios (search, create booking, save passengers) has its alternates as Examples under the request — click the Examples dropdown next to the request name in Postman to see each scenario's request body and sample response.

Suppliers

Two suppliers sit behind the same API. Pass source on /v1/flights/search/ to choose; it defaults to Nuitee.

sourceSupplierWhat to know
nuitee (default)Nuitee ConnectOffers expire in ~5–10 minutes. Travel documents required on every passenger. Round trips are always sold as a combination.
travclanTravClanFlat 15-minute result window. Travel documents required on international routes only. Round trips are combinations internationally, independent lists domestically.

You only send source on the search. Every later call in the chain is pinned to whichever supplier served that search — we resolve it from the trace_id and from the booking itself. A booking never falls back to the other supplier: a trace_id, its result indices and its fares are all supplier-bound.

Differences that affect your integration

  • Round-trip shape. Read round_trip_style on the search response. "independent" means separate outbound[] and inbound[] lists — send both result indices to create-booking. "coupled" means the returns are nested under each outbound as inbound_options[] — send only the chosen option's result index. Nuitee is always coupled; TravClan is coupled internationally and independent domestically.
  • Result lifetime. Use expires_at / remaining_seconds when present instead of assuming 15 minutes.
  • Passenger documents. Read passport_required on the booking. Nuitee rejects a booking without passport number, expiry and nationality on every passenger, domestic sectors included — do not derive this from is_domestic.
  • SSR. Nuitee has no meal ancillary, and seats/baggage are only offered after a booking is reserved, so the ssr block on a passenger is ignored for Nuitee bookings.
  • Duplicate detection. Only TravClan reports it. For Nuitee, duplicates_found is always false.
  • Expired handles. If the fare you are holding has expired, the API answers 409 Conflict with a message telling you to search again — as opposed to a 502, which means the supplier itself failed.
  • Supplier-specific validation. A payload a supplier rejects on its own rules — most commonly a passenger missing passport details on a Nuitee booking — comes back as 400 Bad Request with the reason in detail. Fix the payload and resubmit; nothing upstream was touched.

Pricing, markup, currency, the wallet and every response shape are identical across suppliers. An integration written against TravClan works against Nuitee with no changes beyond the three fields above.

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
  },
  "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://dev.neptune.tarzanway.com/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
  },
  "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://dev.neptune.tarzanway.com/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://dev.neptune.tarzanway.com/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_charged": 10748.93,
      "wallet_debited_inr": 10748.93,
      "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. final_fare is the amount you'll be charged for the booking.

{
  "currency": "INR",
  "base_fare": 4837.50,
  "service_fee": 0.0,
  "published_fare": 5375.00,
  "final_fare": 5375.00
}

Itinerary-level responses (create-booking, reprice, get-booking) also include tax_and_surcharge, previous_total_amount, and price_changed (which goes true when the fare moves during a reprice). Search options additionally carry an airline_remark, fare_name, and group_id at the option level.

Error envelope

All non-2xx responses share the same shape:

{
  "success": false,
  "errors": [
    { "detail": ["Authentication credentials were not provided."] }
  ]
}

Airports Autocomplete

GET /v1/airports/autocomplete/?q=<query>

Public typeahead used by the portal's origin / destination pickers. Backed by mercury's curated hub table — same dataset the consumer site uses. The endpoint is unauthenticated (no API key needed) since it's purely for UI autocomplete; results are cached for an hour.

Query parameters

ParamTypeNotes
qstring (min length 2)Matches against airport name, city name, or IATA code (prefix-match).

Response

{
  "results": [
    { "code": "DEL", "city": "New Delhi", "name": "Delhi" },
    { "code": "DXB", "city": "Dubai",     "name": "Dubai Intl" }
  ]
}

Empty results for queries under 2 characters or with no matches. 502 if mercury is unreachable.

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.

API Status

Run the four end-to-end smoke tests against this environment on demand. Each test exercises the full booking chain (search → fare-rules → create-booking → save-passengers → reprice) without issuing a real PNR.

Not run yet
  • Domestic one-way
  • Domestic round-trip
  • International one-way
  • International round-trip

Each check takes 20–60s. Booking is skipped (--skip-book) so no real PNR is issued and no wallet balance is debited.

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.

How is pricing determined?

Pricing terms are agreed during onboarding and are baked into the rates you see in every response. The final_fare on each search result and on the booking is exactly what you'll be charged — no hidden line items, no end-of-month adjustments. Differentiated pricing (e.g. by route or cabin class) is something we can discuss on the commercial side.

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.

How does billing work?

Neptune runs on a prepaid wallet model. You recharge your account balance with us in advance, and each successful /book/ deducts the booking total from that balance. There's no per-transaction invoice or end-of-month settlement — you always see your live balance, and bookings fail with a clear error if you don't have enough credit. Top-up requests, balance enquiries, and statements go through your account contact for now; a self-serve recharge endpoint is on the roadmap.

Payment collection from your end customer still happens on your platform — Neptune isn't a payment processor. You charge the customer, then call /book/ against your prepaid balance.

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