Safeonward Public B2B API v1
The public B2B API lets an approved agency create onward ticket reservation PDFs from its prepaid wallet.
How the reservation works
Each order produces a real airline PNR created in the GDS, delivered as a ready-to-render reservation PDF. It is a genuine reservation, not a mock-up.
- Temporary hold. The reservation is a held (un-ticketed) booking. Typical validity is ~72 hours, and the minimum window we target before automated carrier sweeps is 48 hours. Download and use the PDF while the hold is valid.
- Automatic release. When the hold reaches its due date, Safeonward
releases it automatically — you do not need to do anything. A still-pending
order can also be canceled with
POST /orders/{order_id}/cancel(see below). Once a reservation and PDF have been delivered, it cannot be canceled for a wallet credit. - Early carrier drop. If a carrier drops an un-ticketed hold earlier than expected, contact us — we handle these case by case and will either place a free replacement hold on the same route or return the credit to your wallet at no cost.
- The PDF. The reservation document is branded with the operating carrier's logo and name — there is no Safeonward branding on it — so it can be handed to your end-users as-is (see Reservation document & privacy).
Base URL
https://safeonward.com/api/v1
Authentication
All endpoints require an API key linked to an approved B2B agency wallet. Safeonward provides the key after agency access is approved.
Send the key as a Bearer token:
Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Quickstart
- Create a unique idempotency key for the customer request.
- Send
POST /api/v1/orderswith the itinerary and passengers. - If the order is
pending, poll the returnedstatus_urlafterretry_afterseconds. - When the order is
ready, storeairline_reference,itinerary.segments, andpdf_url. - Optionally enable a signed webhook in API notification settings to receive terminal updates automatically.
Use the same idempotency key when retrying a request after an HTTP timeout. A new key represents a new order and may consume another credit.
Endpoints
Wallet Balance
GET /api/v1/balance
Response:
{
"total_credits": 20,
"used_credits": 3,
"remaining_credits": 17
}
Create Order
POST /api/v1/orders
This endpoint normally consumes 1 credit and queues fulfillment. A completed
self-transfer fallback consumes 2 credits because it creates two independent
reservations. It waits up to
PUBLIC_API_FULFILLMENT_WAIT_SECONDS seconds for a terminal result; the default
is 60 seconds. If processing is still underway, the API returns HTTP 202 with
the durable order_id and status_url instead of keeping the request open.
For integration testing, send "test_mode": true. Test mode returns a ready demo reservation using the same PDF template as production, populated with randomized sample flight details and marked as non-live. It does not contact the provider or consume wallet credits.
Request:
curl -X POST https://safeonward.com/api/v1/orders \
-H "Authorization: Bearer $SAFEONWARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from_iata": "CDG",
"to_iata": "BKK",
"departure_date": "2026-10-15",
"direct_only": false,
"allowed_airlines": ["EK", "QR"],
"excluded_connection_countries": ["MY"],
"excluded_connection_airports": ["KUL"],
"email": "client@example.com",
"billing_country": "FR",
"passengers": [
{
"gender": "male",
"first_name": "Jean",
"last_name": "Dupont"
}
]
}'
Test mode request:
curl -X POST https://safeonward.com/api/v1/orders \
-H "Authorization: Bearer $SAFEONWARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"test_mode": true,
"from_iata": "CDG",
"to_iata": "BKK",
"departure_date": "2026-10-15",
"email": "client@example.com",
"passengers": [
{
"gender": "male",
"first_name": "Jean",
"last_name": "Dupont"
}
]
}'
Required fields:
| Field | Type | Notes |
|---|---|---|
from_iata |
string | 3-letter IATA code |
to_iata |
string | 3-letter IATA code, different from from_iata |
departure_date |
date | Between 10 days and 9 months from today |
email |
string | Customer email |
passengers |
array | 1 to 11 passengers |
passengers.*.gender |
string | male or female |
passengers.*.first_name |
string | 2 to 32 characters, including spaces |
passengers.*.last_name |
string | Minimum 2 characters |
Optional fields:
| Field | Type | Notes |
|---|---|---|
test_mode |
boolean | If true, returns a demo reservation and does not consume credits |
return_date |
date | Must be after departure_date and within 9 months from today |
phone_number |
string | E.164 format, for example +33612345678 |
billing_country |
string | 2-letter country code |
currency |
string | EUR, USD, GBP, INR, or PHP |
direct_only |
boolean | If true, only nonstop itineraries are eligible |
allow_self_transfer_fallback |
boolean | Opt-in. For one-way orders, try the requested route first, then two separate reservations through an eligible hub if needed. Requires at least 2 wallet credits. |
self_transfer_hubs |
array | Optional ordered list of 1 or 2 IATA hub airport codes. Defaults to DXB, then SIN. |
allowed_airlines |
array | Up to 5 two-character IATA airline codes. Every validating, marketing, and operating carrier in the selected itinerary must be included. Cannot be combined with excluded_airlines. |
excluded_airlines |
array | Up to 5 two-character IATA airline codes. The selected itinerary will not use these validating, marketing, or operating carriers. |
excluded_connection_countries |
array | Up to 50 two-letter ISO country codes. Intermediate connections in these countries are rejected. The origin and final destination are not treated as connections. |
excluded_connection_airports |
array | Up to 50 specific three-letter IATA airport codes that must not be used as intermediate connections. |
passengers.*.born_on |
date | Must be before today |
Routing controls are applied to every outbound and return journey. Airline
exclusions cover validating, marketing, and operating carriers, including
codeshare segments. Connection exclusions inspect intermediate airports only;
they do not reject the requested origin or final destination. direct_only
cannot be combined with a connecting itinerary. If no eligible itinerary
remains, fulfillment fails and the consumed credit is returned automatically.
Use allowed_airlines when the reservation must use only named carriers. This
is a strict allowlist: for example, ["EK", "QR"] rejects Oman Air and also
rejects a codeshare operated or marketed by any carrier outside that list.
Do not send allowed_airlines and excluded_airlines together. A strict
allowlist can reduce availability, so the order may fail and be refunded when
none of the permitted carriers supports a temporary reservation for the route.
Self-transfer fallback
Enable the fallback explicitly on POST /api/v1/orders with:
"allow_self_transfer_fallback": true
You may also send self_transfer_hubs as an ordered list containing one or two
specific airport codes. Safeonward tries eligible hubs in the supplied order.
If the field is omitted, the current defaults are DXB, then SIN.
The fallback is disabled by default and currently supports one-way orders
only. It cannot be combined with direct_only, and the wallet must contain at
least 2 available credits when the request is accepted.
When to use the self-transfer fallback
Flight availability does not always mean temporary-reservation availability. Many short regional routes are dominated by low-cost carriers (LCCs). These carriers commonly sell seats only through immediate ticketing and may not support the temporary, unticketed GDS reservation required for an onward-ticket PNR. A full-service carrier or a particular fare can also refuse a temporary hold even though the flight appears in ordinary search results.
This is why short routes such as Manila to Bangkok can have many visible flights
but still return route_not_bookable: Safeonward did not find a reservation it
could safely deliver as a verifiable temporary PNR. Repeating the same direct
request generally does not solve that structural limitation.
Enable allow_self_transfer_fallback when all of the following are acceptable:
- the request is one-way;
- a single through-PNR is not required;
- the customer can use two independent reservations through a permitted hub;
- the wallet has 2 credits available for a completed two-reservation journey.
The option is particularly useful for short, LCC-heavy regional routes. You may enable it on the first request: Safeonward still tries the requested route normally before attempting any hub. If the original route succeeds, the result remains a single reservation and consumes only 1 credit.
Do not enable it when the customer requires a direct flight, one protected connection, one PNR for the complete journey, or automatic baggage transfer. The fallback improves the chance of fulfillment but cannot guarantee that both hub reservations will accept temporary holds.
Processing follows this sequence:
- Safeonward first attempts the requested route normally.
- If that reservation succeeds, the response uses
"fulfillment_mode": "single"and only 1 credit is consumed. - If the original route conclusively fails and its credit is safely returned, Safeonward tries the permitted hubs in order.
- Each hub attempt creates two independent, direct reservations: origin to hub, then hub to destination. The second departure is scheduled at least one day after the recorded arrival of the first reservation.
- A completed fallback uses
"fulfillment_mode": "self_transfer", returns two verifiable PNRs, and consumes 2 credits.
The two reservations are a self-transfer, not a protected through-ticket. They may use different record locators, baggage is not transferred automatically, and each reservation must be presented and managed separately.
Routing controls remain active during fallback. The selected airline constraint
applies to both reservations. A hub is skipped when it appears in
excluded_connection_airports, belongs to an
excluded_connection_countries country, or matches the requested origin or
destination.
A partial fallback is never delivered. If the first reservation succeeds but the second fails conclusively, Safeonward releases the first reservation, returns its credit, and may try the next permitted hub. If the state or release of any reservation is ambiguous, processing stops for manual review and no duplicate reservation is attempted.
{
"from_iata": "MNL",
"to_iata": "BKK",
"departure_date": "2026-10-15",
"allow_self_transfer_fallback": true,
"self_transfer_hubs": ["DXB", "SIN"],
"excluded_connection_countries": ["MY"],
"email": "client@example.com",
"passengers": [
{
"gender": "male",
"first_name": "Jean",
"last_name": "Dupont"
}
]
}
These controls reduce unsuitable routes but do not guarantee that an airline will accept a temporary hold. A visible flight may still require immediate ticketing or decline to return a valid PNR.
Ready response:
{
"order_id": "pao_abc123",
"status": "ready",
"service": "onward_ticket",
"test_mode": false,
"booking_reference": "PNR123",
"airline_reference": "DJNRBI",
"itinerary": {
"segments": [
{
"airline_code": "QR",
"airline_name": "Qatar Airways",
"flight_number": "QR704",
"origin_iata": "CDG",
"destination_iata": "DOH",
"departure_at": "2026-10-15T16:20:00",
"arrival_at": "2026-10-15T23:45:00"
}
]
},
"pdf_url": "https://safeonward.com/order/reservation/1/download?expires=...",
"status_url": "https://safeonward.com/api/v1/orders/pao_abc123",
"credits_remaining": 19,
"credit_refunded": false
}
Which reference do I show my customer? Use
airline_reference— it is the airline's own record locator, the code your customer and immigration officers verify on the carrier's public "Manage My Booking" / CheckMyTrip. On some content the airline exposes a locator that differs frombooking_reference(our internal PNR);airline_referencealways resolves to the verifiable code (falling back to the PNR when the carrier exposes no separate locator). Do not surfacebooking_referenceto end-users.
For a ready reservation, itinerary.segments contains the exact selected
flight legs. flight_number includes the carrier code. departure_at and
arrival_at preserve the provider's local airport date-time and may not include
a UTC offset. The optional itinerary property is omitted from pending, failed,
or older reservations where no persisted segment data is available.
Self-transfer ready response:
{
"order_id": "pao_abc123",
"status": "ready",
"fulfillment_mode": "self_transfer",
"self_transfer_hub_iata": "DXB",
"booking_references": ["ABC123", "DEF456"],
"reservations": [
{
"leg": 1,
"from_iata": "MNL",
"to_iata": "DXB",
"booking_reference": "ABC123",
"airline_reference": "ABC123",
"itinerary": { "segments": [] },
"pdf_url": "https://safeonward.com/order/reservation/101/download?expires=..."
},
{
"leg": 2,
"from_iata": "DXB",
"to_iata": "BKK",
"booking_reference": "DEF456",
"airline_reference": "DEF456",
"itinerary": { "segments": [] },
"pdf_url": "https://safeonward.com/order/reservation/102/download?expires=..."
}
],
"credits_remaining": 18,
"credit_refunded": false
}
Each reservations item is a separate verifiable PNR with its own PDF. Treat
the itinerary as a self-transfer: it is not a protected connection and baggage
is not transferred automatically. For a completed self-transfer, read the
documents from reservations[].pdf_url; the aggregate pdf_url is omitted.
Safeonward also sends one final confirmation email containing every completed
reservation PDF. Delivery follows the agency's Ticket delivery setting: to
the customer email by default, or to the agency contact when agency delivery is
enabled. Intermediate failed attempts do not generate partner-facing emails.
Terminal webhooks include the same fulfillment_mode, self_transfer_hub_iata,
booking_references, and reservations fields as the polling response.
Pending response:
{
"order_id": "pao_abc123",
"status": "pending",
"service": "onward_ticket",
"test_mode": false,
"status_url": "https://safeonward.com/api/v1/orders/pao_abc123",
"retry_after": 5,
"credits_remaining": 19,
"credit_refunded": false
}
If the response is pending, poll status_url after retry_after seconds. Keep
the same order_id and do not submit the same passenger and itinerary again
with a new idempotency key. Provider-account contention is handled internally:
the order remains pending and is retried automatically without consuming an
additional credit. You may also configure an optional signed webhook in the
B2B wallet; polling remains supported and existing integrations do not need to
change.
If your HTTP client or gateway times out after the request was accepted, repeat
the request with the same Idempotency-Key. Safeonward returns the existing
order instead of creating or charging a second one.
Failed response:
{
"order_id": "pao_abc123",
"status": "failed",
"service": "onward_ticket",
"test_mode": false,
"status_url": "https://safeonward.com/api/v1/orders/pao_abc123",
"credits_remaining": 20,
"credit_refunded": true,
"error": {
"code": "provider_draft_failed",
"message": "We could not complete this reservation. Check credit_refunded and create a new order only if the credit was returned."
}
}
If fulfillment fails before a valid reservation PDF is produced, the credit is automatically refunded.
Get Order Status
GET /api/v1/orders/{order_id}
Example:
curl -H "Authorization: Bearer $SAFEONWARD_API_KEY" \
https://safeonward.com/api/v1/orders/pao_abc123
The response uses the same shape as POST /orders.
Webhooks
Webhooks are optional, signed HTTP requests that notify your server as soon as
an API order becomes ready or fails. They remove the need for continuous
polling, while GET /orders/{order_id} remains fully supported as a fallback.
Configure webhooks in the B2B wallet
Open API notification settings
- Sign in to your Safeonward B2B wallet.
- Open API notifications → Order webhook.
- Paste a public HTTPS endpoint on port
443. - Enable webhook delivery and select Save webhook.
- Copy the generated
whsec_...signing secret and store it securely.

The endpoint must use HTTPS, must not contain embedded credentials, and must resolve only to public IP addresses. Redirects are not followed. You can disable delivery without changing the behavior of order creation or polling.
Keep the signing secret server-side. Never place it in browser JavaScript, mobile applications, source control, support screenshots, or customer-facing logs. Rotating it in the wallet immediately invalidates the previous secret.
Event types
Safeonward sends one event when an API order reaches each terminal state:
| Event | When it is sent | Recommended action |
| --- | --- |
| order.ready | The reservation and PDF are ready | Store the references and itinerary, then download the PDF |
| order.failed | Fulfillment reached a terminal failure | Inspect credit_refunded; create a new order only when the credit was returned |
Each terminal event is created once per order. Network retries reuse the same
event id and the same Safeonward-Delivery header, so your handler must be
idempotent.
Event envelope
Example payload:
{
"id": "31a5451a-4d4c-47dd-8c11-f710c4d29331",
"event": "order.ready",
"created_at": "2026-08-13T08:30:00+00:00",
"data": {
"order_id": "pao_abc123",
"status": "ready",
"service": "onward_ticket",
"test_mode": false,
"booking_reference": "PNR123",
"airline_reference": "DJNRBI",
"itinerary": {
"segments": [
{
"airline_code": "QR",
"airline_name": "Qatar Airways",
"flight_number": "QR704",
"origin_iata": "CDG",
"destination_iata": "DOH",
"departure_at": "2026-10-15T16:20:00",
"arrival_at": "2026-10-15T23:45:00"
}
]
},
"pdf_url": "https://safeonward.com/order/reservation/1/download?expires=...",
"status_url": "https://safeonward.com/api/v1/orders/pao_abc123",
"credits_remaining": 19,
"credit_refunded": false
}
}
The data object uses the same response contract as GET /orders/{order_id}.
For order.ready, it therefore includes the same optional
itinerary.segments array with flight numbers and departure times.
Request headers
Every request contains these headers:
Safeonward-Event: order.ready
Safeonward-Delivery: 31a5451a-4d4c-47dd-8c11-f710c4d29331
Safeonward-Signature: t=1786609800,v1=hex_hmac_sha256
| Header | Description |
|---|---|
Safeonward-Event |
Event type, such as order.ready |
Safeonward-Delivery |
Stable delivery identifier used for deduplication |
Safeonward-Signature |
Timestamp and HMAC-SHA256 signature |
Content-Type |
Always application/json |
Verify the signature
Calculate HMAC-SHA256 over timestamp + "." + raw_request_body using the
signing secret shown in the B2B wallet. Compare the hexadecimal result to v1
with a constant-time comparison.
Important: verify the exact raw request body before decoding JSON. Re-encoding the parsed payload can change whitespace or key formatting and invalidate the signature. Reject timestamps older than five minutes to reduce replay risk.
PHP example:
$signatureHeader = $_SERVER['HTTP_SAFEONWARD_SIGNATURE'] ?? '';
$rawBody = file_get_contents('php://input');
preg_match('/t=(\d+),v1=([a-f0-9]{64})/', $signatureHeader, $matches);
$timestamp = $matches[1] ?? '';
$receivedSignature = $matches[2] ?? '';
$expectedSignature = hash_hmac(
'sha256',
$timestamp.'.'.$rawBody,
getenv('SAFEONWARD_WEBHOOK_SECRET'),
);
if (abs(time() - (int) $timestamp) > 300 || ! hash_equals($expectedSignature, $receivedSignature)) {
http_response_code(401);
exit;
}
http_response_code(200);
Node.js example:
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhooks/safeonward', express.raw({ type: 'application/json' }), (request, response) => {
const signatureHeader = request.header('Safeonward-Signature') ?? '';
const deliveryId = request.header('Safeonward-Delivery');
const match = signatureHeader.match(/^t=(\d+),v1=([a-f0-9]{64})$/);
if (!match) {
return response.sendStatus(401);
}
const [, timestamp, receivedSignature] = match;
const signedPayload = `${timestamp}.${request.body.toString('utf8')}`;
const expectedSignature = crypto
.createHmac('sha256', process.env.SAFEONWARD_WEBHOOK_SECRET)
.update(signedPayload)
.digest('hex');
const timestampIsFresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
const signatureIsValid = crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex'),
);
if (!timestampIsFresh || !signatureIsValid) {
return response.sendStatus(401);
}
const event = JSON.parse(request.body.toString('utf8'));
// Store deliveryId before processing so retries remain idempotent.
// Queue your business logic, then acknowledge immediately.
return response.sendStatus(200);
});
Delivery and retries
Return any HTTP 2xx response within 10 seconds. Perform slow work in your own
queue and acknowledge the webhook immediately.
Safeonward retries delivery up to six times with increasing delays when the
endpoint times out, cannot be reached, or returns a non-2xx response. A retry
keeps the same signed payload and delivery ID.
| Response | Safeonward behavior |
|---|---|
200–299 |
Delivery accepted; no further attempt |
400–499 |
Delivery considered failed and retried |
500–599 |
Delivery considered failed and retried |
| Timeout or connection error | Delivery retried automatically |
Integration checklist
Before enabling production delivery:
- Verify signatures against the raw body and reject stale timestamps.
- Deduplicate using
Safeonward-Deliveryor the payloadid. - Return a
2xxresponse before starting slow downstream work. - Handle both
order.readyandorder.failed. - Keep polling
status_urlas a recovery path when needed.
Troubleshooting webhooks
No event received. Confirm the webhook is enabled in API notification
settings, the endpoint is
publicly reachable over HTTPS on port 443, and the order reached ready or
failed.
Signature mismatch. Use the raw request bytes, not parsed and re-encoded JSON. Confirm that the secret matches the one currently shown in the wallet and that no proxy modifies the body.
Repeated events. Retries are expected when Safeonward does not receive a
timely 2xx. Store the delivery ID and return success for an event already
processed.
Slow handler. Move PDF downloads, emails, and downstream API calls into a
background job. Return 200 as soon as the event has been authenticated and
durably queued.
Cancel Order
POST /api/v1/orders/{order_id}/cancel
Use this only while an order is still pending. Safeonward releases the provider hold when one is already open and returns the credit to your wallet. A failed order has already been refunded automatically. Once an order is ready and its real PNR/PDF has been delivered, cancellation is rejected and the credit remains consumed.
Example:
curl -X POST https://safeonward.com/api/v1/orders/pao_abc123/cancel \
-H "Authorization: Bearer $SAFEONWARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Customer no longer needs the reservation."}'
Response:
{
"order_id": "pao_abc123",
"status": "canceled",
"service": "onward_ticket",
"test_mode": false,
"status_url": "https://safeonward.com/api/v1/orders/pao_abc123",
"credits_remaining": 20,
"credit_refunded": true,
"canceled_at": "2026-06-26T12:00:00+00:00",
"cancellation": {
"code": "partner_requested",
"message": "Customer no longer needs the reservation."
}
}
The endpoint is idempotent. Calling it again for the same order returns the canceled order and does not refund another credit.
Attempting to cancel a delivered reservation returns HTTP 409:
{
"error": {
"code": "booking_already_delivered",
"message": "This reservation has already been delivered and cannot be canceled for a wallet credit."
}
}
Download PDF
GET /api/v1/orders/{order_id}/pdf
If the PDF is ready, this endpoint redirects to a temporary signed PDF download URL.
If the PDF is not ready:
{
"message": "The reservation PDF is not ready.",
"status": "pending",
"retry_after": 5
}
Reload Wallet
POST /api/v1/wallet/reloads
Request by ticket count:
curl -X POST https://safeonward.com/api/v1/wallet/reloads \
-H "Authorization: Bearer $SAFEONWARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tickets": 10}'
Request by amount:
{
"amount": 30
}
Response:
{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_...",
"tickets": 10
}
The agency must complete Stripe Checkout. Credits are added through the existing Stripe webhook or wallet sync flow.
Error Responses
Error messages are intentionally provider-neutral. Use the stable error.code
for application logic and send order_id to Safeonward support when manual
review is required. Internal supplier names, booking-system URLs and diagnostic
responses are never included in the partner API response.
Missing API key:
{
"message": "Missing API key."
}
Invalid or inactive API key:
{
"message": "Invalid API key."
}
Insufficient credits:
{
"error": {
"code": "insufficient_credits",
"message": "Your prepaid balance is empty. Click here to reload."
},
"reload_url": "https://safeonward.com/api/v1/wallet/reloads"
}
Active duplicate booking:
{
"error": {
"code": "duplicate_active_booking",
"message": "An active booking already exists for this passenger on the same route and departure date."
}
}
This response means an earlier order for the same passenger, route and date is
still active. Retrieve that order with its existing status_url; do not create
a second order with a new idempotency key. A transient provider-account lock is
not returned as a terminal error: the accepted order stays pending while the
platform retries it automatically.
Validation errors use Laravel's standard 422 JSON validation response.
Unknown airport codes, including metropolitan codes such as PAR, are rejected
before an order is created or a wallet credit is consumed. Send the specific
airport code instead, for example CDG or ORY:
{
"error": {
"code": "invalid_airport_code",
"message": "One or more airport codes are not supported. Use a specific IATA airport code from the Safeonward airport list."
},
"invalid_airport_codes": ["PAR"]
}
Departure dates earlier than the minimum 10-day booking window return a specific 422 error:
{
"error": {
"code": "departure_date_too_soon",
"message": "Departure date must be at least 10 days from today."
},
"minimum_departure_date": "2026-07-03"
}
Departure dates beyond the maximum 9-month booking window return:
{
"error": {
"code": "departure_date_too_far",
"message": "Departure date must be within 9 months from today."
},
"maximum_departure_date": "YYYY-MM-DD"
}
The returned maximum_departure_date is calculated when the request is
validated. It is inclusive: a departure or return on that exact date is
accepted.
Reservation document & privacy
White-label. The API returns both a structured JSON order object and a
ready-to-render PDF itinerary (GET /orders/{order_id}/pdf). The PDF is styled
with the operating carrier's logo and name and carries no Safeonward
branding, so you can deliver it to your end-users without re-processing. If
you prefer, you can consume the JSON payload and render your own document.
Data privacy. Passenger data submitted through the API is used solely to create and manage your reservations. It is treated as strictly confidential and is never shared with, marketed to, or used by Safeonward's retail side.
Operational Notes
- Default order wait time:
PUBLIC_API_FULFILLMENT_WAIT_SECONDS=60. - Default polling interval while the request is open:
PUBLIC_API_FULFILLMENT_POLL_MILLISECONDS=500. - Booking-account contention is retried automatically with exponential
backoff. It does not produce
provider_draft_failed, refund the credit, or require the partner to create another order. - Default rate limit:
PUBLIC_API_RATE_LIMIT_PER_MINUTE=60. - Booking date limits:
BOOKING_MINIMUM_DEPARTURE_DAYS_AHEAD=10andBOOKING_MAXIMUM_DEPARTURE_MONTHS_AHEAD=9. The maximum is calculated as nine calendar months from the current date, inclusive. POST /orderswithtest_mode: truenever consumes a credit and never contacts the booking provider.- Partner order status and PDF routes are scoped to the authenticated agency.
- PDF links are temporary signed URLs and should not be stored permanently by partners unless their integration downloads the file.