# 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:

  1. Search — Find available offers for one-way or round-trip itineraries
  2. Get Offer — Retrieve verified pricing and fare details for a specific flight combination
  3. Get Fares — Retrieve all available fares for an offer
  4. Get Baggage — Retrieve paid baggage options (checked and cabin)
  5. Create Order — Submit passenger details, baggage selections, and contact information
  6. Confirm Order — Trigger the actual booking
  7. 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 OPENCONFIRMINGBOOKED statuses
  • 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_IN and CABIN, 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 passengers field is now required
  • flights has been renamed to flight_ids
  • is_infant boolean for passengers has been dropped

/bookings

  • The passengers field is no longer required
  • requests has been renamed to request_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_number key has been removed and replaced by two keys: operating_carrier_flight_number and marketing_carrier_flight_number, which are now into segments. Typing is also changed from string to integer.
  • airline key has been removed and replaced by two distincts objects, marketing_carrier and operating_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_method and three_d_secure fields can be substituted by self_psp if 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_token field 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.

3D Secure workflow

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"
  }
}'

DEPRECATED

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.