# Development quickstart
Learn how to search and book a flight through the CitizenPlane (opens new window) v4 API.
The CitizenPlane v4 API provides a standardized interface for distributors to search, price, order and book flights. The API follows an NDC-inspired booking flow. CitizenPlane (opens new window) only supports EUR as its currency.
# Testing
You'll be given a sandbox API key for the implementation phase & testing process. You can search & book test flights using this sandbox key. No real charge will be induced.
# Complete flow
The search-to-booking flow with the v4 API is as follows:
# Step 1: Search Offers
Search for available flight offers matching the traveler's criteria. You can search for one-way or round-trip itineraries using either airport codes or city codes.
Flight content
Please note that all our flights are non-refundable, non-exchangeable and have no flex fares. Only ECONOMY class is currently supported.
# Implementation
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
}'
# Example 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,
"sourceAmount": 627.27,
"currency": "EUR"
}
},
"paymentOptions": [
{ "type": "cash", "fee": { "amount": 0, "currency": "EUR" } }
],
"connectivityStandard": "NDC"
}
]
}
The response contains an array of offers, each with flight segments, pricing, and included baggage information.
Go to the API reference
# Step 2: Get Offer
Once you've identified an interesting offer from the search results, retrieve the detailed offer by providing the exact flight sections. This step verifies availability and pricing with the supplier.
The returned offer includes:
- Detailed fare information with per-passenger pricing
- Included baggage details
- Additional passenger information requirements (e.g. passport needed)
- An offer ID valid for 30 minutes
# Implementation
curl 'https://booking-api.citizenplane.com/v4/offer' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}' \
-d '{
"adults": 1,
"children": 0,
"infants": 0,
"tripType": "ONE_WAY",
"isResidentDiscountEligible": false,
"outboundSegment": {
"sections": [
{
"origin": "CDG",
"destination": "AYT",
"departureDate": "2025-08-06T17:45:00",
"arrivalDate": "2025-08-06T22:40:00",
"flightNumber": "511",
"operatingCarrierCode": "XQ",
"marketingCarrierCode": "XQ"
}
]
},
"inboundSegment": null
}'
# Example Response
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"outboundSegment": {
"sections": [
{
"origin": "CDG",
"destination": "AYT",
"departureDate": "2025-08-06T17:45:00",
"arrivalDate": "2025-08-06T22:40:00",
"travelClass": "ECONOMY",
"flightNumber": "511",
"departureTerminal": "1",
"arrivalTerminal": "1",
"operatingCarrierCode": "XQ",
"marketingCarrierCode": "XQ",
"technicalStops": []
}
]
},
"cheapestFare": {
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"passengerPricingByPassengerType": {
"ADULT": {
"fareAmount": { "amount": 627.27, "currency": "EUR" },
"taxAmount": { "amount": 0, "currency": "EUR" },
"numberOfPassengers": 1
}
},
"includedBaggage": [
{ "quantity": 1, "descriptor": { "weight": 20 }, "type": "CHECK_IN" },
{ "quantity": 1, "descriptor": { "weight": 10, "dimensions": { "width": 56, "height": 45, "depth": 25 } }, "type": "CABIN" }
],
"totalPrice": { "amount": 627.27, "currency": "EUR" }
},
"providerAdditionalPassengerInformationByPassengerType": {
"ADULT": {
"providerIdentificationTypeOptions": ["PASSPORT"],
"providerRequiredAdditionalFields": ["BIRTH_DATE"]
}
},
"paymentOptions": [
{ "type": "cash", "fee": { "amount": 0, "currency": "EUR" } }
],
"connectivityStandard": "NDC",
"validatingCarrier": "XQ"
}
Offer TTL
The offer is cached for 30 minutes. After that, you must retrieve a new offer.
Go to the API reference
# Step 3: Get Baggage Options
Retrieve the available paid baggage options (checked and cabin) for the offer. This returns only the paid options — free baggage is already included in the offer's includedBaggage field.
# Implementation
curl 'https://booking-api.citizenplane.com/v4/offer/{offerId}/baggage' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}'
# Example Response
{
"fareBaggageOptions": [
{
"fareId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"itineraryOptions": [],
"outboundSegmentOptions": [
{
"type": "CHECK_IN",
"maxPiecesPerPassenger": 1,
"maxKilosPerPiece": 20,
"pricePerPiece": { "amount": 36.05, "currency": "EUR" }
},
{
"type": "CABIN",
"maxPiecesPerPassenger": 1,
"maxKilosPerPiece": 10,
"pricePerPiece": { "amount": 15.00, "currency": "EUR" }
}
],
"inboundSegmentOptions": null
}
]
}
Go to the API reference
# Step 4: Create Order
Create an order with passenger details, selected baggage, and customer contact information. The offer ID and fare ID must be provided as query parameters.
# Implementation
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"
},
"phone": { "number": "+33600000000", "countryCode": "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 }
}'
# Example Response
{
"id": "d4e5f6a7-b8c9-0123-defg-234567890123",
"offer": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"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"
}
]
},
"fare": {
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"passengerPricingByPassengerType": {
"ADULT": {
"fareAmount": { "amount": 627.27, "currency": "EUR" },
"taxAmount": { "amount": 0, "currency": "EUR" },
"numberOfPassengers": 1
}
},
"totalPrice": { "amount": 627.27, "currency": "EUR" }
},
"connectivityStandard": "NDC"
},
"passengers": [
{
"number": 1,
"type": "ADULT",
"title": "MR",
"name": "John",
"firstLastName": "Doe",
"gender": "MALE",
"birthDate": "1990-01-15",
"nationalityCountryCode": "FR"
}
],
"selectedBaggage": [
{
"passengerNumber": 1,
"outboundSegmentBaggageSelection": [
{
"type": "CHECK_IN",
"numberOfPieces": 1,
"kilosPerPiece": 20,
"pricePerPiece": { "amount": 36.05, "currency": "EUR" }
}
]
}
],
"status": "OPEN",
"totalPrice": { "amount": 663.32, "currency": "EUR" }
}
The response returns an order with status OPEN and a unique order ID.
Go to the API reference
# Step 5: Confirm Order
Confirm the order to trigger the actual booking. Once confirmed, a reservation ID is generated and the booking is processed.
WARNING
An order can only be confirmed once. Duplicate confirmation requests will be rejected.
# Implementation
curl -X POST 'https://booking-api.citizenplane.com/v4/order/{orderId}/confirm' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}'
# Example Response
{
"id": "d4e5f6a7-b8c9-0123-defg-234567890123",
"offer": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"outboundSegment": { "sections": [...] },
"fare": { ... },
"connectivityStandard": "NDC"
},
"passengers": [...],
"selectedBaggage": [...],
"selectedSeats": [],
"customerContact": { ... },
"status": "CONFIRMING",
"reservationId": "e5f6a7b8-c9d0-1234-efgh-345678901234",
"totalPrice": { "amount": 663.32, "currency": "EUR" }
}
The response returns the order with status CONFIRMING and a reservationId.
Go to the API reference
# Step 6: Get Reservation
Retrieve the final reservation details including the PNR. You can retrieve the reservation either by reservation ID or by order ID.
# By reservation ID
curl 'https://booking-api.citizenplane.com/v4/reservation/{reservationId}' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}'
# By order ID
curl 'https://booking-api.citizenplane.com/v4/reservation?orderId={orderId}' \
-H 'Accept: application/json' \
-H 'Authorization: {your_api_key}'
# Example 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",
"departureTerminal": "1",
"arrivalTerminal": "1",
"operatingCarrierCode": "XQ",
"marketingCarrierCode": "XQ",
"technicalStops": []
}
]
},
"passengers": [
{
"number": 1,
"type": "ADULT",
"title": "MR",
"name": "John",
"firstLastName": "Doe",
"gender": "MALE",
"birthDate": "1990-01-15",
"nationalityCountryCode": "FR"
}
],
"selectedBaggage": [...],
"selectedSeats": [],
"customerContact": { ... },
"fare": { ... }
}
The response includes the PNR, passenger details, flight segments, baggage selections, and fare information.
Go to the API reference
# Error Responses
All endpoints return errors in a consistent format with a message and type field. Here are common error scenarios you may encounter:
Expired offer (400):
{
"message": "The requested offer is no longer available or has expired",
"type": "OFFER_NOT_AVAILABLE"
}
Invalid request parameters (400):
{
"message": "The request payload is invalid or missing required fields",
"type": "INVALID_REQUEST_PARAMETERS"
}
Order not found (404):
{
"message": "The specified order could not be found",
"type": "ORDER_NOT_FOUND"
}
Authentication error (401):
{
"message": "Authentication failed",
"type": "AUTHENTICATION_ERROR"
}
Go to the API reference — Errors for the full list of error types.