# Changelog
# v4
CitizenPlane now supports an NDC-inspired booking flow, enabling distributors to search, book and manage flights through a standardized interface. The v4 API is available at booking-api.citizenplane.com/v4.
# New booking flow
The v4 API introduces a new offer-based booking flow, replacing the flight/request/booking model used in v2 and v3:
- Search — Find available offers for one-way or round-trip itineraries
- Get Offer — Retrieve verified pricing and fare details for a specific flight combination
- Get Fares — Retrieve all available fares for an offer
- Get Baggage — Retrieve paid baggage options (checked and cabin)
- Create Order — Submit passenger details, baggage selections, and contact information
- Confirm Order — Trigger the actual booking
- Get Reservation — Retrieve the final booking record with PNR
TIP
Offers are cached for 30 minutes after retrieval. Orders using an expired offer will fail. Orders must be confirmed within 15 minutes of creation.
# Example: Search offers
Request
curl 'https://booking-api.citizenplane.com/v4/search' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}' \
-d '{
"adults": 1,
"children": 0,
"infants": 0,
"tripType": "ONE_WAY",
"travelClass": "ECONOMY",
"outboundSegment": {
"origin": { "code": "CDG", "type": "AIRPORT" },
"destination": { "code": "AYT", "type": "AIRPORT" },
"departureDate": "2025-08-06"
},
"inboundSegment": null
}'
Response
{
"offers": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"outboundSegment": {
"sections": [
{
"origin": "CDG",
"destination": "AYT",
"departureDate": "2025-08-06T17:45:00",
"arrivalDate": "2025-08-06T22:40:00",
"travelClass": "ECONOMY",
"availableSeats": 10,
"flightNumber": "511",
"operatingCarrierCode": "XQ",
"marketingCarrierCode": "XQ",
"technicalStops": []
}
]
},
"fare": {
"passengerPricingByPassengerType": {
"ADULT": {
"fareAmount": { "amount": 627.27, "currency": "EUR" },
"taxAmount": { "amount": 0, "currency": "EUR" },
"numberOfPassengers": 1
}
},
"includedBaggage": {
"checkedBaggageQuantity": 1,
"cabinBaggageQuantity": 1
},
"price": { "totalAmount": 627.27, "currency": "EUR" }
},
"paymentOptions": [{ "type": "cash", "fee": { "amount": 0, "currency": "EUR" } }],
"connectivityStandard": "NDC"
}
]
}
# Example: Create order
Request
curl 'https://booking-api.citizenplane.com/v4/order?offerId={offerId}&fareId={fareId}' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}' \
-d '{
"passengers": [
{
"number": 1,
"type": "ADULT",
"title": "MR",
"name": "John",
"firstLastName": "Doe",
"gender": "MALE",
"birthDate": "1990-01-15",
"nationalityCountryCode": "FR",
"identification": {
"type": "PASSPORT",
"identificationNumber": "ABCDE123",
"expirationDate": "2030-01-01",
"issueCountryCode": "FR"
}
}
],
"selectedBaggage": [
{
"passengerNumber": 1,
"itineraryBaggageSelection": [],
"outboundSegmentBaggageSelection": [
{ "type": "CHECK_IN", "numberOfPieces": 1, "kilosPerPiece": 20 }
],
"inboundSegmentBaggageSelection": null
}
],
"selectedSeats": [],
"customerContact": {
"name": "John",
"lastNames": "Doe",
"email": "john.doe@example.com",
"countryCode": "FR",
"phone": { "number": "+33600000000", "countryCode": "FR" },
"address": "123 Rue de Paris",
"cityName": "Paris",
"zipCode": "75001"
},
"paymentMethod": { "cash": true }
}'
Response
{
"id": "d4e5f6a7-b8c9-0123-defg-234567890123",
"status": "OPEN",
"offer": { "..." },
"passengers": [{ "..." }],
"selectedBaggage": [{ "..." }],
"totalPrice": { "amount": 663.32, "currency": "EUR" }
}
# Example: Confirm order
Request
curl -X POST 'https://booking-api.citizenplane.com/v4/order/{orderId}/confirm' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}'
Response
{
"id": "d4e5f6a7-b8c9-0123-defg-234567890123",
"status": "CONFIRMING",
"reservationId": "e5f6a7b8-c9d0-1234-efgh-345678901234",
"totalPrice": { "amount": 663.32, "currency": "EUR" }
}
# Example: Get reservation
Request
curl 'https://booking-api.citizenplane.com/v4/reservation/{reservationId}' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}'
Response
{
"id": "e5f6a7b8-c9d0-1234-efgh-345678901234",
"orderId": "d4e5f6a7-b8c9-0123-defg-234567890123",
"pnr": "CPABCD",
"email": "john.doe@example.com",
"outboundSegment": {
"sections": [
{
"origin": "CDG",
"destination": "AYT",
"departureDate": "2025-08-06T17:45:00",
"arrivalDate": "2025-08-06T22:40:00",
"travelClass": "ECONOMY",
"flightNumber": "511",
"operatingCarrierCode": "XQ",
"marketingCarrierCode": "XQ"
}
]
},
"passengers": [
{
"number": 1,
"type": "ADULT",
"name": "John",
"firstLastName": "Doe"
}
],
"fare": { "..." }
}
# Key differences from v2/v3
- Authentication: Uses API Key authentication (not Bearer tokens)
- Offer-based model: Flights are returned as offers with structured fare pricing, replacing the flat flight/fare model
- Order lifecycle: Orders go through
OPEN→CONFIRMING→BOOKEDstatuses - Structured locations: Origin and destination support both airport codes (
AIRPORT) and city codes (CITY) - Payment: Cash-only payment method (
{ "cash": true }) - Currency: EUR only
- Travel class: Economy only (for now)
- Passenger types:
ADULT,CHILD,INFANT - Baggage: Typed as
CHECK_INandCABIN, selected per passenger at the order step - Connectivity standard: All offers are marked with
connectivityStandard: "NDC"
# New endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /v4/search | Search available offers |
| POST | /v4/offer | Get detailed offer with verified pricing |
| GET | /v4/offer/{offerId}/fares | Get all fares for an offer |
| GET | /v4/offer/{offerId}/baggage | Get paid baggage options |
| GET | /v4/offer/{offerId}/seats | Get seat options (not yet supported) |
| POST | /v4/order?offerId=&fareId= | Create an order |
| POST | /v4/order/{orderId}/confirm | Confirm an order |
| GET | /v4/order/{orderId} | Get order status |
| GET | /v4/reservation/{reservationId} | Get reservation by ID |
| GET | /v4/reservation?orderId= | Get reservation by order ID |
WARNING
The v4 API is a separate integration from the v2/v3 APIs. Existing v2/v3 bookings and endpoints are not affected.
# v3
Major changes are brought to this new version of our Booking API. We’ve introduced two new features:
- round trips
- cabin bags
And have also made other significant changes in the overall logic of our API such as passing passenger & luggage information at the request step instead of the booking step.
To use the v3, change your url from booking-api.citizenlane.com/v2 to booking-api.citizenplane.com/v3
# Round trip fares
Clients can now search for round trip fares, along with our regular one-way fares.To search for round trip fares, we’ve introduced a new endpoint: https://booking-api.citizenplane.com/v3/roundTrips.
Search parameters are the same as the ones used for the /flights endpoint.
If roundTrip fares are found, it will be returned in a fares array, with the following structure:
example search
curl -H 'Authorization: Bearer <YOUR_TOKEN>' 'https://booking-api.citizenplane.com/v3/roundTrips?origin=DUS&destination=PMI&start=2024-09-29&returnStart=2024-10-03'
example response
{
"fares": [
{
"outward_id": 130962721,
"return_id": 89499682,
"price": 385.8,
"infant_price": 20.6,
"luggage_prices": [
74.16
],
"cabin_prices": [],
"included_airport_tax": 47.69,
"cc_fees": 0.03,
"available_seats": 383
}
],
"flights": [
{
"id": 130962721,
[…]
},
{
"id": 89499682,
[…]
},
],
"next_page_id": null
}
To use a roundtrip fare at the request & booking step, we’ve introduced a new parameter at the /request step: fare_type can be either one_way or round_trip.
IMPORTANT NOTICE
Current booking flow should not change. You can now add a third call to /roundTrips to check for roundtrip fares and potentially use them later on. However, booking flights with one-way fares remain the most common use of our Booking API.
example request
{
"flight_ids": [
130962721,
89499682
],
[…]
"fare_type": "round_trip",
}
# Cabin bags
We now offer the possibility to select & add cabin bags to your bookings. Cabin bags offers will be returned at the search step.
BREAKING CHANGE
The luggage_options field has been dropped.We now return a bags object containing checked and cabin options.
Example search response
{
"flights": [
{
"id": 130962721,
"bags": [
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36,
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"quantity": 1,
"price": 0,
},
]
},
{
"id": 89499682,
[…]
},
],
"next_page_id": null
}
BREAKING CHANGE
The /requests endpoint now requires checked & cabin bags options to be sent within the payload.
The luggage field is no longer authorised at the /bookings step.
Example request payload
{
"flight_ids": [
130962721
],
"passengers": [...],
"bags": {
"130962721": {
"checked": [
{
"weight": 20,
"quantity": 1
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"quantity": 1
},
]
},
"fare_type": "one_way",
}
BREAKING CHANGE
The bags field is now required.
We no longer allow an empty object or null passed as its value.
Options must be clearly specified in the payload, even if no options are to be selected. If no option is to be selected, then quantity: 0 must be sent.
If multiple options are available, all options must be also listed in the request payload, with their required quantities.
Bag prices will be returned in a bags_fee object.
Example response
{
"requests": [
{
"id": 1857154,
[…]
"bags_fee": {
"checked": 36,
"cabin": 0
},
}
]
}
# Other breaking changes
BREAKING CHANGE
/requests
- The
passengersfield is now required flightshas been renamed toflight_idsis_infantboolean for passengers has been dropped
/bookings
- The
passengersfield is no longer required requestshas been renamed torequest_ids
In order to simplify the /bookings endpoint, we’ve moved a variety of information to the /requests step.
As explained above, the bags are now required to be sent at the /requests step.
But we also now require passengers information to be sent at the /requests step instead of the /bookings step. The passenger object remains, however the same.
# v2
# Booking status
Two new booking statuses are now provided: AWAITING_PNR_AIRLINE and CONFIRMED.
By default, a booking will have the status AWAITING_PNR_AIRLINE until the PNR is returned - generally within two hours.
Once it is, we'll update the booking status to CONFIRMED.
For flights without airline PNR, the booking status will be directly set at CONFIRMED.
Possible booking statuses are: AWAITING_PNR_AIRLINE, CONFIRMED, CANCELED, CHANGED
# CP reference
BREAKING CHANGES
pnr_reference has been renamed cp_reference.
We renamed this key to make sure there is no confusion with the new column pnr_airline.
# PNR airline (Record locator)
We now provide, when available, the PNR the airline will provide us at booking time so booker can directly check-in on the website of the operating airline.
If not available at booking time, we set it to null.
# Multi-segments
CitizenPlane is now able to sell flights with stop-overs, by introducing segments.
Each segment contains important details about the different parts of the flight.
A flight can have 2 stop-overs / 3 segments.
BREAKING CHANGES
via_airportkey has been moved from flight into segments.flight_numberkey has been removed and replaced by two keys:operating_carrier_flight_numberandmarketing_carrier_flight_number, which are now into segments. Typing is also changed fromstringtointeger.airlinekey has been removed and replaced by two distincts objects,marketing_carrierandoperating_carrier, which are now into segments.
Example flight
{
"id": 4014831,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 59.71,
"duration": "07:55",
"luggage_options": [
{
"weight": 20,
"quantity": 1,
"price": 36.05
}
],
"departure_date": "2022-08-06 13:45:00",
"arrival_date": "2022-08-06 22:40:00",
"origin": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"destination": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"segments": [
{
"via_airport": null,
"origin": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"destination": {
"iata_code": "WAW",
"city_name": "Warsaw",
"country_name": "Poland",
"timezone": "Europe/Warsaw"
},
"operating_carrier_flight_number": 1346,
"operating_carrier": {
"name": "Air France",
"iata_code": "AF"
},
"marketing_carrier_flight_number": 1346,
"marketing_carrier": {
"name": "Air France",
"iata_code": "AF"
},
"departure_date": "2022-08-06 13:45:00",
"arrival_date": "2022-08-06 16:05:00",
"duration": "02:20"
},
{
"via_airport": null,
"origin": {
"iata_code": "WAW",
"city_name": "Warsaw",
"country_name": "Poland",
"timezone": "Europe/Warsaw"
},
"destination": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"operating_carrier_flight_number": 421,
"operating_carrier": {
"name": "Sunexpress",
"iata_code": "XQ"
},
"marketing_carrier_flight_number": 421,
"marketing_carrier": {
"name": "Sunexpress",
"iata_code": "XQ"
},
"departure_date": "2022-08-06 18:40:00",
"arrival_date": "2022-08-06 22:40:00",
"duration": "03:00"
}
],
"available_seats": 10,
"price": 483.07,
"infant_price": 129.78,
"cc_fee": 0.03
}
# CSV content export evolution
We have added a new content export method, which is a more lightweight version of our flight export, containing only our routes with associated dates. To get price & availability details a call to v2/flights will be needed. This change allows us to offer a much faster and streamlined export. To activate the drop of the routes export on your FTP folder or to switch from the flights export to the routes export, please ask tech@citizenplane.com.
New routes format
departureAirport;arrivalAirport;departureDate
BSL;PRN;21.06.2022
MLH;PRN;21.06.2022
AAL;AYT;22.06.2022
ACE;CGN;22.06.2022
ADB;AMS;22.06.2022
ADB;CGN;22.06.2022
...
In addition, the file structure has also been changed to offer dedicated files per customers. If some content restrictions are applied on some of your customers, here is the file structure that you will start getting:
Example routes export structure:
cp_routes_customerOne_v2.csv
cp_routes_customerTwo_v2.csv
cp_routes_all_v2.csv
cp_routes.csv // legacy name
Example flights export structure:
cp_flights_customerOne_v2.csv
cp_flights_customerTwo_v2.csv
cp_flights_all_v2.csv
cp_flights.csv // legacy name
TIP
To avoid any confusion, we've also kept the legacy file names: cp_routes.csv & cp_flights.csv. You can keep using the same file without worries for a couple months until you've complete the migration to the new file naming structure. Then they will be deprecated.
# Self PSP Implementation
# Create bookings
- The
payment_methodandthree_d_securefields can be substituted byself_pspif your company has activated the setting "self payment processor" with our sales team.
curl 'https://booking-api.citizenplane.com/v2/bookings' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_token}' \
-d '{
"requests": [ 19, 20 ],
... // booking payload
},
"self_psp": true,
}'
# 3D Secure implementation
# Create booking
- The
card_tokenfield and the card token generation method is now deprecated it will be removed in a future version of the API. To update, please use the payment method instead.
Create a payment method using Stripe
curl https://api.stripe.com/v1/payment_methods \
-u sk_test_123456789abcdedf: \
-d card[number]=4242424242424242 \
-d card[exp_month]=12 \
-d card[exp_year]=2020 \
-d card[cvc]=123 \
-d type=card
Sending payment method and 3D Secure information (if needed) to CitizenPlane at booking step
curl 'https://booking-api.citizenplane.com/v2/bookings' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_token}' \
-d '{
"requests": [ 19, 20 ],
... // booking payload
},
"payment_method": "pm_1IxrDoCLoBt04Rh8deU0seLG",
"three_d_secure": {
"version": "2.2.0",
"electronic_commerce_indicator": "05",
"cryptogram": "4BQwsg4yuKt0S1LI1nDZTcO9vUM=",
"transaction_id": "f879ea1c-aa2c-4441-806d-e30406466d79"
}
}'
Create a card token using Stripe
curl https://api.stripe.com/v1/tokens \
-u sk_test_123456789abcdedf: \
-d card[number]=4242424242424242 \
-d card[exp_month]=12 \
-d card[exp_year]=2020 \
-d card[cvc]=123
Sending card token to CitizenPlane at booking step
curl 'https://booking-api.citizenplane.com/v2/bookings' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_token}' \
-d '{
"requests": [ 19, 20 ],
... // booking payload
},
"card_token": "card_1Ec124ZvKYr3e2CnFAqDD5Q"
}'
# 1.1
API Version
This version has been officially decprecated and every endpoints / docs removed. You should now only be using v2 and above.
# 1.0
API Version
This version has been officially decprecated and every endpoints / docs removed. You should now only be using v2 and above.