Introduction
The Distribution API is the partner-facing channel for pool-based flight distribution. It lets approved partners search flights, re-price offers, hold seats, create orders, and process payments on behalf of their customers.
The API is a two-phase flow:
-
Mint a session — the partner signs a request with their pre-shared secret (HMAC-SHA256) and exchanges it for a short-lived, scope-limited Bearer token (JWT).
-
Call endpoints — every other endpoint is called with
Authorization: Bearer <token>. The token carries the partner identity, currency, locale, and the granted scopes.
Important: Each airline has their own specific domain. The API endpoints are hosted on airline-specific domains.
Base URL Pattern: https://{your-airline-domain}/api
Important: All API endpoints are prefixed with /api due to the servlet context path configuration.
To get your partner credentials and airline’s API domain, contact your technical integration manager. Onboarding and operational procedures (secret generation, rotation, kill switches) are covered separately in the partner onboarding guide.
Authentication
Phase 1 — Minting a session
POST /distribution/v1/sessions is the only unauthenticated endpoint. It is protected by an HMAC signature instead of a Bearer token. The partner sends the following headers:
| Header | Description |
|---|---|
|
The partner’s UUID, as issued during onboarding. |
|
Unix epoch seconds at the time of the request. Requests outside the allowed clock-skew window are rejected. |
|
A unique value per request (UUIDv4 recommended). Replayed nonces are rejected. |
|
Hex-encoded HMAC-SHA256 of the canonical message (see below), keyed with the partner’s pre-shared secret. |
The canonical message that is signed is the newline-joined tuple:
<partner_id>\n<timestamp>\n<nonce>\n<sha256_hex(request_body_bytes)>
Example using openssl:
BODY='{"locale":"en_US","currency":"USD","scopes":["search:read","fare:read"]}'
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr 'A-Z' 'a-z')
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
SIGNATURE=$(printf '%s\n%s\n%s\n%s' "$PARTNER_ID" "$TIMESTAMP" "$NONCE" "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$PARTNER_SECRET" -hex | awk '{print $2}')
Phase 2 — Calling endpoints
All other endpoints require the minted token:
Authorization: Bearer <access_token>
Scopes
The token is granted a subset of scopes — the intersection of what the partner requests and what the partner is entitled to. Each endpoint requires a specific scope; a token missing the required scope receives 403 SCOPE_INSUFFICIENT.
| Scope | Grants access to |
|---|---|
|
Flight search |
|
Offer re-pricing |
|
Seat holds and order creation |
|
Reading order details |
|
Creating and confirming payment intents |
|
Listing payment methods and reading payment intent status |
Airline scoping
Requests may include an optional X-Airline-ID header. When present, it must match the partner’s configured airline; a mismatch is rejected with 403 AUTHZ_FAILED. When absent, the airline is resolved from the partner record.
Error Format
All errors share a consistent JSON envelope:
{
"code": "SCOPE_INSUFFICIENT",
"message": "session token does not carry the scope required by this endpoint"
}
Common codes:
| HTTP | Code | Meaning |
|---|---|---|
400 |
|
Request body or parameters failed validation. |
400 |
|
Passenger composition violates inventory rules. |
400 |
|
Requested payment |
400 |
|
Requested payment method is not available. |
401 |
|
Bearer token absent. |
401 |
|
Token no longer valid. |
403 |
|
Token lacks the scope required by the endpoint. |
403 |
|
|
403 |
|
Return URL is not HTTPS or its host is not in the partner’s allowed origins. |
404 |
|
Order does not exist or is not owned by the partner. |
404 |
|
Hold does not exist, expired, or is not owned by the partner. |
404 |
|
No payment intent for the order/intent id. |
409 |
|
Capacity is unavailable for the requested composition. |
410 |
|
Offer or hold is no longer available. |
429 |
|
Per-partner rate limit exceeded. |
Rate Limiting
Each partner has a configured requests-per-second limit. Exceeding it returns 429 RATE_LIMIT_EXCEEDED.
Endpoints Summary
| Method | Endpoint | Scope | Description |
|---|---|---|---|
POST |
|
— (HMAC) |
Mint a session token |
GET |
|
any valid token |
Bearer-authenticated smoke test |
POST |
|
|
Pool-based flight search (one-way) |
POST |
|
|
Batch re-price pool offers |
POST |
|
|
Hold seats on a pool offer |
POST |
|
|
Create an order from a hold |
GET |
|
|
Get order details |
GET |
|
|
List available payment methods |
POST |
|
|
Create a payment intent |
GET |
|
|
Get payment intent status |
POST |
|
|
Confirm a payment intent |
API Endpoints
Each request block below lists the headers that endpoint requires. Every endpoint except Mint Session requires the Authorization: Bearer <access_token> header; requests that carry a JSON body also require Content-Type: application/json.
Mint Session
Exchanges an HMAC-signed request for a short-lived Bearer token. See Authentication for the signing scheme.
HTTP Request
POST /distribution/v1/sessions Content-Type: application/json X-Partner-Id: <partner_uuid> X-Timestamp: <unix_epoch_seconds> X-Nonce: <unique_per_request> X-Signature: <hmac_sha256_hex>
Request Body
{
"locale": "en_US",
"currency": "USD",
"cart_id": null,
"scopes": ["search:read", "fare:read", "order:write", "order:read", "payment:write", "payment:read"]
}
Response
{
"session_id": "5f8d0a2c-1b3e-4c6a-9f1d-2e3b4c5d6e7f",
"access_token": "<jwt>",
"expires_at": "2026-07-15T12:30:00Z",
"token_type": "Bearer",
"scopes": ["search:read", "fare:read", "order:write", "order:read", "payment:write", "payment:read"]
}
The returned scopes reflect what was actually granted (the intersection of requested and entitled scopes), so the partner can detect any gap.
Ping
A smoke-test endpoint that echoes the caller’s identity from the Bearer token. Useful to confirm the mint → bearer pipeline works.
HTTP Request
GET /distribution/v1/ping Authorization: Bearer <access_token>
Response
{
"partner_id": "f8cc4f24-8f68-4ade-9033-ec1402fe1836",
"session_id": "5f8d0a2c-1b3e-4c6a-9f1d-2e3b4c5d6e7f",
"server_time": "2026-07-15T12:00:00Z"
}
Search Flights
Pool-based flight search. Requires scope search:read.
|
Note
|
This release supports direct, one-way itineraries only. Supplying return_date is rejected with 400 INVALID_REQUEST (round-trip and connections are a later milestone).
|
HTTP Request
POST /distribution/v1/search Authorization: Bearer <access_token> Content-Type: application/json
Request Body
{
"origin": "ALA",
"destination": "OSS",
"outbound_date": "2026-07-15",
"pax": { "adt": 1, "chd": 0, "inf": 0 },
"currency": "USD"
}
Response
The call always returns 200 OK; offers may be empty when no pool offers match.
{
"offers": [
{
"offer_id": "22222222-0000-0000-0000-000000000001",
"fare_frame_id": "ffffffff-0000-0000-0000-000000000001",
"flight_legs": [
{
"flight_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"flight_number": "FA101",
"origin": "ALA",
"destination": "OSS",
"departure_date": "2026-07-15",
"arrival_date": "2026-07-15",
"departure_time": "08:00",
"arrival_time": "09:00",
"duration_minutes": 60
}
],
"pricing": {
"base_price": 50.00,
"total_price": 50.00,
"currency": "USD",
"adt_count": 1,
"chd_count": 0,
"inf_count": 0
}
}
],
"currency": "USD"
}
Re-price Offers
Re-checks a batch of pool offers by id. Requires scope fare:read. The whole call returns 200 OK; the partner inspects each entry’s status. Up to 100 ids per call.
HTTP Request
POST /distribution/v1/offer Authorization: Bearer <access_token> Content-Type: application/json
Request Body
{ "offer_ids": ["22222222-0000-0000-0000-000000000001"] }
Response
{
"results": [
{
"offer_id": "22222222-0000-0000-0000-000000000001",
"status": "VALID",
"current_price": 50.00,
"currency": null,
"fare_frame_ids": ["ffffffff-0000-0000-0000-000000000001"]
}
]
}
Per-offer status is one of VALID, NOT_FOUND, or EXPIRED.
Hold Seats
Reserves seats on a pool offer for a configurable time-to-live, giving the partner time to collect passenger details before creating an order. Requires scope order:write.
HTTP Request
POST /distribution/v1/orders/holds Authorization: Bearer <access_token> Content-Type: application/json
Request Body
{
"pool_offer_id": "22222222-0000-0000-0000-000000000001",
"fare_frame_id": "ffffffff-0000-0000-0000-000000000001",
"pax": { "adt": 1, "chd": 0, "inf": 0 }
}
Response
201 Created:
{
"hold_id": "66666666-6666-6666-6666-666666666666",
"expires_at": "2026-07-15T12:15:00Z"
}
Failure cases: 409 OFFER_SOLD_OUT, 409 SUBQUOTA_EXCEEDED, 410 OFFER_NOT_AVAILABLE, 400 INVALID_PAX_COMPOSITION.
Create Order
Creates an order from a previously obtained hold. Requires scope order:write.
This endpoint is idempotent — callers MUST supply an Idempotency-Key header (UUIDv4 recommended). Repeating the call with the same key returns the original order instead of creating a duplicate.
HTTP Request
POST /distribution/v1/orders Authorization: Bearer <access_token> Content-Type: application/json Idempotency-Key: <uuid>
Request Body
{
"hold_id": "66666666-6666-6666-6666-666666666666",
"passengers": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1990-01-01",
"gender": "MALE",
"citizenship": "US",
"document_type": "PASSPORT",
"document_number": "123456789",
"document_expiry_date": "2030-01-01"
}
],
"contact": {
"name": "John Doe",
"phone": "+1234567890",
"email": "john@example.com"
}
}
Response
201 Created:
{
"order_id": "77777777-7777-7777-7777-777777777777",
"status": "CONFIRMED",
"currency": "USD",
"total_amount": 50.00,
"payment_due_date": "2026-07-15T13:00:00Z",
"payment_intent_id": "99999999-3333-0000-0000-000000000001"
}
Failure cases: 404 HOLD_NOT_FOUND, 410 HOLD_EXPIRED.
Get Order
Returns order details for a partner-owned order. Requires scope order:read. Partners can only read their own orders; an unknown or foreign order returns 404 ORDER_NOT_FOUND.
HTTP Request
GET /distribution/v1/orders/{orderId}
Authorization: Bearer <access_token>
Response
{
"order_id": "77777777-7777-7777-7777-777777777777",
"status": "CONFIRMED",
"currency": "USD",
"total_amount": 50.00,
"contact": { "name": "John Doe", "phone": "+1234567890", "email": "john@example.com" },
"items": [
{
"item_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
"type": "FLIGHT",
"status": "CONFIRMED",
"description": "ALA-OSS 2026-07-15",
"amount": 50.00,
"hold_expires_at": null
}
],
"invoice": {
"invoice_id": "1a2b3c4d-5e6f-7081-9203-a4b5c6d7e8f9",
"status": "ISSUED",
"amount": 50.00,
"currency": "USD",
"payment_due_date": "2026-07-15T13:00:00Z",
"payment_intent_id": "99999999-3333-0000-0000-000000000001",
"issued_at": "2026-07-15T12:00:00Z",
"paid_at": null
}
}
List Payment Methods
Returns the payment methods available for the order. Requires scope payment:read.
HTTP Request
GET /distribution/v1/orders/{orderId}/payments/methods
Authorization: Bearer <access_token>
Response
{
"methods": [
{ "id": "payler", "name": "Payler", "flow_kinds": ["PSP_NATIVE"] },
{ "id": "freedom", "name": "Freedom", "flow_kinds": ["PSP_NATIVE"] }
]
}
Create Payment Intent
Creates a provider-specific payment session on the order’s reserved payment intent. Requires scope payment:write.
The endpoint is idempotent per order: a single payment intent is reserved at order creation, and provider references are get-or-create on that intent, so repeating the call for the same order returns the same reference. No client idempotency key is required.
success_url and cancel_url must use HTTPS and their host must be in the partner’s allowed origins, otherwise 403 RETURN_URL_NOT_ALLOWED is returned. This release supports flow_kind PSP_NATIVE only.
HTTP Request
POST /distribution/v1/orders/{orderId}/payments/intents
Authorization: Bearer <access_token>
Content-Type: application/json
Request Body
{
"method": "payler",
"flow_kind": "PSP_NATIVE",
"success_url": "https://partner.example.com/ok",
"cancel_url": "https://partner.example.com/cancel",
"return_locale": "en_US"
}
Response
201 Created:
{
"intent_id": "99999999-3333-0000-0000-000000000001",
"status": "CREATED",
"psp_payload": {
"url": "https://psp.example.com/pay/abc123",
"payment_due_date": "2026-07-15T13:00:00Z"
}
}
The shape of psp_payload depends on the provider.
Get Payment Intent Status
Polls the current status of a payment intent. Requires scope payment:read.
HTTP Request
GET /distribution/v1/orders/{orderId}/payments/intents/{intentId}
Authorization: Bearer <access_token>
Response
{
"intent_id": "99999999-3333-0000-0000-000000000001",
"status": "PENDING",
"has_expired": false,
"payment_method": "payler",
"last_transition_at": "2026-07-15T12:05:00Z"
}
Confirm Payment Intent
Server-side confirmation hook, typically called after the buyer completes the provider flow (e.g. post-3DS). Requires scope payment:write. Returns 204 No Content on success.
HTTP Request
POST /distribution/v1/orders/{orderId}/payments/intents/{intentId}/confirm
Authorization: Bearer <access_token>
Machine-readable Contract
The authoritative, always-current request/response schema is published as an OpenAPI document and served by each deployment at /api/swagger-ui/index.html. This page is a narrative companion to that contract.