Everything you need to integrate FDFares into your platform: live flight and hotel search, booking, and management, over a clean JSON REST API.
Base URL
https://fdfares.com/api/v1Production API URL: https://fdfares.com/api/v1
UAT Environment (Sandbox) API URL: https://sandbox.fdfares.com/api/v1
You'll need an API key from the Partner Portal. A flight booking always follows the same three steps — search for fares, review one to lock in a price and get a booking_session, then book using that booking_session directly. Nothing about pricing is ever supplied by the client — the server always resolves and re-validates it server-side.
const API_KEY = 'FD_lv_your_key_here'
const BASE = 'https://fdfares.com/api/v1'
// 1. Search
const search = await fetch(`${BASE}/flights/search`, {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
origin: 'DEL', destination: 'BOM',
travel_date: '2026-09-20', trip_type: 'one_way', adults: 1
})
}).then(r => r.json())
const flight = search.data.results.onward.results[0]
// 2. Review — locks in the fare and returns a booking_session
const review = await fetch(`${BASE}/flights/review`, {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ inventory_ids: [flight.inventory_id], adults: 1 })
}).then(r => r.json())
// 3. Book — pass the booking_session straight through, never a raw booking id
const booking = await fetch(`${BASE}/bookings/instant`, {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
booking_session: review.data.booking_session,
passengers: [{ type: 'adult', title: 'Mr', first_name: 'Jane', last_name: 'Doe' }],
delivery_emails: ['jane@example.com'],
delivery_contacts: ['9999999999'],
contact_info: { emails: ['jane@example.com'], contacts: ['9999999999'], name: 'Jane Doe' }
})
}).then(r => r.json())
console.log(booking.data.booking_no, booking.data.status)
Or with curl:
curl -X POST https://fdfares.com/api/v1/flights/search \
-H "x-api-key: FD_lv_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"origin": "DEL",
"destination": "BOM",
"travel_date": "2026-09-20",
"trip_type": "one_way",
"adults": 1
}'Every request under /api/v1 requires an API key, sent as an x-api-key header — not a Bearer token. Keys look like FD_lv_<32 hex characters>. Only a hash of your key is stored on our side; the raw key is shown once, at creation time, in the Partner Portal — copy it then, it can't be retrieved again.
x-api-key: FD_lv_d7ffc8a91b2e4f0c9a8d7e6f5c4b3a2dModule scoping
Each key is provisioned with access to specific modules. A request outside a key's granted modules is rejected with MODULE_NOT_ALLOWED.
flights_domestic | Search, review and book flights within India. |
flights_international | Search, review and book flights that cross an international border. |
hotels | Every endpoint under /hotels. |
buses | Reserved for a future bus API. |
packages | Reserved for a future holiday-packages API. |
IP allow-listing
Optionally restrict a key to specific IPs or CIDR ranges in the Partner Portal. A request from outside the allow-list is rejected with IP_NOT_ALLOWED.
Auth error responses
| 401 | MISSING_API_KEY | x-api-key header is required |
| 401 | INVALID_API_KEY | API key is invalid, inactive, or expired |
| 403 | IP_NOT_ALLOWED | Caller IP is not in the whitelist configured for this key |
| 403 | MODULE_NOT_ALLOWED | This API key does not have access to the module this request needs |
Every request is rate-limited per API key (falling back to IP if no key is present). Booking endpoints carry an additional, tighter limit on top of the general one. Every response includes RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers.
| Scope | Window | Max requests |
|---|---|---|
| All of /api/v1 | 60s | 100 |
| /bookings/* (on top of the above) | 60s | 20 |
{
"success": false,
"error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Too many requests — see Retry-After / RateLimit-* headers" }
}Every error — auth failures, validation errors, and booking-system failures alike — is JSON in the same envelope. Validation errors use a messages array instead of a single message.
{
"success": false,
"error": {
"code": "STRING_CODE",
"message": "Human-readable description",
"reason": "optional — a stable machine identifier",
"details": [ /* optional — present when the airline or hotel system reported multiple errors at once */ ]
}
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"messages": [
"origin is required",
"travel_date must be in YYYY-MM-DD format"
]
}
}How status codes are chosen
| Trigger | Status | error.code |
|---|---|---|
| Search result or review session expired | 422 | SEARCH_RESULT_EXPIRED |
| Flight/room no longer available | 410 | FLIGHT_UNAVAILABLE |
| Fare or price expired | 400 | FARE_EXPIRED |
| Other rejection from the airline or hotel system (400) | 400 | see error reference table below |
| Internal authentication failure | 401 | PROVIDER_AUTH_ERROR |
| Other failure from the airline or hotel system | 502 | see error reference table below |
| The airline or hotel system did not respond in time | 504 | PROVIDER_TIMEOUT |
| Request body failed validation | 400 | VALIDATION_ERROR |
| Anything unhandled | 500 | INTERNAL_ERROR |
Error code reference
When a search or booking is rejected by the airline or hotel system itself, the response carries one of these stable codes in error.code (with the numeric id in error.reason). Anything not listed here falls back to PROVIDER_ERROR.
Availability & session — usually resolved by searching again
| 1001 | FLIGHT_UNAVAILABLE | Flight is no longer available. Please search again. |
| 1002 | FARE_EXPIRED | Fare has expired. Please search again. |
| 1003 | SESSION_NOT_FOUND | Booking session not found. Please search again. |
| 1004 | HOLD_EXPIRED | Hold time limit has expired. Please re-book. |
| 1005 | FARE_UNAVAILABLE | Fare is no longer available. Please search again. |
| 1006 | FARE_MISMATCH | Payment amount does not match the fare total. Please retry. |
| 1007 | DUPLICATE_BOOKING | A booking for this flight already exists. |
| 1008 | SEAT_UNAVAILABLE | Seat selection is not available for this flight. |
| 1009 | INVALID_FARE_TYPE | Children and infants cannot be booked on a student/senior-citizen fare. |
Passenger data
| 2001 | INVALID_PAX_COUNT | Infants/children exceed adults, or total passengers exceed 9. |
| 2002 | INVALID_PASSENGER | Name formatting issue or missing document id. |
| 2003 | DUPLICATE_PASSENGER | Two passengers share the same name. |
| 2004 | INVALID_PASSENGER_AGE | Age is out of the allowed range for this passenger type / fare. |
| 2005 | MISSING_DOB | Date of birth is required and was not supplied. |
| 2006 | INVALID_TITLE | Title must be Mr/Mrs/Ms/Master/Miss as applicable. |
| 2007 | INVALID_PAN | The PAN number entered is invalid. |
Travel documents
| 3001 | MISSING_PASSPORT | A passport is required for this booking. |
| 3002 | INVALID_PASSPORT | Passport number, expiry, or issue date is invalid (includes expiry-within-6-months checks). |
Route & dates
| 4001 | INVALID_TRAVEL_DATES | |
| 4002 | INVALID_ROUTE |
Booking system
| 5001 | PROVIDER_ERROR | Generic fallback for anything unmapped. |
| 5002 | PROVIDER_AUTH_ERROR | |
| 5003 | PROVIDER_INACTIVE | |
| 5004 | PROVIDER_ACCESS_DENIED | |
| 5005 | PROVIDER_BAD_REQUEST | |
| 5006 | INVALID_ORDER_STATE | This booking cannot be processed in its current state. Please contact support. |
Contact & GST
| 6001 | INVALID_GST | |
| 6002 | INVALID_CONTACT | |
| 6003 | MISSING_CONTACT |
Amendment
| 7001 | AMENDMENT_UNSUPPORTED | |
| 7002 | AMENDMENT_EXISTS | |
| 7003 | INVALID_AMENDMENT | |
| 7004 | AMENDMENT_EXPIRED |
Search live domestic and international fares, review a fare before booking, and pull fare rules or seat maps for a specific flight.
/flights/searchSearches live fares between an origin and destination for a given date. Round trips return onward and return results in the same call, partitioned into two buckets.
Body parameters
originrequired | string | Origin airport IATA code, e.g. "DEL". Case-insensitive. |
destinationrequired | string | Destination airport IATA code. Must differ from origin. |
travel_daterequired | string | Departure date, YYYY-MM-DD. |
return_date | string | Return date, YYYY-MM-DD. Required when trip_type is round_trip. |
trip_type | "one_way" | "round_trip" | Defaults to one_way. |
cabin_class | "economy" | "premium_economy" | "business" | "first" | Defaults to economy. |
adults | integer ≥ 1 | Defaults to 1. adults + children + infants cannot exceed 9. |
children | integer ≥ 0 | Defaults to 0. Results are limited to fares with a published child fare when this is greater than 0. |
infants | integer ≥ 0 | Defaults to 0. Cannot exceed adults (each infant must travel with an adult). Results are limited to fares with a published infant fare when this is greater than 0. |
direct_only | boolean | Restrict results to non-stop flights. Defaults to false. |
fare_type | "STANDARD" | "STUDENT" | "SENIOR_CITIZEN" | Fare bucket to search. Defaults to the standard fare set. |
airlines | string[] | Filter results to specific carrier IATA codes, e.g. ["6E", "AI"]. |
departure_time_filter | ("before_6am"|"6am_12pm"|"12pm_6pm"|"after_6pm")[] | Filter by local departure time window. |
arrival_time_filter | same enum as above | Filter by local arrival time window. |
sort | "cheapest" | "fastest" | "slowest" | "highest_price" | Defaults to cheapest. |
limit | integer | Results per bucket. Defaults to 20, capped at 100. |
offset | integer | Pagination offset. Defaults to 0. |
Response
{
"success": true,
"data": {
"origin": "DEL",
"destination": "BOM",
"travelDate": "2026-09-15",
"returnDate": null,
"tripType": "one_way",
"cabinClass": "economy",
"result_expiry": "2026-09-15T14:32:00+05:30",
"limit": 20,
"offset": 0,
"results": {
"onward": {
"total": 47,
"results": [
{
"result_id": "8f21c...",
"inventory_id": "0af7304d-d7cd-506d-82af-aef2a20f5070",
"direction": "ONWARD",
"fare_type": "STANDARD",
"deal_code": "OFFER",
"journey": {
"origin": "DEL",
"destination": "BOM",
"departure_time": "2026-09-15T08:10:00+05:30",
"arrival_time": "2026-09-15T10:20:00+05:30",
"duration_minutes": 130,
"stops": 0,
"segments": [
{
"sequence": 1,
"flight": { "number": "6E123", "carrier_code": "6E", "carrier_name": "IndiGo", "is_lcc": true, "aircraft_type": "A320" },
"operating_flight": null,
"departure": { "airport": "DEL", "terminal": "3", "time": "2026-09-15T08:10:00+05:30" },
"arrival": { "airport": "BOM", "terminal": "2", "time": "2026-09-15T10:20:00+05:30", "next_day": false },
"duration_minutes": 130,
"connecting_time_minutes": null,
"stopovers": [],
"ssr": { "baggage": [{ "code": "BAG15", "desc": "15kg check-in", "amount": null }], "meals": [], "seats": [], "extra": [] }
}
]
},
"fare_product": {
"fare_basis": "Y26ID", "booking_class": "Y", "cabin_class": "ECONOMY",
"branded_fare": null, "branded_fare_label": null,
"refundable": 1, "meal_included": false, "seat_selection": null,
"seats_remaining": 9,
"baggage": { "check_in_kg": 15, "check_in_pieces": null, "cabin_kg": 7, "cabin_pieces": null, "raw": "15Kg" },
"infant_baggage": null
},
"price": {
"currency": "INR",
"per_adult": { "base_fare": 4200, "taxes": 1232, "total": 5432 },
"per_child": null,
"per_infant": null
},
"total_price": {
"currency": "INR",
"net_total": 5432,
"base_fare": 4200,
"taxes": 1232,
"adults": { "traveller": 1, "base_fare": 4200, "taxes": 1232, "net_total": 5432 },
"children": null,
"infants": null
},
"account_code": null,
"special_return": null
}
]
},
"return": { "total": 0, "results": [] }
},
"airlines": {
"onward": [ { "code": "6E", "name": "IndiGo", "count": 12 } ],
"return": []
}
}
}Possible errors
| 400 | VALIDATION_ERROR | origin/destination/travel_date missing or malformed, invalid cabin_class or fare_type, etc. — see error.messages[] for the exact field. |
/flights/reviewLocks in the exact fare and passenger conditions for one or two inventory_ids from a search response — one for a one-way itinerary, two (onward first, then return) for a round trip. Returns a short-lived booking_session — this is the only handle you pass into every booking endpoint, never a raw inventory_id or fare id.
Body parameters
inventory_idsrequired | string[] | One id for a one-way itinerary. Two ids for a round trip — the first is always treated as onward, the second as return, regardless of which leg is cheaper or which airline operates it; onward and return may come from entirely different fares or airlines. |
adults | integer ≥ 1 | Defaults to 1. |
children | integer ≥ 0 | Defaults to 0. |
infants | integer ≥ 0 | Defaults to 0. |
Response
{
"success": true,
"data": {
"booking_session": "a13f3869-32af-474d-9e37-522bf781762a",
"session_expires_at": "2026-08-27T10:15:00.000Z",
"onward": {
"inventory_id": "0af7304d-d7cd-506d-82af-aef2a20f5070",
"journey": {
"origin": "DEL",
"destination": "BOM",
"departure_time": "2026-09-15T08:10:00+05:30",
"arrival_time": "2026-09-15T10:20:00+05:30",
"duration_minutes": 130,
"stops": 0,
"segments": [ "… identical shape to a /flights/search result's journey.segments …" ]
},
"fare_product": {
"fare_basis": "Y26ID", "booking_class": "Y", "cabin_class": "ECONOMY",
"branded_fare": null, "branded_fare_label": null,
"refundable": 1, "meal_included": false, "seat_selection": null,
"seats_remaining": 6,
"baggage": { "check_in_kg": 15, "check_in_pieces": null, "cabin_kg": 7, "cabin_pieces": null, "raw": "15Kg" },
"infant_baggage": null
},
"seats": { "adults": 1, "children": 0, "infants": 0 },
"price": {
"currency": "INR",
"per_adult": { "base_fare": 4200, "taxes": 1259, "total": 5459 },
"per_child": null,
"per_infant": null
},
"conditions": {
"hold_allowed": true,
"passport": { "mandatory": false, "expiry_required": false, "issue_date_required": false },
"dob": { "adult_required": false, "child_required": true, "infant_required": true },
"gst": { "applicable": true, "mandatory": false },
"document_id": { "applicable": false, "mandatory": false }
},
"fare_rules": {
"cancellation": [
{ "amount": 3500, "additional_fee": 115, "policy_info": null, "start_hours": 4, "end_hours": 96, "policy_period": null, "fare_components": null }
],
"date_change": [
{ "amount": 3000, "additional_fee": 100, "policy_info": "+ Fare Difference if any", "start_hours": 4, "end_hours": 8760, "policy_period": null, "fare_components": null }
],
"no_show": [
{ "amount": 0, "additional_fee": 0, "policy_info": "If Cancelled within 4 hrs of scheduled departure only statutory taxes will be Refunded.", "start_hours": 0, "end_hours": 4, "policy_period": null, "fare_components": null }
],
"seat_chargeable": [
{ "amount": 0, "additional_fee": 0, "policy_info": "Paid Seat", "start_hours": 0, "end_hours": 8760, "policy_period": null, "fare_components": null }
],
"misc_info": null
}
},
"return": { "…": "same shape as onward — present only when a second (return) inventory_id was included in the request" },
"total_price": {
"currency": "INR",
"net_total": 5459,
"base_fare": 4200,
"taxes": 1259,
"adults": { "traveller": 1, "base_fare": 4200, "taxes": 1259, "net_total": 5459 },
"children": null,
"infants": null
}
}
}Possible errors
| 400 | VALIDATION_ERROR | inventory_ids missing, empty, over 6 entries, or invalid pax counts. |
| 404 | REQUEST_ERROR | One or more inventory_ids were not found or have expired — search again. |
| 410 | FLIGHT_UNAVAILABLE | The flight could no longer be reviewed even after a refresh attempt — search again. |
/flights/fare-rules/{inventoryId}Cancellation, date-change and no-show rules for a specific fare. If you've already called POST /flights/review for this fare, you don't need this — the same data is included there as fare_rules, no extra call required.
Path parameters
inventoryIdrequired | string | The inventory_id from a /flights/search result. |
Response
{
"success": true,
"data": {
"routes": {
"DEL-BOM": {
"cancellation": [
{ "amount": 3000, "additional_fee": 0, "policy_info": "Non-refundable within 24h of departure", "start_hours": 0, "end_hours": 24, "policy_period": "Departure -24h to 0h", "fare_components": null }
],
"date_change": [
{ "amount": 2000, "additional_fee": 0, "policy_info": "Subject to fare difference", "start_hours": 0, "end_hours": 9999, "policy_period": "Any time before departure", "fare_components": null }
],
"no_show": [
{ "amount": 5432, "additional_fee": 0, "policy_info": "Full fare forfeited on no-show", "start_hours": 0, "end_hours": 0, "policy_period": "At departure", "fare_components": null }
],
"seat_chargeable": null,
"misc_info": "Fare rules are subject to airline discretion and may change without notice."
}
}
}
}Possible errors
| 404 | REQUEST_ERROR | inventory_id not found or expired — search again. |
/flights/seat-map/{bookingId}Seat availability and pricing for a flight tied to an existing booking.
Path parameters
bookingIdrequired | string | Your own booking_id (the UUID from /bookings/instant or /bookings/hold's response). |
Response
{
"success": true,
"data": {
"onward": {
"segments": {
"DEL-BOM-6E123": {
"rows": 30, "columns": 6, "notes": null,
"seats": [
{ "seat_no": "12A", "row": 12, "column": "A", "is_booked": false, "is_legroom": false, "is_aisle": true, "is_exit_row": false, "code": "STD", "amount": 250 }
]
}
}
},
"return": {
"segments": {
"BOM-DEL-6E124": {
"rows": 30, "columns": 6, "notes": null,
"seats": [
{ "seat_no": "14C", "row": 14, "column": "C", "is_booked": false, "is_legroom": false, "is_aisle": true, "is_exit_row": false, "code": "STD", "amount": 250 }
]
}
}
}
}
}Possible errors
| 404 | REQUEST_ERROR | No booking found for that booking_id. |
| 400 | SEAT_UNAVAILABLE | Seat selection isn't offered on any leg of this booking — a normal, expected response for some flights/fares, not an error on your part. |
Book a reviewed fare instantly or on hold, then ticket, confirm, retrieve, or cancel it.
/bookings/instantConfirms and tickets a reviewed fare immediately. Takes the booking_session from /flights/review — never a raw booking id or inventory_id. The amount charged is always resolved and validated server-side, never trusted from the client. For a round trip, booking_session already carries both legs, so this one call books both.
Body parameters
booking_sessionrequired | string | From the matching /flights/review response. Carries both legs for a round trip — nothing else identifies the flight(s). |
passengersrequired | PassengerInput[] | One entry per traveller — see below. No two passengers may share the same first + last name. |
passengers[].typerequired | "adult" | "child" | "infant" | |
passengers[].titlerequired | "Mr" | "Mrs" | "Ms" (adult) or "Ms" | "Master" (child/infant) | Enforced per passenger type. |
passengers[].first_namerequired | string | |
passengers[].last_namerequired | string | |
passengers[].date_of_birth | string (YYYY-MM-DD) | Required when the review response's conditions.dob says so for this passenger type. |
passengers[].passport_number | string | Required when conditions.passport.mandatory is true. |
passengers[].passport_expiry | string | Required when conditions.passport.expiry_required is true. |
passengers[].passport_nationality | string | |
passengers[].passport_issue_date | string | Required when conditions.passport.issue_date_required is true. |
passengers[].pan_number | string | |
passengers[].document_id | string | Required when conditions.document_id.mandatory is true. |
passengers[].frequent_flyer | object (carrier code → number) | |
passengers[].ssr_baggage / ssr_meals / ssr_seats / ssr_extra | { segment_key, code }[] | Ancillary selections, matched against the codes returned in search/review. |
delivery_emailsrequired | string[] | At least one. Used for the booking confirmation email. |
delivery_contactsrequired | string[] | At least one. Falls back to contact_info.contacts if omitted. |
contact_info | { emails: string[], contacts: string[], name: string } | Required if delivery_emails is omitted — its emails are used instead. |
gst_info | { gst_number, registered_name, mobile?, email?, address? } | Attaches a business GST number to the booking. gst_number must be exactly 15 characters. |
Response
{
"success": true,
"data": {
"booking_id": "df1c5416-7dea-4db8-94e2-b7b21064c23f",
"booking_no": "TH-MTANQRD3-U2WR",
"status": "confirmed"
}
}Possible errors
| 400 | VALIDATION_ERROR | Missing/invalid booking_session, passengers, or contact fields — see error.messages[]. |
| 402 | INSUFFICIENT_WALLET_BALANCE | Wallet balance (or available credit) is lower than the reviewed fare total. |
| 409 | ALREADY_BOOKED | This exact fare has already been booked — check your bookings list instead of retrying. |
| 410 | SEARCH_RESULT_EXPIRED | The booking_session has expired — call /flights/review again. |
| 400 | (mapped, see Errors reference) | A booking rejection from the airline — fare changed, invalid passenger data, etc. |
/bookings/holdSame request shape as instant booking, but places the fare on hold instead of ticketing immediately — only when the reviewed fare's conditions.hold_allowed was true. Confirm and ticket it later with /bookings/confirm-fare followed by /bookings/confirm, before the hold deadline.
Body parameters
(all fields)required | — | Identical request body to POST /bookings/instant — see above. |
Response
{
"success": true,
"data": {
"booking_id": "…",
"booking_no": "TH-…",
"status": "on_hold"
}
}Possible errors
| 409 | HOLD_NOT_ALLOWED | The reviewed fare does not support holding — book instantly instead. |
| 402 | INSUFFICIENT_WALLET_BALANCE | The hold amount is charged (refundable) at hold time, same as an instant booking. |
/bookings/confirm-fareChecks whether a held fare is still valid (and at what price) before ticketing it. A read-only step — nothing is charged here.
Body parameters
booking_idrequired | string | Your FDFares booking_id from /bookings/hold. |
Response
{
"success": true,
"data": {
"booking_id": "…",
"success": true,
"alerts": [],
"errors": [],
"conditions": { "is_hold_allowed": true, "session_valid_seconds": 900 },
"total_price": { "base_fare": 4200, "taxes": 1259, "total": 5459, "net_fare": 4100, "commission": 100, "currency": "INR" }
}
}Possible errors
| 404 | REQUEST_ERROR | No booking found for that booking_id. |
/bookings/confirmPays and finalises a held booking. The payment amount is never taken from the client — the server always re-validates the fare first and pays exactly what's quoted at that moment (falling back to the amount recorded at hold time only if no change is reported).
Body parameters
booking_idrequired | string | Your FDFares booking_id from /bookings/hold. |
Response
{
"success": true,
"data": {
"bookingRef": "TH-…",
"bookingId": "…",
"status": "confirmed",
"errors": [],
"message": "Ticketing submitted."
}
}Possible errors
| 404 | REQUEST_ERROR | No booking found for that booking_id. |
| 409 | REQUEST_ERROR | Fare changed or is no longer available for this booking. |
| 502 | PROVIDER_ERROR | No usable fare figure was returned for confirmation. |
/bookings/{bookingId}/detailsRetrieves the full booking record, including the PNR and ticket numbers. Also syncs FDFares's own stored booking status from the result.
Path parameters
bookingIdrequired | string | Your FDFares booking_id (the UUID from /bookings/instant or /bookings/hold's response). |
Query parameters
leg | "onward" | "return" | Which leg of a round-trip booking to fetch. Defaults to onward. |
paxPricing | "true" | omitted | Include a per-passenger price breakdown. |
Response
{
"success": true,
"data": {
"booking_id": "…",
"order_status": "SUCCESS",
"amount_charged": 5459,
"order_note": null,
"delivery_info": { "emails": ["a@b.com"], "contacts": ["+919999999999"] },
"trip_infos": [
{
"segments": [ "… same shape as a search result's journey.segments …" ],
"price_list": [
{
"price_id": "…", "fare_identifier": "STANDARD", "account_code": null,
"adult_fare": { "base_fare": 4200, "taxes": 1259, "total": 5459, "net_fare": 4100, "commission": 100 },
"child_fare": null, "infant_fare": null,
"seats_remaining": 6, "plating_carrier": "6E",
"fare_rule_info": { "…": "same shape as fare_rules on POST /flights/review — null if there's no fare-rule data on file for this booking" }
}
]
}
],
"total_price": { "base_fare": 4200, "taxes": 1259, "total": 5459, "net_fare": 4100, "commission": 100, "currency": "INR" },
"travellers": [
{
"title": "Mr", "first_name": "John", "last_name": "Doe", "pax_type": "ADULT",
"pnr_details": { "6E": "ABCDEF" }, "gds_pnrs": {}, "ticket_numbers": { "6E": "1234567890" },
"status_map": {}, "ssr_baggage": {}, "ssr_meals": {}, "ssr_seats": {}
}
],
"gst_info": null,
"time_limit": null
}
}Possible errors
| 404 | REQUEST_ERROR | No booking found for that booking_id, or leg=return was requested on a booking with no return leg. |
/bookings/release-pnrBody parameters
booking_idrequired | string | Your FDFares booking_id. |
pnrsrequired | string[] | PNR codes to release, from the booking details response. |
Response
{
"success": true,
"data": { "…": "raw response from the airline system — shape varies by carrier, treat as opaque" }
}Possible errors
| 404 | REQUEST_ERROR | No booking found for that booking_id. |
/bookings/{bookingId}/amendment-chargesPath parameters
bookingIdrequired | string | Your FDFares booking_id. |
Query parameters
remarksrequired | string | A short reason for the amendment — required by the airline. |
leg | "onward" | "return" | Which leg of a round-trip booking to get charges for. Defaults to onward. |
Response
{
"success": true,
"data": {
"booking_id": "…",
"errors": [],
"trips": [
{
"origin": "DEL", "destination": "BOM", "departure_date": "2026-09-15",
"flight_numbers": ["6E123"], "airlines": ["6E"],
"pax_charges": { "adult": { "amendment_charges": 500, "refund_amount": 3700, "total_fare": 5459 } },
"traveller_charges": [ { "first_name": "John", "last_name": "Doe", "amendment_charges": 500, "refund_amount": 3700, "total_fare": 5459 } ]
}
]
}
}Possible errors
| 404 | REQUEST_ERROR | No booking found for that booking_id, or leg=return was requested on a booking with no return leg. |
/bookings/cancelBody parameters
booking_idrequired | string | Your FDFares booking_id. |
remarksrequired | string | Reason for cancellation. |
trips | array | Scope the cancellation to specific legs of a multi-leg itinerary. Passed through as-is. |
leg | "onward" | "return" | Which leg of a round-trip booking to cancel. Defaults to onward — cancelling both legs of a round trip currently takes two separate calls. |
Response
{
"success": true,
"data": { "…": "raw response from the airline system — treat as opaque" }
}Possible errors
| 404 | REQUEST_ERROR | No booking found for that booking_id, or leg=return was requested on a booking with no return leg. |
/bookings/amendment/{amendmentId}Path parameters
amendmentIdrequired | string | Returned by the cancel call. |
Response
{
"success": true,
"data": {
"booking_id": "…",
"amendment_id": "…",
"status": "PROCESSED",
"amendment_charges": 500,
"refundable_amount": 3700,
"total_fare": 5459,
"trips": [
{
"origin": "DEL", "destination": "BOM", "departure_date": "2026-09-15",
"travellers": [ { "first_name": "John", "last_name": "Doe", "amendment_charges": 500, "refund_amount": 3700, "total_fare": 5459 } ]
}
]
}
}Possible errors
| 404 | REQUEST_ERROR | No amendment found for that id. |
Search hotel availability by city, drill into room-level pricing for one property, then book, hold, or cancel a room.
/hotels/searchSearches hotels for a city and date range. Returns a card per hotel with its cheapest available price — call /hotels/detail on a specific listing_id to see full room options.
Body parameters
check_inrequired | string (YYYY-MM-DD) | |
check_outrequired | string (YYYY-MM-DD) | Must be after check_in. |
nationality | string (ISO 3166-1 alpha-2) | Guest nationality, e.g. "IN", "US", "GB" — case-insensitive. Defaults to "IN" (India) when omitted. |
roomsrequired | { adults: number, children?: number, child_ages?: number[] }[] | 1–9 rooms. child_ages must have exactly children entries when children > 0. |
city_id | string (UUID) | The id from a GET /hotels/cities result. Takes priority over city_name if both are given. |
city_name | string | Exact, case-insensitive city name — an alternative to city_id when you already know the name and don't want an extra /hotels/cities lookup. Can match more than one region (e.g. a city and its wider metro area share a name); all matches are searched together automatically. |
country | string | Paired with city_name. Accepts either a 2-letter ISO 3166-1 code ("IN") or a full country name ("India"). Defaults to "IN". Ignored if city_id is given. |
hids | number[] | Search specific known hotel ids directly instead of a destination. One of city_id, city_name, or hids is required. |
offset | number | Resume cursor for "load more" — always echo back the previous response's next_offset verbatim. Not a page number. |
currency | string | Defaults to INR. |
min_star_rating | number (1–5) |
Response
{
"success": true,
"data": {
"searchId": "e2f4c8a1-9b3d-4e2f-8a1c-9b3d4e2f8a1c",
"checkIn": "2026-09-20",
"checkOut": "2026-09-22",
"city_name": "Mumbai",
"country_name": "India",
"has_more": true,
"next_offset": 40,
"total_results": 312,
"currency": "INR",
"hotels": [
{
"listing_id": "8b2c4a17-2e91-4d5f-9c3a-1f7e6b8d0a2c",
"name": "Grand Regency Palace",
"address": "Apollo Bunder, Colaba",
"star_rating": "5",
"min_price": 24500,
"currency": "INR"
}
]
}
}Possible errors
| 400 | VALIDATION_ERROR | Missing/invalid dates or rooms, nationality isn't a 2-letter code, or country is blank — see error.messages[]. |
| 400 | VALIDATION_ERROR | nationality isn't a recognized ISO 3166-1 alpha-2 country code, or country isn't a recognized code or country name. |
| 400 | REQUEST_ERROR | None of city_id, city_name, or hids was provided. |
| 400 | REQUEST_ERROR | city_id or city_name doesn't match a known destination. |
/hotels/citiesFree-text city autocomplete, used to obtain a city_id for /hotels/search.
Query parameters
qrequired | string | Free-text city name. |
country | string | Narrow results to one country — a 2-letter ISO 3166-1 code ("IN") or a full country name ("India"). |
limit | number | Defaults to 20, capped at 50. |
Response
{
"success": true,
"data": {
"results": [
{ "id": "c05ac8fa-8474-4775-87d3-3ebda2c21b51", "city": "Mumbai", "country": "IN", "full_name": "Mumbai, Maharashtra, India" }
]
}
}Possible errors
| 400 | VALIDATION_ERROR | country isn't a recognized ISO 3166-1 code or country name. |
/hotels/detailBody parameters
search_idrequired | string | From the matching /hotels/search response. |
listing_idrequired | string | A listing_id from that same search response — not a raw hotel id. |
Response
{
"success": true,
"data": {
"hotel_id": "3f9e2c1a-8b7d-4e6f-9a2c-1b8d7e6f9a2c",
"hotel_name": "Grand Regency Palace",
"review_hash": "opaque-hash-pass-through-as-is",
"options": [
{
"option_id": "opt_9f3a2c1e",
"option_type": "STANDARD",
"room_info": [ { "id": "rm_1", "name": "Deluxe Room", "adults": 2, "children": 0 } ],
"inclusions": ["Free WiFi", "Breakfast included"],
"meal_basis": "Room Only",
"booking_notes": null,
"pricing": {
"total": 24500, "base": 20000, "discount": 0, "taxes": 4500,
"management_fee": 0, "management_fee_tax": 0,
"service_fee": 0, "gst_on_service_fee": 0,
"currency": "INR", "strikethrough": 27000, "gst_claimable": 0
},
"commercial": { "type": "STANDARD", "commission": 0 },
"compliance": { "gst_type": "REGULAR", "pan_required": false, "passport_required": false },
"cancellation": { "isRefundable": true, "penalties": [ { "from": "2026-09-18", "to": "2026-09-20", "amount": 5000 } ] },
"rooms_left": 3,
"deadline": "2026-09-19T18:00:00Z"
}
]
}
}Possible errors
| 400 | VALIDATION_ERROR | search_id or listing_id missing. |
| 410 | REQUEST_ERROR | The search session expired, or this listing_id is no longer part of your search — search again. |
| 502 | (mapped, see Errors reference) | A live error on this specific hotel. |
/hotels/reviewLocks in a specific room option for booking and returns a real booking_id — unlike the flights side, the hotel booking flow gives you this id directly, so /hotels/book can be called right away with it.
Body parameters
search_idrequired | string | |
hotel_idrequired | string | The hotel_id from /hotels/detail — FDFares's own id, not the property's id. |
option_idrequired | string | From a specific entry in /hotels/detail's options[]. |
review_hashrequired | string | From /hotels/detail. |
Response
{
"success": true,
"data": {
"hotel_id": "3f9e2c1a-8b7d-4e6f-9a2c-1b8d7e6f9a2c",
"hotel_name": "Grand Regency Palace",
"booking_id": "HTL-9F3A2C1E-BOOK",
"hold_allowed": true,
"option": { "…": "same shape as a /hotels/detail options[] entry, re-priced" }
}
}Possible errors
| 400 | VALIDATION_ERROR | search_id, hotel_id, option_id, or review_hash missing. |
| 400 | REQUEST_ERROR | hotel_id did not resolve — search again. |
| 409 | OPTION_SOLD_OUT | The room option sold out between detail and review. |
| 410 | REQUEST_ERROR | The search session expired. |
/hotels/bookSubmits the booking. This call polls for a terminal status before responding — allow up to 3 minutes.
Body parameters
booking_idrequired | string | From /hotels/review. |
book_typerequired | "instant" | "hold" | "hold" is rejected if the reviewed option doesn't support it. |
roomsrequired | { travellers: Traveller[] }[] | One entry per room, matching the room count from review. |
rooms[].travellers[].titlerequired | string | |
rooms[].travellers[].typerequired | "ADULT" | "CHILD" | |
rooms[].travellers[].first_namerequired | string | |
rooms[].travellers[].last_namerequired | string | |
rooms[].travellers[].pan | string | Required for the lead guest only, when the reviewed option's compliance.pan_required is true. |
rooms[].travellers[].passport_number | string | Required for every guest when compliance.passport_required is true. |
delivery_emailsrequired | string[] | At least one. |
delivery_contactsrequired | string[] | At least one. |
delivery_codesrequired | string[] | Country dial codes for delivery_contacts. |
Response
{
"success": true,
"data": {
"id": "internal-uuid",
"booking_ref": "HT-LX3F9A2-K7QZ",
"status": "confirmed"
}
}Possible errors
| 400 | GUEST_INFO_INCOMPLETE | A required PAN or passport field is missing. |
| 402 | INSUFFICIENT_WALLET_BALANCE | Instant bookings only — hold bookings aren't charged at booking time. |
| 409 | HOLD_NOT_ALLOWED | book_type was "hold" but the reviewed option doesn't support it. |
| 409 | REQUEST_ERROR | The booking failed after submission — nothing was charged. |
| 410 | REQUEST_ERROR | The 10-minute review/booking session expired — call /hotels/review again. |
/hotels/confirm-bookPays and finalises a booking made with book_type: "hold", before its hold deadline. Also polls for a terminal status — allow up to 3 minutes.
Body parameters
booking_idrequired | string | The id from /hotels/book's response — not booking_ref. |
Response
{
"success": true,
"data": { "id": "internal-uuid", "booking_ref": "HT-…", "status": "confirmed" }
}Possible errors
| 402 | INSUFFICIENT_WALLET_BALANCE | |
| 404 | REQUEST_ERROR | booking_id not found. |
| 409 | REQUEST_ERROR | The booking isn't currently on_hold, or confirmation didn't complete successfully — nothing was charged. |
/hotels/booking-detailsBody parameters
booking_idrequired | string | The id from /hotels/book's response — not booking_ref. |
Response
{
"success": true,
"data": { "…": "raw booking-details response from the property/booking system — shape not fixed, treat as opaque and inspect a live response for the fields you need" }
}/hotels/cancel/{bookingId}Path parameters
bookingIdrequired | string | This is FDFares's own booking id (the "id" from /hotels/book's response) — not booking_ref. |
Response
{
"success": true,
"data": { "id": "internal-uuid", "booking_ref": "HT-…", "status": "cancelled" }
}Possible errors
| 404 | REQUEST_ERROR | No booking found for that booking_id. |
/hotels/static-detailProperty-level content — name, star rating, address, images — independent of live pricing.
Body parameters
hotel_idrequired | string |
Response
{
"success": true,
"data": { "…": "raw static content — name, star rating, address, coordinates, images", "fromCache": true }
}Initial public reference for flight search, review and booking, and the full hotel search-to-booking flow.
Need an API key?
Become a partner to get access to your API keys and a dedicated test environment.