Idempotency & retries
Networks fail and providers can be slow. The API is designed so that you can always retry without booking twice.
Book is idempotent per internal_reference
Every book carries your own order reference in internal_reference. Use one unique value per booking (per leg
for round trips). We record the attempt before calling the provider, so re-sending the same request is safe:
You send book again with the same internal_reference and… | Result |
|---|---|
the same request, and the first booking succeeded (completed, pending, or since cancelled) | 200 with the original response and header Idempotent-Replayed: true. No second booking, even after the offer token has expired. |
the same request, and the first attempt failed | A new attempt (nothing was booked the first time) |
the same request, while the first is still running or its outcome is unknown | 409 duplicate_booking. Nothing is booked. Wait and send it again. |
| a different request (other offer or travellers) | 409 duplicate_booking. Use a new internal_reference for a new booking. |
"The same request" means the same provider, rate_key and travellers.
HTTP/1.1 200 OK
Idempotent-Replayed: true
X-Request-Id: 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
Content-Type: application/json
{"status": "completed", "supplier_booking_ref": "PRV0000001", "...": "original response"}
{
"error": {
"code": "duplicate_booking",
"message": "A booking with this internal_reference is in progress; retry this request later",
"provider": null,
"request_id": "5e4f3a2b-1c0d-4e9f-8a7b-6c5d4e3f2a1b"
}
}
When book times out or answers unknown
A book can end without a definite answer: a client timeout, a network error, a 5xx, or status: unknown with
error.code: book_outcome_unknown (the provider received the request but did not answer in time). A booking
may exist. An unknown response has an empty supplier_reference, so there is nothing to look up yet. We are
notified and reconcile every unknown booking with the provider.
- Mark the order "booking being confirmed". Do not charge or confirm the customer yet.
- Re-send the identical book request (same
internal_reference,provider_contextand travellers) with backoff, for example after 1, 5 and 15 minutes. You get one of:- the booking (
completedorpending,Idempotent-Replayed: true) with itssupplier_reference: continue with booking details as usual; failed: nothing was booked; you can recheck and book again;409 duplicate_booking: the outcome is still being confirmed; keep waiting.
- the booking (
- Still
409after about 30 minutes? Contact [email protected] with theinternal_referenceand theX-Request-Idof the first attempt.
Do not send a new internal_reference, or book another offer for the same order, while an outcome is unknown.
That is the one way to create a duplicate booking.
{
"status": "unknown",
"supplier_booking_id": null,
"supplier_booking_ref": null,
"supplier_reference": {},
"total_price": null,
"currency": null,
"booking_details": {},
"error": {
"code": "book_outcome_unknown",
"message": "The booking outcome could not be confirmed with the provider. Do not book again: retry the same request (same internal_reference) later, or contact support with the request_id",
"provider": "provider_a"
}
}
- Python
- Node.js
import time
def book_with_retries(payload):
"""Re-send the identical request until the outcome is known."""
for delay in (0, 60, 300, 900):
time.sleep(delay)
try:
r = requests.post(f"{BASE_URL}/v1/transfer/book", json=payload,
headers=HEADERS, timeout=150)
except requests.RequestException:
continue # outcome unknown: same request again
if r.status_code == 409: # still being confirmed
continue
if r.status_code >= 500:
continue
body = r.json()
if body.get("status") != "unknown":
return body # completed, pending or failed
raise RuntimeError("Outcome still unknown: contact support")
const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));
async function bookWithRetries(payload) {
for (const delay of [0, 60, 300, 900]) {
await sleep(delay);
let res;
try {
res = await fetch(`${BASE_URL}/v1/transfer/book`, {
method: "POST", headers: HEADERS, body: JSON.stringify(payload),
signal: AbortSignal.timeout(150_000)
});
} catch { continue; } // outcome unknown: same request again
if (res.status === 409 || res.status >= 500) continue;
const body = await res.json();
if (body.status !== "unknown") return body; // completed, pending or failed
}
throw new Error("Outcome still unknown: contact support");
}
Retry guidance per call
| Call | Safe to retry? | Guidance |
|---|---|---|
| Search | Yes | Retry on network errors, 429 (after Retry-After) and 5xx with backoff (1 s, 2 s, 4 s; at most 3 attempts). A per-provider failure in errors[] is not a reason to retry the whole search. |
| Recheck | Yes | Read-only. |
| Booking details | Yes | Read-only. |
| Book | Only the identical request | Never automatically with a new internal_reference. Follow the steps above. |
| Cancel | Yes, after checking | A booking we already cancelled is answered from our records (Idempotent-Replayed: true) without calling the provider again. After a timeout, 5xx or status: unknown, call booking details first and cancel again only if the booking is still confirmed or pending. |
Never retry 401, 403, 404, 413 or 422 without changing the request. A 200 with status: failed is a
definite failure, so it is safe to recheck and book again.
Recommended client timeouts: search 60 s; recheck, booking details and cancel 100 s; book 150 s (a provider can take up to about 135 s to confirm).