Quote
A quote is the price of one choice: an option, a start, who is coming, and any extras or pickup. Ask for one each time the traveller changes a choice, and show it as it comes.
POST /v1/public/quotes with:
| Field | What it is |
|---|---|
option_id |
The option chosen. |
start |
The start, in the trip’s own time: 2026-11-14T15:00, or a date (2026-11-14) for an option open all day. |
units |
Each unit chosen, with qty, and ages when the seller prices children by age. |
extras |
Each extra chosen, with qty. Optional. |
pickup |
Where to collect the travellers: a place_id, or lat and lng. Optional. |
currency |
One of the site’s currencies. Optional: the site’s display currency by default. |
curl -X POST "https://api.stg.vacationpackagesoman.com/v1/public/quotes" \ -H "Tp-Publishable-Key: $TP_KEY" \ -H "Content-Type: application/json" \ -d '{ "option_id": "9e4c2a71-3b5d-4e8f-a1c6-7d20f5b93e48", "start": "2026-11-14T15:00", "units": [ { "unit_id": "1c7e9b42-6a3f-4d85-b0e2-58f4a1c9d736", "qty": 2 }, { "unit_id": "2d8fac53-7b40-4e96-81f3-69a5b2dae847", "qty": 1, "ages": [ 8 ] } ], "currency": "USD"}'const res = await fetch("https://api.stg.vacationpackagesoman.com/v1/public/quotes", { method: "POST", headers: { "Tp-Publishable-Key": process.env.TP_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ "option_id": "9e4c2a71-3b5d-4e8f-a1c6-7d20f5b93e48", "start": "2026-11-14T15:00", "units": [ { "unit_id": "1c7e9b42-6a3f-4d85-b0e2-58f4a1c9d736", "qty": 2 }, { "unit_id": "2d8fac53-7b40-4e96-81f3-69a5b2dae847", "qty": 1, "ages": [ 8 ] } ], "currency": "USD" }),})const answer = await res.json()Answer: 200 OK
{ "option_id": "9e4c2a71-3b5d-4e8f-a1c6-7d20f5b93e48", "lines": [ { "kind": "unit", "ref": "1c7e9b42-6a3f-4d85-b0e2-58f4a1c9d736", "label": "Adult", "qty": 2, "unit_price": { "amount": 6500, "currency": "USD" }, "original_unit_price": null, "total": { "amount": 13000, "currency": "USD" }, "pay_at": "upfront", "included": false, "vehicle": null }, { "kind": "unit", "ref": "2d8fac53-7b40-4e96-81f3-69a5b2dae847", "label": "Child (3-11)", "qty": 1, "unit_price": { "amount": 5000, "currency": "USD" }, "original_unit_price": null, "total": { "amount": 5000, "currency": "USD" }, "pay_at": "upfront", "included": false, "vehicle": null } ], "totals": { "due_now": { "amount": 18000, "currency": "USD" }, "pay_locally": [], "grand_total": { "amount": 18000, "currency": "USD" } }, "confirmation": "instant", "pending_expires_at": null, "remaining": 14, "pax": 3, "pairing": false, "vehicles": [], "fx": { "base": "USD", "quote": "USD", "rate": "1", "rate_id": "0f6d2b8e-4c1a-4e5f-9b3d-7a8c6e2f1d90", "as_of": "2026-10-01T00:00:00.000Z" }, "policy": { "name": "Flexible", "tiers": [ { "from_days": 1, "to_days": null, "refund_pct": 100 }, { "from_days": 0, "to_days": 0, "refund_pct": 0 } ], "preset": "flexible" }, "pickup": null}- Each line: its
label,qtyandtotal. Whenoriginal_unit_priceis set, show it struck through: it is a special’s price before its discount. totals.due_now: what is paid when booking.totals.pay_locally: what is paid on the day, in its own currency. Show it apart, so nobody thinks it is charged now.confirmation:instantbooks at once;manualwaits for the seller, so say so before checkout.policy: the cancellation policy this booking would have.
Show amounts with the currency’s own decimals (money). Don’t round, add or convert them yourself.
When it is refused
Section titled “When it is refused”A selection that can’t be booked gets a 422 whose code says why, with
the facts in details:
curl -X POST "https://api.stg.vacationpackagesoman.com/v1/public/quotes" \ -H "Tp-Publishable-Key: $TP_KEY" \ -H "Content-Type: application/json" \ -d '{ "option_id": "9e4c2a71-3b5d-4e8f-a1c6-7d20f5b93e48", "start": "2026-11-14T15:00", "units": [ { "unit_id": "1c7e9b42-6a3f-4d85-b0e2-58f4a1c9d736", "qty": 4 } ], "currency": "USD"}'const res = await fetch("https://api.stg.vacationpackagesoman.com/v1/public/quotes", { method: "POST", headers: { "Tp-Publishable-Key": process.env.TP_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ "option_id": "9e4c2a71-3b5d-4e8f-a1c6-7d20f5b93e48", "start": "2026-11-14T15:00", "units": [ { "unit_id": "1c7e9b42-6a3f-4d85-b0e2-58f4a1c9d736", "qty": 4 } ], "currency": "USD" }),})const answer = await res.json()Answer: 422 Unprocessable Content
{ "type": "about:blank", "title": "Unprocessable Content", "status": 422, "code": "NotEnoughCapacity", "detail": "Not enough places are left then.", "details": { "remaining": 2 }}Turn each code into a message of your own, using details: “Only 2 places
are left at 15:00” reads better than an error. The
errors guide lists them
all. A request that doesn’t fit the contract (a missing field, a wrong type)
is 422 InvalidInput instead.
When the traveller goes on to book, hold the places with the same selection.