# API Reference
The CitizenPlane API is based on REST (opens new window), returns JSON responses and uses standard HTTP codes. The API base url is https://booking-api.citizenplane.com/v3.
To get started with CitizenPlane API, send us a mail. We'll create your account, and send the credentials to start using our API.
URL-based versioning is supported in our API. Each version will be appended to the base url.
# Authentication
The CitizenPlane API uses Bearer Tokens to authenticate requests. All requests made whithout authentication will fail, as well as requests made over plain HTTP rather than HTTPS.
To get your token, send us a mail and we'll assign one for you.
# Errors
CitizenPlane uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the 4xx range indicate a request that failed given the information provided (e.g., a required parameter was omitted, a request has expired, a charge failed, etc.). Codes in the 5xx range indicate an internal error, which is rare.
# HTTP Status Codes Summary
| Code | Status | Meaning |
|---|---|---|
| 400 | Bad Request | Your request is invalid (e.g payload sent doesn't match request signature) |
| 401 | Unauthorized | Your API key is wrong |
| 403 | Forbidden | The requested action cannot be permitted (e.g process a booking on a ABORTED request) |
| 404 | Not Found | The specified data could not be found (e.g flight_id doesn't exist) |
| 409 | Conflict | The request wasn't processed. Potential reasons are: flight is no longer available or found in our cache, unacceptable price difference between request & booking, flight has already been booked, etc. |
| 422 | Unprocessable Entity | A validation error has occured (these are payment-related errors) |
| 500 | Internal Server Error | Error on CitizenPlane's end. Try again later and contact us if the issue remain |
# Test mode
Test mode tokens will be provided during your integration period. These tokens will allow you to search for flights and process bookings on a set of demo flights with no danger of spending any money or booking flights you don't want.
# Going live
Before switching to production, you'll need to complete a test phase to ensure your integration works correctly. During this phase, you should:
Send test PNRs covering different booking scenarios:
- Multiple adults and infants
- With and without checked bags
- With and without cabin bags
Once your test bookings are validated by our team, we will provide production API tokens
We recommend doing a few live test bookings in production with real payments to verify the full flow works as expected. These bookings will be canceled & refunded free of charge.
After successful live testing, you can fully switch your integration to production
Please contact our support team when you're ready to begin the go-live process. We'll guide you through the test scenarios and help ensure a smooth transition to production.
# Key concepts
The CitizenPlane API is very simple to use and implement. In only 4 steps, you can complete an entire booking flow, from searching a flight to processing the booking. You'll only need to understand a few concepts in order to get started with the CitizenPlane API.
# Booking requests
In order to process a booking, a booking request must be created first. Creating a booking request allows you to have the most up-to-date information regarding price & availability. You'll only need one or two request ids to confirm and process the booking.
The request will expire after 3 minutes and any booking using an id for an expired request will fail.
Check our development quickstart to get an overview of the complete booking flow.
# Customers
Customers in the CitizenPlane API are the entity (ie businesses) whose card is processed during the booking.
Customers must be identified with a unique customer_code, sent both at every step of the booking process. Customer codes are used to identify OTAs within CitizenPlane's system (e.g govoyagesfr for GoVoyages.fr (opens new window), kiwicom for Kiwi.com (opens new window)). If you want to know to list of supported OTAs or add your OTA and get a customer code, please contact us.
Note
Even when connecting directly to the API (without going through a third party), you must still specify a customer code. In this case, you will be provided with both an API token and a customer code.
# Exporting our content
If you need an exhaustive list of all the content we provide, you can contact our tech team to activate a drop of a CSV file on our ftp server.
We offer two specific formats: routes or flights. Formats specification will be detailed below. We recommend that you use the routes format, which is a lighter version of our complete export.
# Files structure
Depending on distribution agreements, several files can be dropped onto your FTP folder. You can have as many exports as you have customers with specific content distribution rules. For every other customers, a file containing our entire content will also be available. If no customers have specific distribution rules, you will only get the "all" content file.
Customers
Customers are identified by a customer code, sent during the flight search and subsequent steps (request, booking).
All files are appended with the API version currently in use.
TIP
To avoid any confusion, we've also kept the legacy file names: cp_routes.csv & cp_flights.csv. These legacy files will stay for a couple months to allow you to complete the migration to the new file naming structure.
Example routes export structure:
cp_routes_customerOne_v3.csv
cp_routes_customerTwo_v3.csv
cp_routes_all_v3.csv
cp_routes.csv // legacy name
Example flights export structure:
cp_flights_customerOne_v3.csv
cp_flights_customerTwo_v3.csv
cp_flights_all_v3.csv
cp_flights.csv // legacy name
# Routes format
departureAirport;arrivalAirport;departureDate
BSL;PRN;21.06.2025
MLH;PRN;21.06.2025
AAL;AYT;22.06.2025
ACE;CGN;22.06.2025
ADB;AMS;22.06.2025
ADB;CGN;22.06.2025
...
# Flights format
departureAirport;arrivalAirport;departureDate;marketingAirlineCode;marketingFlightNumber;operatingAirlineCode;operatingFlightNumber;bookingClass;classOfService;availableSeats;fareType;baseFareAmountAdult;supplementSumAmountAdult;totalFareAmountAdult;baseFareAmountChild;supplementSumAmountChild;totalFareAmountChild;baseFareAmountInfant;supplementSumAmountInfant;totalFareAmountInfant;currency;journeyDuration;overNight;stops;segmentFirstDepartureAirport;segmentFirstArrivalAirport;segmentFirstMarketingAirlineCode;segmentFirstMarketingFlightNumber;segmentFirstOperatingAirlineCode;segmentFirstOperatingFlightNumber;segmentFirstDepartureTime;segmentFirstArrivalTime;segmentFirstBookingClass;segmentFirstClassofService;segmentSecondDepartureAirport;segmentSecondArrivalAirport;segmentSecondMarketingAirlineCode;segmentSecondMarketingFlightNumber;segmentSecondOperatingAirlineCode;segmentSecondOperatingFlightNumber;segmentSecondDepartureTime;segmentSecondArrivalTime;segmentSecondBookingClass;segmentSecondClassofService;segmentThirdDepartureAirport;segmentThirdArrivalAirport;segmentThirdMarketingAirlineCode;segmentThirdMarketingFlightNumber;segmentThirdOperatingAirlineCode;segmentThirdOperatingFlightNumber;segmentThirdDepartureTime;segmentThirdArrivalTime;segmentThirdBookingClass;segmentThirdClassofService;baggageFirstWeight;baggageFirstAmount;baggageSecondWeight;baggageSecondAmount;baggageThirdWeight;baggageThirdAmount;checkInFee;airportCheckInFee;creditCardFee;combinationGroup;flightKey;tariffClass
NAS;FLL;19.11.2025;WU;102;WU;102;Y;Y;*;OW;;;243.08;;;243.08;;;63.35;EUR;;0;0;NAS;FLL;WU;102;WU;102;11:00;11:50;Y;Y;;;;;;;;;;;;;;;;;;;;;18;0.00;;;;;;;0.03;;;
PRN;DUS;19.11.2025;IV;620;IV;620;Y;Y;*;OW;;;324.45;;;324.45;;;85.90;EUR;;0;0;PRN;DUS;IV;620;IV;620;11:05;13:30;Y;Y;;;;;;;;;;;;;;;;;;;;;20;0.00;;;;;;;0.03;;;
PRN;STR;19.11.2025;IV;630;IV;630;Y;Y;*;OW;;;224.54;;;224.54;;;58.09;EUR;;0;0;PRN;STR;IV;630;IV;630;11:50;13:50;Y;Y;;;;;;;;;;;;;;;;;;;;;20;0.00;;;;;;;0.03;;;
PRN;MLH;19.11.2025;C3;1806;C3;1806;Y;Y;2;OW;;;115.36;;;115.36;;;30.90;EUR;;0;0;PRN;MLH;C3;1806;C3;1806;13:30;15:40;Y;Y;;;;;;;;;;;;;;;;;;;;;20;0.00;;;;;;;0.03;;;
...
# Endpoints
# Search flights
Use this endpoint to retrieve a list of flights that you can book. Each flight is identified by a unique ID that will be needed to create a request.
GET
https://booking-api.citizenplane.com/v3/flights
# Our flight content
All flights prices are in EUR only. All flights can have one, two or three segments and are non-refundable, non-exchangeable with no flex fares. We sell both one-way & round trip fares, although round trip fares are not always available (while one-way fares are always available).
# Query parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| origin | string | required | Origin airport IATA code (CDG for instance). Airport city codes are not currently supported. |
| destination | string | required | Destination airport IATA code (AYT for instance). Airport city codes are not currently supported. |
| start | date | optional | Date from which the flight search will be operated. Expected format: YYYY-MM-DD. Defaults to current day. |
| end | date | optional | Date until which the flight search will be operated, up to max +3 days from start. Expected format: YYYY-MM-DD. Defaults to start + 3 days. |
| seats | integer | optional | Minimum number of seats to search for. |
| customerCode | string | optional | Unique code to identify the customer making this request |
| next_page_id | string | optional | Specify the page to retrieve. You get the first next_page_id cursor at the end of the first page of results. Each page displays up to 100 results. |
Example request
curl "https://booking-api.citizenplane.com/v3/flights?origin=CDG&destination=AYT&start=2025-08-06&seats=1&customerCode={your_code}" \
-H "Accept: application/json" \
-H "Authorization: Bearer {your_api_token}"
# Response elements
| Field | Type | Description |
|---|---|---|
| cc_fee | float | Credit card fee amount in percentage. This fee is included in the total price when paying by credit card. Default is 0.03 (3%). |
| infant_price | float | Price for an infant ticket (under 2 years old). Can be null if infant tickets are not available. |
| included_airport_tax | float | Airport taxes and fees that are included in the total price. |
| price | float | Base fare price for an adult or child ticket, excluding bag fees |
Example response
{
"results": [
{
"id": 4014831,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 59.71,
"duration": "07:55",
"bags": {
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36.00
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"quantity": 1,
"price": 0.00
}
]
},
"departure_date": "2025-08-06 13:45:00",
"arrival_date": "2025-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": "2025-08-06 13:45:00",
"arrival_date": "2025-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": "2025-08-06 18:40:00",
"arrival_date": "2025-08-06 22:40:00",
"duration": "03:00"
}
],
"available_seats": 10,
"price": 483.07,
"infant_price": 129.78,
"cc_fee": 0.03
},
{
"id": 4014832,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 77.53,
"duration": "03:55",
"bags": {
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36.05
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"quantity": 1,
"price": 0.00
}
]
},
"departure_date": "2025-08-06 17:45:00",
"arrival_date": "2025-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": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"operating_carrier_flight_number": 511,
"operating_carrier": {
"name": "Sunexpress",
"iata_code": "XQ"
},
"marketing_carrier_flight_number": 511,
"marketing_carrier": {
"name": "Sunexpress",
"iata_code": "XQ"
},
"departure_date": "2025-08-06 17:45:00",
"arrival_date": "2025-08-06 22:40:00",
"duration": "03:55"
}
],
"available_seats": 10,
"price": 627.27,
"infant_price": 169.95,
"cc_fee": 0.03
},
{...},
{...},
],
"next_page_id": null
}
# Search one-way fares (required)
To search one-way fares and get complete flight details, use the /flights endpoint defined above. This endpoint returns all essential flight information including:
- Available fares
- Checked and cabin baggage options with dimensions and prices
- Flight segments and timings
- Available seats
# Search roundtrip fares (optional)
This endpoint is optional and should only be used to check if special roundtrip fares exist for your search criteria. It will return results if and only if specific roundtrip fares are available.
Important
You must always perform two separate one-way searches using the /flights endpoint to:
- Get complete flight details for both outbound and return flight
- Compare individual one-way fares with any available roundtrip fares to find the best fare available
GET
https://booking-api.citizenplane.com/v3/roundTrips
# Query parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| origin | string | required | Origin airport IATA code (CDG for instance). Airport city codes are not currently supported. |
| destination | string | required | Destination airport IATA code (AYT for instance). Airport city codes are not currently supported. |
| start | date | optional | Date from which the flight search will be operated. Expected format: YYYY-MM-DD. Defaults to current day. |
| end | date | optional | Date until which the flight search will be operated, up to max +3 days from start. Expected format: YYYY-MM-DD. Defaults to start + 3 days. |
| returnStart | date | optional | Date from which the return flight search will be operated. Expected format: YYYY-MM-DD. Defaults to start + 1 day. |
| returnEnd | date | optional | Date until which the return flight search will be operated, up to max +3 days from returnStart. Expected format: YYYY-MM-DD. Defaults to end + 1 days. |
| customerCode | string | optional | Unique code to identify the customer making this request |
| seats | integer | optional | Minimum number of seats to search for. |
| next_page_id | string | optional | Specify the page to retrieve. You get the first next_page_id cursor at the end of the first page of results. Each page displays up to 100 results. |
Example request
curl "https://booking-api.citizenplane.com/v3/roundTrips?origin=CDG&destination=AYT&start=2025-08-06&seats=1&returnStart=2025-08-13&customerCode={your_code}" \
-H "Accept: application/json" \
-H "Authorization: Bearer {your_api_token}"
# Response elements
| Field | Type | Description |
|---|---|---|
| checked_bags_prices | array | Array of prices for checked bags applicable for the whole roundtrip. Each element represents the total price for one checked bag on both outbound and return flights. |
| cabin_bags_prices | array | Array of prices for cabin bags applicable for the whole roundtrip. Each element represents the total price for one cabin bag on both outbound and return flights. |
| cc_fees | float | Credit card fee amount in percentage. This fee is included in the total price when paying by credit card. Default is 0.03 (3%). |
| infant_price | float | Price for an infant ticket (under 2 years old) for the roundtrip. Can be null if infant tickets are not available. |
| included_airport_tax | float | Total airport taxes and fees that are included in the roundtrip price. |
| price | float | Base fare price for an adult or child roundtrip ticket, excluding bag fees |
| available_seats | integer | Number of seats available at this fare for the roundtrip |
Example response
{
"fares": [
{
"outward_id": 4014831,
"return_id": 4014832,
"price": 600,
"infant_price": 200,
"checked_bags_prices": [
72
],
"cabin_bags_prices": [
0
],
"included_airport_tax": 72,
"cc_fees": 0.03,
"available_seats": 9
}
],
"flights": [
{
"id": 4014831,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 59.71,
"duration": "07:55",
"departure_date": "2025-08-06 13:45:00",
"arrival_date": "2025-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": "2025-08-06 13:45:00",
"arrival_date": "2025-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": "2025-08-06 18:40:00",
"arrival_date": "2025-08-06 22:40:00",
"duration": "03:00"
}
],
},
{
"id": 4014832,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 77.53,
"duration": "03:55",
"departure_date": "2025-08-06 17:45:00",
"arrival_date": "2025-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": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"operating_carrier_flight_number": 511,
"operating_carrier": {
"name": "Sunexpress",
"iata_code": "XQ"
},
"marketing_carrier_flight_number": 511,
"marketing_carrier": {
"name": "Sunexpress",
"iata_code": "XQ"
},
"departure_date": "2025-08-06 17:45:00",
"arrival_date": "2025-08-06 22:40:00",
"duration": "03:55"
}
],
},
{...},
{...},
],
"next_page_id": null
}
# Verify price and availability
The verify endpoint allows customers to confirm the latest price and availability for a specific flight using live data.
If the requested number of seats is available, the response will include the real-time seat price, currency, total price, and applicable airport taxes. The price is identical for adults and children. The infant_price field is only included if passengers.infants is greater than 0.
POST
https://booking-api.citizenplane.com/v3/flights/verify
# Parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| flight_id | integer | required | The unique flight ID to verify |
| customer_code | string | optional | Unique code to identify the customer making the request |
| passengers | object | required | Object specifying the number of adults, children, and infants (see below) |
# Passengers object
| Parameter | Type | Status | Description |
|---|---|---|---|
| adults | integer | required | Number of adults (min 1, max 9) |
| children | integer | optional | Number of children (default 0, max 9) |
| infants | integer | optional | Number of infants (default 0, max = adults) |
Example request
curl --location 'https://booking-api.citizenplane.com/v3/flights/verify' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--header 'Authorization: {your_api_token}' \
--data '{
"flight_id": 123456,
"passengers": {
"adults": 1,
"children": 0
},
"customer_code": "{your_customer_code}"
}'
Example response
{
"price": 50,
"currency": "EUR",
"infant_price": null,
"total_price": 50,
"included_airport_tax": 13.2
}
# Create a booking request
This endpoint creates booking requests for one or two flights (identified by their id). You can thus simultaneously create two different requests.
POST
https://booking-api.citizenplane.com/v3/requests
Expired requests
A request only lasts for five minutes. Past that deadline, a new request will have to be created to create a booking.
Bags
All available bag options (checked & cabin) returned in the flight search must be specified in the request payload, even if you don't want to book any bags. For example, if a flight offers two checked bag options (20kg and 30kg) and one cabin bag option, you must include all three options in your request with their quantities (set to 0 if not selected).
# Body parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| flight_ids | array | required | An array containing up to two flight_id |
| customer_code | string | optional | Unique code to identify the customer making this request |
| passengers | array | required | An array containing personal information for each passenger on this booking. See child arguments |
| bags | object | required | An object containing all available checked & cabin bag options for each flight, with their selected quantities. Every bag option returned in flight search must be included. See child arguments |
| fare_type | string | required | type of fare chosen for the given flight(s). Two options possible: one_way or round_trip. Chose round_trip if you want to book a round trip fare, chose one_way otherwise. |
# Passengers parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| first_name | string | required | Passenger's first name |
| middle_name | string | optional | Passenger's middle name (if applicable) |
| last_name | string | required | Passenger's last name |
| gender | string | required | Passenger's gender |
| date_of_birth | string | required | Passenger's date of birth |
| birth_country | string | optional | Passenger's birth country |
| passport_number | string | optional | Passenger's passport number |
| passport_expiry_date | string | optional | Passenger's passport expiry date |
| passport_country_of_issue | string | optional | Passenger's passport country of issue |
| nationality | string | optional | Passenger's nationality |
# Bags
The bags parameter is an object whose keys must match the ids sent in the flight_ids parameter.
For each flight, you must include all checked & cabin bag options that were returned in the flight search response, even if you don't want to book any bags. For example, if a flight offers a 20kg and 30kg checked bag option, both must be specified in your request with their quantities (set to 0 if not selected).
Bags are not attached to a passenger. They are attached to a booking. If a booking contains three passengers, you can book up to three checked bags and three cabin bags.
Each checked bags option contains two parameters:
| Parameter | Type | Status | Description |
|---|---|---|---|
| quantity | number | required | the number of the chosen checked bag. The quantity must not exceed the number of passengers |
| weight | number | required | the weight of the chosen checked bag. |
Cabin bags can however be defined both by their dimensions and weight. You can chose to send us either the dimensions or the weight, or both.
| Parameter | Type | Status | Description |
|---|---|---|---|
| quantity | number | required | the number of the chosen cabin bag. The quantity must not exceed the number of passengers |
| dimensions | object | required | dimensions of the chosen cabin bag. Dimensions are composed of width, height and length |
| weight | number | required | the weight of the chosen cabin bag. |
Example request
curl 'https://booking-api.citizenplane.com/v3/requests' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_token}' \
-d '{
"flight_ids": [
4014831,
3795943
],
"passengers": [
{
"first_name": "John",
"last_name": "Doe",
"gender": "male",
"date_of_birth": "1970-01-01",
"passport_number": "ABCDE123",
"passport_expiry_date": "01/01/2040",
"nationality": "fr",
"passport_country_of_issue": "fr"
},
{
"first_name": "Jane",
"last_name": "Doe",
"gender": "female",
"date_of_birth": "1975-06-15",
"passport_number": "XYZ789",
"passport_expiry_date": "01/01/2040",
"nationality": "fr",
"passport_country_of_issue": "fr"
}
],
"bags": {
"4014831": {
"checked": [
{
"weight": 20,
"quantity": 1
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"weight": 10,
"quantity": 1
}
]
},
"3795943": {
"checked": [
{
"weight": 20,
"quantity": 1
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"weight": 10,
"quantity": 1
}
]
}
},
"fare_type": one_way,
"customer_code": {your_customer_code}
}'
Example response
{
"requests": [
{
"id": 244401,
"passengers": {
"adults": 2,
"children": 0,
"infants": 0
},
"price": 469,
"infant_price": null,
"bags_fee": {
"checked": 36,
"cabin": 0
},
"cc_fee": 0,
"customer_code": "{your_customer_code}",
"created_at": "2025-06-28T00:02:25.317Z",
"flight": {
"id": 4014831,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 56.28,
"duration": "07:55",
"bags": {
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36.00
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"weight": 10,
"quantity": 1,
"price": 0.00
}
]
},
"departure_date": "2025-08-06 13:45:00",
"arrival_date": "2025-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": "2025-08-06 13:45:00",
"arrival_date": "2025-08-06 16:05:00"
},
{
"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": "2025-08-06 18:40:00",
"arrival_date": "2025-08-06 22:40:00"
}
]
},
"customer": {
"name": "{your_company_name}",
"email": null,
"is_company": true,
"company_codes": ["{your_customer_code}"],
"verified": true,
"phonenumber": null,
"gender": null,
"address": null,
"postal_code": null,
"city_name": null,
"country_code": "{your_country_code}",
"country_name": "{your_country_name}",
"vat_number": null,
"registration_number": null,
"created_at": "2025-01-27T09:56:15.773Z",
"updated_at": "2025-01-31T10:26:20.424Z"
}
},
{
"id": 244402,
"passengers": {
"adults": 2,
"children": 0,
"infants": 0
},
"price": 555,
"infant_price": null,
"cc_fee": 0,
"bags_fee": {
"checked": 36,
"cabin": 0
},
"customer_code": "{your_customer_code}",
"created_at": "2025-06-28T00:02:25.319Z",
"flight": {
"id": 3795943,
"need_apis": false,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 66.6,
"duration": "04:20",
"bags": {
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36.00
}
],
"cabin": [
{
"weight": 10,
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"quantity": 1,
"price": 0.00
}
]
},
"departure_date": "2025-08-13 13:35:00",
"arrival_date": "2025-08-13 16:55:00",
"origin": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"destination": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"segments": [
{
"via_airport": null,
"origin": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"destination": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"operating_carrier_flight_number": 510,
"operating_carrier": {
"name": "Sunexpress",
"iata_code": "XQ"
},
"marketing_carrier_flight_number": 510,
"marketing_carrier": {
"name": "Sunexpress",
"iata_code": "XQ"
},
"departure_date": "2025-08-13 13:35:00",
"arrival_date": "2025-08-13 16:55:00"
}
]
},
"customer": {
"name": "{your_company_name}",
"email": null,
"is_company": true,
"company_codes": ["{your_customer_code}"],
"verified": true,
"phonenumber": null,
"gender": null,
"address": null,
"postal_code": null,
"city_name": null,
"country_code": "{your_country_code}",
"country_name": "{your_country_name}",
"vat_number": null,
"registration_number": null,
"created_at": "2025-01-27T09:56:15.773Z",
"updated_at": "2025-01-31T10:26:20.424Z"
}
}
]
}
# Cancel a booking request
Use this endpoint to cancel an existing booking request that is no longer needed. Once cancelled, you can restart a complete booking flow, which will then create a new booking request.
Important
Only pending booking requests can be cancelled
POST
https://booking-api.citizenplane.com/v3/requests/{request_id}/cancel
# Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
request_id | string | Yes | The unique identifier of the booking request to cancel |
Example request
curl -X POST 'https://booking-api.citizenplane.com/v3/requests/123456789/cancel' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_token}'
Example response
{
"success": true
}
# Create a Payment Method
WARNING
This step can be omitted if you are authorized to use the self_psp setting and go directly to create a booking.
This external endpoint from Stripe (opens new window) generates a payment_method which will be used in the next step to process the payment and confirm the booking.
POST
https://api.stripe.com/v1/payment_methods
# Using Stripe as payment provider
CitizenPlane's API uses Stripe (opens new window) to process credit card payments. In order to be PCI-compliant (opens new window), CitizenPlane cannot directly process the customer's credit card data.
The Stripe API also returns JSON-encoded responses and accepts form-encoded requests bodies. Several client libraries are available for an easy integration, as explained in the Stripe API documentation (opens new window)
Failed payment
If the payment fails for any reason, the associated request will be considered as aborted and the process is to be started again. In case of a failed payment, the corresponding Stripe error code (opens new window) will be returned.
# Authorization
To perform a request to the Stripe API, you will need to have a Stripe restricted token (e.g sk_test_123456789abcdedf) which we will give along with your CitizenPlane API credentials.
# Card parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| number | string | required | The card number, as a string without any separators |
| exp_month | number | required | Two-digit number representing the card's expiration month |
| exp_year | number | required | Four-digit number representing the card's expiration year |
| cvc | string | required | Card security code. Highly recommended to always include this value, but it's required only for accounts based in European countries |
| type | string | required | The type of the PaymentMethod (e.g card). An additional hash is included on the PaymentMethod with a name matching this value. It contains additional information specific to the PaymentMethod type. |
Example request
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
Example response
{
"id": "pm_1Ixu0UCLoBt04Rh8x4xr4E2a",
"object": "payment_method",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": "42424",
"state": null
},
"email": "jenny@example.com",
"name": null,
"phone": "+15555555555"
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 8,
"exp_year": 2025,
"fingerprint": "vsS0JwU3bB3xdpsO",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": ["visa"],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 123456789,
"customer": null,
"livemode": false,
"metadata": {
"order_id": "123456789"
},
"type": "card"
}
Payment method
The payment method id will always be appended with pm_ followed by a random string of numbers and letters.
# Create a booking
This endpoint validates the previously created booking requests and processes the booking as well as the associated payment. Once the booking is created, the customer will receive a confirmation by email along with the ticket(s). Passengers can also check their reservation on our website (opens new window). If the booking is linked to multiple requests (up to three), the same PNR will be issued for each booking, so that the customer can find all the bookings with a unique reference.
POST
https://booking-api.citizenplane.com/v3/bookings
# Body parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| request_ids | integer | required | An array containing up to two different request_id |
| external_id | string | optional | The id used on your side to identify the booking. This field is optional, though highly recommended in case we need to retrieve a booking in your system |
| customer_code | string | optional | A unique code used to identify the customer making this reservation (must be already added in CitizenPlane's system). |
| first_name | string | required | The booker's first name |
| last_name | string | required | The booker's last name |
| gender | string | required | The booker's gender |
| string | required | The booker's email | |
| phonenumber | string | required | The booker's phone number |
| address | string | optional | The booker's address |
| city_name | string | optional | The booker's city name |
| country_code | string | optional | The booker's country code |
| postal_code | string | optional | The booker's postal code |
| self_psp | boolean | required (if authorized) | This lets you create a booking directly after request step, without needing payment_method to be specified as it is already handled on your side. |
| payment_method | string | required (if not using self_psp) | An id obtained by sending credit card information to Stripe's API (opens new window) |
| three_d_secure | object | optional (if not using self_psp) | 3D Secure results obtained previously with your 3D Secure provider |
self_psp
The self_psp parameter allows you to book using a deposit model. You'll have to be explicitly authorized for your account to use this setting. A regular payment will be required to ensure that your balance is always positive. A tracking dasbhoard with list of bookings & balance information can be provided upon request.
# 3D Secure parameters
For more in depth information regarding 3D Secure results you may have to pass with booking details, please refer to Stripe API Reference (opens new window)
| Parameter | Type | Status | Description |
|---|---|---|---|
| version | string | required | The version of 3D Secure that was performed. Possible enum values: 1.0.2, 2.1.0, 2.2.0 |
| electronic_commerce_indicator | string | required | The Electronic Commerce Indicator (ECI) is returned by your 3D Secure provider and indicates what degree of authentication was performed. (If your provider returns an ECI of 07 or 00, please discard it: authentication has failed. If your provider returns an ECI of 06 or 01, attempt was only acknowledged and payment won't be processed by CitizenPlane. If desired, you may make an unauthenticated payment by leaving three_d_secure unset.) |
| cryptogram | string | required | The cryptogram, also known as the “authentication value” (AAV, CAVV or AEVV). This value is 20 bytes, base64-encoded into a 28-character string. (Most 3D Secure providers will return the base64-encoded version, which is what you should specify here.) |
| transaction_id | boolean | required | For 3D Secure 1, the XID. For 3D Secure 2, the Directory Server Transaction ID (dsTransID). |
curl 'https://booking-api.citizenplane.com/v3/bookings' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_token}' \
-d '{
"request_ids": [ 244401, 244402 ],
"customer_code": "{your_customer_code}",
"external_id": "ABC123",
"first_name": "John",
"last_name": "Doe",
"gender": "male",
"email": "john.doe@example.com",
"phonenumber": "+33600000000",
"payment_method": "pm_1IxrDoCLoBt04Rh8deU0seLG",
"three_d_secure": {
"version": "2.2.0",
"electronic_commerce_indicator": "05",
"cryptogram": "4BQwsg4yuKt0S1LI1nDZTcO9vUM=",
"transaction_id": "f879ea1c-aa2c-4441-806d-e30406466d79"
},
"self_psp": true // TO USE ONLY IF YOUR TEAM IS AUTHORIZED TO APPLY THIS SETTING
}'
Ticket issuance
A successful booking (200 status response) means the ticket is considered as issued. However, for non-charter flights, the airline PNR may take up to 2 hours to be generated after booking. You should poll the GET booking endpoint regularly to check if the pnr_airline field has been populated.
Example response
{
"bookings": [
{
"id": 253571,
"flight_id": 4014831,
"request_id": 244401,
"cp_reference": "CPKVLF",
"pnr_airline": null,
"status": "AWAITING_PNR_AIRLINE",
"first_name": "john",
"last_name": "doe",
"gender": "male",
"email": "john.doe@example.com",
"phonenumber": "0033600000000",
"address": "n/a",
"country_code": "n/a",
"postal_code": "n/a",
"passenger_count": 2,
"infant_count": 0,
"passengers": [
{
"id": 426678,
"booking_id": 253571,
"gender": "male",
"last_name": "doe",
"first_name": "john",
"is_infant": false,
"date_of_birth": "01/01/2001",
"nationality": null,
"confirmed": false,
"confirmed_at": null,
"canceled": false,
"canceled_at": null,
"passport_number": null,
"passport_expiry_date": null,
"passport_country_of_issue": null,
"created_at": "2025-06-28T00:34:21.461Z",
"updated_at": "2025-06-28T00:34:21.317Z",
"birth_country": null
},
{
"id": 426679,
"booking_id": 253571,
"gender": "female",
"last_name": "doe",
"first_name": "jane",
"is_infant": false,
"date_of_birth": "01/01/2002",
"nationality": null,
"confirmed": false,
"confirmed_at": null,
"canceled": false,
"canceled_at": null,
"passport_number": null,
"passport_expiry_date": null,
"passport_country_of_issue": null,
"created_at": "2025-06-28T00:34:21.461Z",
"updated_at": "2025-06-28T00:34:21.317Z",
"birth_country": null
}
],
"bags": {
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36.00
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"weight": 10,
"quantity": 1,
"price": 0.00
}
]
},
"cc_fee": 0,
"bags_fee": {
"checked": 36,
"cabin": 0
},
"created_at": "2025-06-28T00:34:21.416Z",
"price": 469,
"infant_price": 126,
"total_price": 938,
"external_id": "ABC123",
"flight": {
"id": 4014831,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 56.28,
"duration": "07:55",
"departure_date": "2025-08-06 13:45:00",
"arrival_date": "2025-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": "2025-08-06 13:45:00",
"arrival_date": "2025-08-06 16:05:00"
},
{
"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": "2025-08-06 18:40:00",
"arrival_date": "2025-08-06 22:40:00"
}
]
}
},
{
"id": 253572,
"flight_id": 3795943,
"request_id": 244402,
"cp_reference": "CPKVLF",
"pnr_airline": null,
"status": "AWAITING_PNR_AIRLINE",
"first_name": "john",
"last_name": "doe",
"gender": "male",
"email": "john.doe@example.com",
"phonenumber": "0033600000000",
"address": "n/a",
"country_code": "n/a",
"postal_code": "n/a",
"passenger_count": 2,
"infant_count": 0,
"passengers": [
{
"id": 426679,
"booking_id": 253572,
"gender": "male",
"last_name": "doe",
"first_name": "john",
"is_infant": false,
"date_of_birth": "01/01/2001",
"nationality": null,
"confirmed": false,
"confirmed_at": null,
"canceled": false,
"canceled_at": null,
"passport_number": null,
"passport_expiry_date": null,
"passport_country_of_issue": null,
"created_at": "2025-06-28T00:34:21.706Z",
"updated_at": "2025-06-28T00:34:21.659Z",
"birth_country": null
},
{
"id": 426680,
"booking_id": 253572,
"gender": "female",
"last_name": "doe",
"first_name": "jane",
"is_infant": false,
"date_of_birth": "01/01/2002",
"nationality": null,
"confirmed": false,
"confirmed_at": null,
"canceled": false,
"canceled_at": null,
"passport_number": null,
"passport_expiry_date": null,
"passport_country_of_issue": null,
"created_at": "2025-06-28T00:34:21.706Z",
"updated_at": "2025-06-28T00:34:21.659Z",
"birth_country": null
}
],
"bags": {
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36.00
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"weight": 10,
"quantity": 1,
"price": 0.00
}
]
},
"bags_fee": {
"checked": 36,
"cabin": 0
},
"cc_fee": 0,
"created_at": "2025-06-28T00:34:21.695Z",
"price": 555,
"infant_price": 150,
"total_price": 1110,
"external_id": "ABC123",
"flight": {
"id": 3795943,
"need_apis": false,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 66.6,
"duration": "04:20",
"departure_date": "2025-08-13 13:35:00",
"arrival_date": "2025-08-13 16:55:00",
"origin": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"destination": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"segments": [
{
"via_airport": null,
"origin": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"destination": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"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": "2025-08-13 13:35:00",
"arrival_date": "2025-08-13 16:55:00"
}
]
}
}
]
}
# Retrieve booking and flight information
This endpoint retrieves one or two bookings (in case of round trip bookings) and flight information using the cp_reference returned at the booking step. The same cp_reference that can be used for passengers to check their reservation on our website (opens new window). All flight changes along with the final_flight will also be returned, allowing you to automatically check if the flight has received a schedule change.
Two flight objects will be returned in the response:
flight, which is the original flight on which the booking was initially madefinal_flight, the actual flight, with all up to date information
GET
https://booking-api.citizenplane.com/v3/bookings
# Query parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| cp_reference | string | required | The unique reference returned by /bookings linked to up to 2 bookings |
# Flight changes parameters
The flight_changes key is an array listing all changes that occurred on the flight and their respective status which range from WAITING, SEEN, ACCEPTED or REFUSED, depending on the PAX actions.
For each flight_change object you'll have a data key containing an old & new flight, listing all keys that have changed because of the schedule change event.
As each flight can have up to three segments, each key will be prepended with segment_{index} with index ranging from 1 to 3. For instance, if the origin_airport key has changed for the first segment, it will be listed as segment_1_origin_airport. The only key that is specific for the whole trip is the need_apis key which can be either true or false.
Here are are the keys that can change for each segment:
| Parameter | Type | Description |
|---|---|---|
| origin_airport | string | three letters IATA code (e.g CDG) |
| destination_airport | string | three letters IATA code |
| departure_date | string | YYYY-MM-DD HH:mm:ss formatted date |
| arrival_date | string | YYYY-MM-DD HH:mm:ss formatted date |
| flight_number | string | combination of two letters iata airline code and up to four digits (e.g AF1234) |
| origin_timezone | string | timezone of the origin airport, only present with the origin_airport (e.g Europe/Berlin) |
| destination_timezone | string | timezone of the origin airport, only present with the origin_airport (e.g Europe/Berlin) |
| airline_name | string | name of the arline, only present with airline_iata_code (e.g Air France) |
| airline_iata_code | string | two letters IATA code (e.g AF) |
| origin_terminal_label | string | corresponding terminal of the airport (e.g 2) |
| destination_terminal_label | string | corresponding terminal of the airport |
Example request
curl 'https://booking-api.citizenplane.com/v3/bookings?cp_reference=CPKVLF' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_token}'
Example response
{
"bookings": [
{
"id": 253571,
"flight_id": 4014831,
"request_id": 244401,
"cp_reference": "CPKVLF",
"pnr_airline": null,
"passenger_count": 1,
"infant_count": 0,
"type": "outward",
"status": "AWAITING_PNR_AIRLINE",
"passengers": [
{
"id": 426678,
"booking_id": 253571,
"gender": "male",
"last_name": "doe",
"first_name": "john",
"is_infant": false,
"date_of_birth": "01/01/2001",
"nationality": null,
"confirmed": false,
"confirmed_at": null,
"canceled": false,
"canceled_at": null,
"passport_number": null,
"passport_expiry_date": null,
"passport_country_of_issue": null,
"created_at": "2025-06-28T00:34:21.461Z",
"updated_at": "2025-06-28T00:34:21.317Z",
"birth_country": null
}
],
"bags": {
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36.00
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"weight": 10,
"quantity": 1,
"price": 0.00
}
]
},
"created_at": "2025-06-28T00:34:21.416Z",
"flight_changes": [
{
"status": "ACCEPTED",
"data": {
"newFlight": {
"flight_id": 4086743,
"segment_1_departure_date": "2025-08-06 14:45:00",
"segment_1_arrival_date": "2025-08-06 17:05:00",
"segment_1_origin_timezone": "Europe/Paris",
"segment_1_destination_timezone": "Europe/Warsaw",
},
"oldFlight": {
"flight_id": 4014831,
"segment_1_departure_date": "2025-08-06 13:45:00",
"segment_1_arrival_date": "2025-08-06 16:05:00",
"segment_1_origin_timezone": "Europe/Paris",
"segment_1_destination_timezone": "Europe/Warsaw",
}
},
"created_at": "2023-10-23T11:09:32.721Z",
"updated_at": "2023-10-25T12:00:03.088Z"
}
"flight": {
"id": 4014831,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 56.28,
"duration": "07:55",
"departure_date": "2025-08-06 13:45:00",
"arrival_date": "2025-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": "2025-08-06 13:45:00",
"arrival_date": "2025-08-06 16:05:00"
},
{
"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": "2025-08-06 18:40:00",
"arrival_date": "2025-08-06 22:40:00"
}
]
},
"final_flight": {
"id": 4086743,
"need_apis": true,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 56.28,
"duration": "07:55",
"departure_date": "2025-08-06 14:45:00",
"arrival_date": "2025-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": "2025-08-06 14:45:00",
"arrival_date": "2025-08-06 17:05:00"
},
{
"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": "2025-08-06 18:40:00",
"arrival_date": "2025-08-06 22:40:00"
}
]
}
},
{
"id": 253572,
"flight_id": 3795943,
"request_id": 244402,
"cp_reference": "CPKVLF",
"pnr_airline": null,
"passenger_count": 1,
"infant_count": 0,
"type": "return"
"status": "AWAITING_PNR_AIRLINE",
"passengers": [
{
"id": 426679,
"booking_id": 253572,
"gender": "male",
"last_name": "doe",
"first_name": "john",
"is_infant": false,
"date_of_birth": "01/01/2001",
"nationality": null,
"confirmed": false,
"confirmed_at": null,
"canceled": false,
"canceled_at": null,
"passport_number": null,
"passport_expiry_date": null,
"passport_country_of_issue": null,
"created_at": "2025-06-28T00:34:21.706Z",
"updated_at": "2025-06-28T00:34:21.659Z",
"birth_country": null
}
],
"bags": {
"checked": [
{
"weight": 20,
"quantity": 1,
"price": 36.00
}
],
"cabin": [
{
"dimensions": {
"width": 56,
"height": 45,
"length": 25
},
"weight": 10,
"quantity": 1,
"price": 0.00
}
]
},
"created_at": "2025-06-28T00:34:21.695Z",
"flight_changes": null,
"flight": {
"id": 3795943,
"need_apis": false,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 66.6,
"duration": "04:20",
"departure_date": "2025-08-13 13:35:00",
"arrival_date": "2025-08-13 16:55:00",
"origin": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"destination": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"segments": [
{
"via_airport": null,
"origin": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"destination": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"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": "2025-08-13 13:35:00",
"arrival_date": "2025-08-13 16:55:00"
}
]
},
"flight": {
"id": 3795943,
"need_apis": false,
"cabin_class": "economy",
"booking_class": "Y",
"included_airport_tax": 66.6,
"duration": "04:20",
"departure_date": "2025-08-13 13:35:00",
"arrival_date": "2025-08-13 16:55:00",
"origin": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"destination": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"segments": [
{
"via_airport": null,
"origin": {
"iata_code": "AYT",
"city_name": "Antalya",
"country_name": "Turkey",
"timezone": "Europe/Istanbul"
},
"destination": {
"iata_code": "CDG",
"city_name": "Paris",
"country_name": "France",
"timezone": "Europe/Paris"
},
"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": "2025-08-13 13:35:00",
"arrival_date": "2025-08-13 16:55:00"
}
]
}
}
]
}
# Update PNR (suppliers only)
The patchPNR endpoint is designed to update the pnr_airline field of a specific booking. If the airline PNR is not returned at booking time, then you should call this endpoint as soon as possible, once the PNR is available.
For suppliers only
This endpoint is reserved for suppliers only and should not be used by distribution partners. Distribution partners should only use the retrieve booking and flight information endpoint to get up-to-date flight & booking information.
The updated booking will be returned in the response.
PATCH
https://booking-api.citizenplane.com/v3/bookings/{pnr}/pnr
# Parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
| pnr | string | required | The internal CP pnr reference of the booking to be updated (ie starting with CP). |
# Rules for PNR Identification
The PNR is not a unique identifier. To manage different travel segments, we follow these rules:
- One-way:
CPPNR - Outward:
CPPNR-1 - Return:
CPPNR-2
# Examples:
- A one-way trip will be identified as
CP1234. - An outward trip will be identified as
CP1234-1. - A return trip will be identified as
CP1234-2.
# Payload
| Parameter | Type | Status | Description |
|---|---|---|---|
| pnr_airline | string | required | The new PNR to update the booking with. |
Example request
curl 'https://booking-api.citizenplane.com/v3/bookings/ABCDEF/pnr' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_token}'
-d '{
"pnr_airline": "XXXXXX"
}'
Example response
{
"id": 860451,
"flight_id": 122649828,
"request_id": 1533287,
"cp_reference": "CP9I3P",
"first_name": "John",
"last_name": "Doe",
"gender": "male",
"email": "john.doe@citizenplane.com",
"phonenumber": "610433851033",
"address": "n/a",
"country_code": "en",
"postal_code": "n/a",
"passenger_count": 0,
"infant_count": 0,
"pnr_airline": "XXXXXX"
}