Safeonward API Documentation

Safeonward Public B2B API v1

Developer documentation for approved agencies generating onward ticket reservation PDFs, checking wallet balance, reloading credits, and tracking order status.

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

  1. Create a unique idempotency key for the customer request.
  2. Send POST /api/v1/orders with the itinerary and passengers.
  3. If the order is pending, poll the returned status_url after retry_after seconds.
  4. When the order is ready, store airline_reference, itinerary.segments, and pdf_url.
  5. 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:

  1. Safeonward first attempts the requested route normally.
  2. If that reservation succeeds, the response uses "fulfillment_mode": "single" and only 1 credit is consumed.
  3. If the original route conclusively fails and its credit is safely returned, Safeonward tries the permitted hubs in order.
  4. 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.
  5. 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 from booking_reference (our internal PNR); airline_reference always resolves to the verifiable code (falling back to the PNR when the carrier exposes no separate locator). Do not surface booking_reference to 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

  1. Sign in to your Safeonward B2B wallet.
  2. Open API notifications → Order webhook.
  3. Paste a public HTTPS endpoint on port 443.
  4. Enable webhook delivery and select Save webhook.
  5. Copy the generated whsec_... signing secret and store it securely.

Safeonward B2B wallet webhook configuration screen

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
200299 Delivery accepted; no further attempt
400499 Delivery considered failed and retried
500599 Delivery considered failed and retried
Timeout or connection error Delivery retried automatically

Integration checklist

Before enabling production delivery:

  1. Verify signatures against the raw body and reject stale timestamps.
  2. Deduplicate using Safeonward-Delivery or the payload id.
  3. Return a 2xx response before starting slow downstream work.
  4. Handle both order.ready and order.failed.
  5. Keep polling status_url as 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=10 and BOOKING_MAXIMUM_DEPARTURE_MONTHS_AHEAD=9. The maximum is calculated as nine calendar months from the current date, inclusive.
  • POST /orders with test_mode: true never 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.