Public API docs
Back to site
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.

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

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 consumes 1 credit, starts fulfillment, and waits up to PUBLIC_API_FULFILLMENT_WAIT_SECONDS seconds. The default is 120 seconds.

For integration testing, send "test_mode": true. Test mode returns a ready demo reservation with a signed PDF URL, does not contact the provider, and does not 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-07-15",
    "email": "[email protected]",
    "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-07-15",
    "email": "[email protected]",
    "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 At least 10 days from today
email string Customer email
passengers array 1 to 11 passengers
passengers.*.gender string male or female
passengers.*.first_name string Minimum 2 characters
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
phone_number string E.164 format, for example +33612345678
billing_country string 2-letter country code
currency string EUR, USD, GBP, INR, or PHP
passengers.*.born_on date Must be before today

Ready response:

{
  "order_id": "pao_abc123",
  "status": "ready",
  "service": "onward_ticket",
  "test_mode": false,
  "booking_reference": "PNR123",
  "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
}

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.

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": "Provider refused the reservation."
  }
}

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.

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

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

Validation errors use Laravel's standard 422 JSON validation response.

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

Operational Notes

  • Default order wait time: PUBLIC_API_FULFILLMENT_WAIT_SECONDS=120.
  • Default polling interval while the request is open: PUBLIC_API_FULFILLMENT_POLL_MILLISECONDS=500.
  • Default rate limit: PUBLIC_API_RATE_LIMIT_PER_MINUTE=60.
  • 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.