Integrating with the Lakpura Transfers API
You send us point-to-point transfer bookings in Sri Lanka; we quote them and fulfil them with our fleet. This page is everything you need in the order you need it. The full reference has every field.
1. Three things that surprise people
/sandbox/ path and no separate hostname — your key selects the
environment. A test key can only ever reach sandbox. Going live is a credential swap, not
a code change. Every response tells you which environment answered, via the
X-Lakpura-Env header and an env field.
200 with an empty offers array
and a declined object. Please don’t log it as a failure, count it toward an error
budget, or trip a circuit breaker on it — it’s a normal answer, and the reason is usually
actionable (a later pickup time, or coordinates instead of a place name).
offer_id. We never re-price at
booking time. After 30 minutes the offer is 410 offer_expired — ask for a fresh
quote rather than retrying the booking.
2. Your keys
We issue your keys and send them to you. They look like this — the test /
live word is just so you can read one at a glance:
lakpura_sk_test_a3f19c04b7e2d581_9f3c… ← sandbox
lakpura_sk_live_60650be97f12ca25_27c1… ← production
Send it on every request:
Authorization: Bearer lakpura_sk_test_a3f19c04b7e2d581_9f3c…
X-Api-Key: <token> also works if a bearer header is awkward in your stack.
We store only a hash, so we cannot read your key back to you — if you lose it, we issue a new one. Rotating causes no downtime: ask us for a replacement, deploy it, then tell us to revoke the old one. Both work in the meantime.
Every authentication failure returns the same 401 body, whatever the cause. That
is deliberate; if you are stuck, check /ping and talk
to us rather than guessing.
3. Try it in four calls
lakpura_sk_test_… keys: an accidental
Execute on POST /bookings with a production key would dispatch an
actual vehicle.
The same four calls with curl, if you prefer:
Check the key
curl https://api.lakpura.com/transfers/v1/ping \
-H "Authorization: Bearer $LK_KEY"
{"ok":true,"env":"sandbox","partner":"Your Company","time":"2026-07-17T10:33:15+00:00"}
Quote a journey
Place names can be free text — "Galle", or a full hotel address. Coordinates help
when a name is ambiguous. Airports work by name or iata, and an airport at either
end is what makes a journey an arrival or a departure.
curl -X POST https://api.lakpura.com/transfers/v1/quotes \
-H "Authorization: Bearer $LK_KEY" \
-H "Content-Type: application/json" \
-d '{
"pickup": { "name": "Colombo (CMB) Airport", "iata": "CMB" },
"dropoff": { "name": "Galle" },
"pickup_datetime": "2026-08-02T14:30:00+05:30",
"passengers": { "adults": 2, "luggage": 2 }
}'
{
"env": "sandbox",
"quote_id": "561055ebbfef36165bd76359adcc634f",
"expires_at": "2026-07-17T11:03:47Z",
"currency": "USD",
"offers": [
{ "offer_id": "a7c4e1f09b3d5628e4f1a0c73d8b2e95",
"vehicle_code": "STANDARD_CAR",
"vehicle": "Standard Car or similar",
"price": 84.71, "currency": "USD",
"max_passengers": 3, "max_luggage": 3 }
]
}
Book an offer
curl -X POST https://api.lakpura.com/transfers/v1/bookings \
-H "Authorization: Bearer $LK_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-d '{
"offer_id": "a7c4e1f09b3d5628e4f1a0c73d8b2e95",
"quote_id": "561055ebbfef36165bd76359adcc634f",
"partner_reference": "ACME-2026-99812",
"passenger": { "first_name": "Ada", "last_name": "Lovelace",
"email": "[email protected]", "phone": "+447700900123" },
"passengers": { "adults": 2 },
"flight_number": "UL504"
}'
HTTP/1.1 201 Created
{
"env": "sandbox",
"booking_ref": "LK7ANBH4QA",
"partner_reference": "ACME-2026-99812",
"status": "confirmed",
"price": 84.71, "currency": "USD",
"meeting_instructions": "Meet & greet at the arrivals hall — our representative will hold a Lakpura name board.",
"emergency_phone": "+94112354550"
}
booking_ref is ours — use it to read or cancel. Keep your own partner_reference too.
Read it back, and cancel
curl https://api.lakpura.com/transfers/v1/bookings/LK7ANBH4QA \
-H "Authorization: Bearer $LK_KEY"
curl -X POST https://api.lakpura.com/transfers/v1/bookings/LK7ANBH4QA/cancel \
-H "Authorization: Bearer $LK_KEY"
Cancelling twice is not an error. Retrying is safe.
4. Declines: what each reason means
All of these arrive as 200 with "offers": [].
| reason | What it means | What to do |
|---|---|---|
short_notice | Pickup is inside our 24-hour minimum lead time. | Quote a later pickup. Expected, not a fault. |
no_vehicle | Nothing in our fleet fits that party on that route. | Try fewer passengers/bags, or omit vehicle_codes. |
route_unknown | We could not price that journey. | Send lat/lon — free-text-only names are the usual cause. |
below_min_rate | The journey prices below our minimum fare. | Nothing on your side. Very short hops. |
partner_paused | Quoting is paused on your account. | Talk to us — it is deliberate on our end. |
5. Retrying safely
You cannot accidentally book two cars. Three separate things prevent it:
| Mechanism | Behaviour |
|---|---|
Idempotency-Key header | Same key + same body → the original response, as 200 (not 201 — so you can tell a replay from a fresh booking). Same key + different body → 409 idempotency_conflict, which means a bug your side. |
partner_reference | Unique per account. Re-posting the same one returns the existing booking as 200. |
offer_id | An offer is consumed, not re-executed. Booking one twice returns 409, never a second car. |
503 is the only status worth retrying blindly (with backoff). 4xx
means something needs changing first.
6. Errors
Every error has the same shape. Branch on error.code, never on the message — the prose may change, the code will not.
{ "error": { "code": "offer_expired",
"message": "That offer has expired. Request a new quote.",
"details": { "offer_id": "a7c4e1f0…", "expired_at": "2026-07-17T11:03:47Z" } } }
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_request | We could not understand the body. |
| 401 | unauthorized | Missing, malformed, unknown or revoked key. |
| 404 | not_found | No such booking or offer on your account. |
| 409 | idempotency_conflict / booking_conflict | See “Retrying safely”. |
| 410 | offer_expired | Offer older than 30 minutes. Re-quote. |
| 429 | rate_limited | Reserved. We do not rate-limit today; handle it so we can add it without breaking you. |
| 503 | service_unavailable | Temporary. Retry with backoff. |
7. Vehicles
Offers come back in your vehicle vocabulary. Send us your codes (e.g.
SEDAN, MPV5) and we map them to our fleet — until then you will see
ours. Codes we do not recognise are ignored, never rejected, so adding a
vehicle type on your side can never break your quotes.
Our fleet, with what each seats:
| Vehicle | Class | Passengers | Luggage |
|---|---|---|---|
| Small Car | Economy | 3 | 2 |
| Standard Car | Standard | 3 | 3 |
| Premium Car | Business | 3 | 3 |
| SUV | Standard | 4 | 4 |
| Standard Van | Standard | 7 | 7 |
| Premium Van | Business | 7 | 7 |
| Large Van | Standard | 9 | 9 |
| Standard Bus | Standard | 20 | 20 |
8. Going live
/pingreturns200with"env":"sandbox".- You can quote, book, read and cancel end to end in sandbox.
- A decline is handled as a normal empty result, not an error.
- You send an
Idempotency-Keyon every booking. - An expired offer is handled by re-quoting, not retrying.
- Your vehicle mapping is agreed with us.
- We issue the production key. You swap the credential — nothing else changes.
- Confirm
/pingnow reports"env":"production", then send one real booking and check it with us before opening the taps.
Help
Talk to your Lakpura contact. It speeds things up enormously if you include the
quote_id or booking_ref and roughly when the call happened — we log
every request and can look up exactly what we received and answered.