Quickstart
Make your first sandbox booking in about five minutes: search, recheck, book, read the booking and cancel it. The sandbox only talks to the providers' test systems, so nothing is charged and no real ride is booked. Pick your language once; every sample on the page follows it.
Before you start
- Get a sandbox key. Ask your Pathfndr account manager, or write to success@pathfndr.io.
Sandbox keys start with
tp_test_. The key is shown once: keep it in a secrets manager, never in code. - Set two environment variables,
TP_BASE_URLandTP_API_KEY. All samples read them. - Use a pickup date a few days ahead and obviously fake traveller data (Jane Doe,
jane.doe@example.com).
The Python samples use requests; the Node.js samples use the built-in
fetch (Node 18 or later).
export TP_BASE_URL="https://transfers-sandbox.pathfndr.io"
export TP_API_KEY="tp_test_xxxxxxxx" # your sandbox key
- curl
- Python
- Node.js
curl -s "$TP_BASE_URL/health"
# {"status":"healthy"}
import os
import requests
BASE_URL = os.environ["TP_BASE_URL"]
HEADERS = {"X-API-Key": os.environ["TP_API_KEY"]}
print(requests.get(f"{BASE_URL}/health", timeout=10).json())
const BASE_URL = process.env.TP_BASE_URL;
const HEADERS = {"X-API-Key": process.env.TP_API_KEY, "Content-Type": "application/json"};
const tp = async (path, body) => {
const res = await fetch(`${BASE_URL}${path}`, {method: "POST", headers: HEADERS, body: JSON.stringify(body)});
return res.json();
};
console.log(await (await fetch(`${BASE_URL}/health`)).json());
Step 1: Search for offers
Search an airport arrival from Mumbai airport (BOM) to a hotel. One call queries every provider enabled for
your key in parallel and returns one list, cheapest first, priced in the currency you send.
Keep three values of the offer the customer picks: provider, rate_key and offer_token. The token
is opaque and valid for 24 hours, for your key only.
A provider that fails or has nothing for the route is listed in errors[] while the others still return offers,
so a 200 with a non-empty errors[] is normal.
/v1/search- curl
- Python
- Node.js
curl -s -X POST "$TP_BASE_URL/v1/search" \
-H "X-API-Key: $TP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transfer_type": "airport",
"iata": "BOM",
"source_type": "air",
"place": {"lat": 18.9217, "lng": 72.8332, "name": "The Taj Mahal Palace",
"address": "Apollo Bunder, Colaba, Mumbai 400001"},
"arrival_date": "2026-11-26",
"arrival_time": "10:30",
"adults": 2,
"currency": "INR"
}' > search.json
jq '.offers[0] | {provider, rate_key, offer_token}' search.json > offer.json
payload = {
"transfer_type": "airport",
"iata": "BOM",
"source_type": "air",
"place": {"lat": 18.9217, "lng": 72.8332, "name": "The Taj Mahal Palace",
"address": "Apollo Bunder, Colaba, Mumbai 400001"},
"arrival_date": "2026-11-26",
"arrival_time": "10:30",
"adults": 2,
"currency": "INR",
}
search = requests.post(f"{BASE_URL}/v1/search", json=payload, headers=HEADERS, timeout=60).json()
offer = search["offers"][0]
print(offer["provider"], offer["price"], offer["vehicle"]["category"])
const payload = {
"transfer_type": "airport",
"iata": "BOM",
"source_type": "air",
"place": {"lat": 18.9217, "lng": 72.8332, "name": "The Taj Mahal Palace",
"address": "Apollo Bunder, Colaba, Mumbai 400001"},
"arrival_date": "2026-11-26",
"arrival_time": "10:30",
"adults": 2,
"currency": "INR"
};
const search = await tp("/v1/search", payload);
const offer = search.offers[0];
console.log(offer.provider, offer.price, offer.vehicle?.category);
{
"request_id": "6f1c1e0a-3c55-4a59-9b8e-1f0a2a5d9c11",
"transfer_type": "airport",
"offers": [
{
"provider": "provider_a",
"direction": "arrival",
"rate_key": "d320deaaed65343ed2a5b8fac337a7dd",
"offer_token": "ot1.gAAAAAB...",
"price": {"total": 2508.37, "currency": "INR", "taxes_included": true},
"vehicle": {"category": "Sedan", "max_passengers": 3, "max_bags": 3},
"cancellation_summary": "100% refund if cancelled at least 24h before pickup",
"flight_info_required": true
}
],
"errors": [],
"meta": {"offer_count": 24, "currencies": ["INR"], "sort": "price_asc"}
}
Step 2: Recheck the offer
Recheck immediately before booking. It confirms the offer can still be booked and returns the current price.
available: trueandprice_changed: false: book.price_changed: true: show the newtotal_priceto the customer and book only once they accept.available: false: do not book; search again.
Book with the recheck's offer_token, not the search's: it carries the current price.
/v1/transfer/recheck- curl
- Python
- Node.js
jq '{provider_context: .}' offer.json \
| curl -s -X POST "$TP_BASE_URL/v1/transfer/recheck" \
-H "X-API-Key: $TP_API_KEY" -H "Content-Type: application/json" \
--data-binary @- > recheck.json
jq '{available, price_changed, total_price, currency}' recheck.json
payload = {
"provider_context": {
"provider": offer["provider"],
"rate_key": offer["rate_key"],
"offer_token": offer["offer_token"],
}
}
recheck = requests.post(f"{BASE_URL}/v1/transfer/recheck", json=payload, headers=HEADERS, timeout=100).json()
assert recheck["available"], recheck["error"]
const payload = {
"provider_context": {
"provider": offer.provider,
"rate_key": offer.rate_key,
"offer_token": offer.offer_token
}
};
const recheck = await tp("/v1/transfer/recheck", payload);
if (!recheck.available) throw new Error(recheck.error?.message);
{
"available": true,
"price_changed": false,
"total_price": 2509.96,
"currency": "INR",
"cancellation_policy": null,
"offer_token": "ot1.gAAAAAB...",
"error": null
}
Step 3: Book
Book with the recheck's offer_token, the lead traveller and your own order reference in
internal_reference. Airport offers with flight_info_required: true also need the flight.
Book is idempotent per internal_reference: if the call times out, re-send the identical request and you
get the original booking back, never a second one (Idempotency & retries). Allow up to 150 seconds.
status is completed, pending (poll booking details), failed (nothing was booked) or unknown (do not
rebook; re-send the identical request later). Store the whole supplier_reference object: booking details and
cancel need it unchanged.
/v1/transfer/book- curl
- Python
- Node.js
jq -n --slurpfile o offer.json --slurpfile r recheck.json '{
provider_context: {provider: $o[0].provider, rate_key: $o[0].rate_key,
offer_token: $r[0].offer_token},
travellers: [{title: "Ms", first_name: "Jane", last_name: "Doe", is_lead: true,
email: "jane.doe@example.com", phone: "+442079460000",
phone_country_code: "GB"}],
internal_reference: "TEST-0001",
flight_info: {airline: "AI", flight_number: "AI101",
flight_datetime: "2026-11-26T09:30"}
}' > book-request.json
curl -s -X POST "$TP_BASE_URL/v1/transfer/book" \
-H "X-API-Key: $TP_API_KEY" -H "Content-Type: application/json" \
--max-time 150 --data-binary @book-request.json > book.json
jq '{status, supplier_booking_ref, total_price, currency}' book.json
payload = {
"provider_context": {
"provider": offer["provider"],
"rate_key": offer["rate_key"],
"offer_token": recheck["offer_token"],
},
"travellers": [{
"title": "Ms", "first_name": "Jane", "last_name": "Doe", "is_lead": True,
"email": "jane.doe@example.com", "phone": "+442079460000", "phone_country_code": "GB",
}],
"internal_reference": "TEST-0001",
"flight_info": {"airline": "AI", "flight_number": "AI101", "flight_datetime": "2026-11-26T09:30"},
}
book = requests.post(f"{BASE_URL}/v1/transfer/book", json=payload, headers=HEADERS, timeout=150).json()
print(book["status"], book["supplier_booking_ref"])
const payload = {
"provider_context": {
"provider": offer.provider,
"rate_key": offer.rate_key,
"offer_token": recheck.offer_token
},
"travellers": [{
"title": "Ms", "first_name": "Jane", "last_name": "Doe", "is_lead": true,
"email": "jane.doe@example.com", "phone": "+442079460000", "phone_country_code": "GB"
}],
"internal_reference": "TEST-0001",
"flight_info": {"airline": "AI", "flight_number": "AI101", "flight_datetime": "2026-11-26T09:30"}
};
const book = await tp("/v1/transfer/book", payload);
console.log(book.status, book.supplier_booking_ref);
{
"status": "completed",
"supplier_booking_id": "01aa00bb11cc22dd33ee44ff55667781",
"supplier_booking_ref": "PRV0000001",
"supplier_reference": {
"booking_id": "01aa00bb11cc22dd33ee44ff55667781",
"confirmation_number": "PRV0000001",
"provider": "provider_a",
"booking_status": "completed",
"booking_token": "bt1.gAAAAAB..."
},
"total_price": 2509.96,
"currency": "INR",
"booking_details": {"details_version": "3"},
"error": null
}
Step 4: Read the booking
Booking details returns the booking in one shape for every provider: status, pickup time and places, passengers, vehicle, meeting point, driver (closer to pickup) and the cancellation policy. Build your voucher from it.
/v1/transfer/booking-details- curl
- Python
- Node.js
jq '{provider_context: {provider: .supplier_reference.provider}, supplier_reference}' book.json \
| curl -s -X POST "$TP_BASE_URL/v1/transfer/booking-details" \
-H "X-API-Key: $TP_API_KEY" -H "Content-Type: application/json" \
--data-binary @- \
| jq '.booking_details.booking'
payload = {
"provider_context": {"provider": book["supplier_reference"]["provider"]},
"supplier_reference": book["supplier_reference"],
}
details = requests.post(f"{BASE_URL}/v1/transfer/booking-details", json=payload, headers=HEADERS, timeout=100).json()
print(details["booking_details"]["booking"]["status"])
const payload = {
"provider_context": {"provider": book.supplier_reference.provider},
"supplier_reference": book.supplier_reference
};
const details = await tp("/v1/transfer/booking-details", payload);
console.log(details.booking_details.booking.status);
{
"tp_booking_ref": "TEST-0001",
"provider": "provider_a",
"supplier_confirmation_number": "PRV0000001",
"status": "confirmed",
"status_source": "supplier",
"booked_at": "2026-10-23T06:33:43+00:00"
}
Step 5: Cancel the test booking
Always cancel the sandbox bookings you create. status is cancelled, or cancellation_pending for providers
that confirm cancellations later (poll booking details until it reads cancelled). The refund is reported in the
booking's currency.
That's the whole flow. Next: the Booking flow in depth, the Errors you should handle, and the Go-live checklist.
/v1/transfer/cancel- curl
- Python
- Node.js
jq '{provider_context: {provider: .supplier_reference.provider},
supplier_reference, reason: "Sandbox test"}' book.json \
| curl -s -X POST "$TP_BASE_URL/v1/transfer/cancel" \
-H "X-API-Key: $TP_API_KEY" -H "Content-Type: application/json" \
--data-binary @- \
| jq '{status, cancelled, refund_amount, currency}'
payload = {
"provider_context": {"provider": book["supplier_reference"]["provider"]},
"supplier_reference": book["supplier_reference"],
"reason": "Sandbox test",
}
cancel = requests.post(f"{BASE_URL}/v1/transfer/cancel", json=payload, headers=HEADERS, timeout=100).json()
print(cancel["status"], cancel["refund_amount"], cancel["currency"])
const payload = {
"provider_context": {"provider": book.supplier_reference.provider},
"supplier_reference": book.supplier_reference,
"reason": "Sandbox test"
};
const cancel = await tp("/v1/transfer/cancel", payload);
console.log(cancel.status, cancel.refund_amount, cancel.currency);
{
"status": "cancelled",
"cancelled": true,
"refund_amount": 2509.96,
"currency": "INR",
"refund_source": "supplier",
"refund_estimated": false,
"cancelled_at": "2026-10-23T06:33:47+00:00",
"cancelled_at_source": "tp_cancel",
"error": null
}