Prices and quotes
Every amount in the API is an object with an integer amount in the
currency’s minor units and its currency:
{ "amount": 6500, "currency": "USD" }| Amount | Currency | Means |
|---|---|---|
6500 |
USD |
USD 65.00 (2 decimals) |
580595 |
OMR |
OMR 580.595 (3 decimals) |
9000 |
JPY |
JPY 9,000 (no decimals) |
Never turn an amount into a floating-point number to do sums with it, and
never assume two decimals: format it with the currency’s own, as
Intl.NumberFormat does:
// Exact: the amount goes in as a decimal string, never through a float.const show = (money: { amount: number; currency: string }, locale: string) => { const format = new Intl.NumberFormat(locale, { style: "currency", currency: money.currency }) const digits = format.resolvedOptions().maximumFractionDigits ?? 2 return format.format(`${money.amount}E-${digits}` as Intl.StringNumericLiteral)}Currencies and rates
Section titled “Currencies and rates”tourpingo keeps prices in US dollars, and a site can show other currencies
too (its currencies). Ask for one with currency; the API converts at
the current rate and rounds the site’s way (display_rounding). A quote’s
fx says which rate it used, and a booking keeps that rate for good.
Always show the quote
Section titled “Always show the quote”A trip card’s from_price is the lowest price per person the trip can be
booked at, for group_size travellers: on a private tour the price per
person drops as the group grows. It is good for a list, not for a booking.
Once the traveller has picked an option, a start and who is coming, ask for a quote. It is the price: what the traveller sees, what a hold holds and what checkout charges. Never add prices up on your side or keep one from an earlier visit; quote again.
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}| Field | What it is |
|---|---|
lines |
One per unit, extra, room or pickup: quantity, price of one, total. original_unit_price is a special’s price before its discount, to show struck through. |
totals.due_now |
Paid when booking. |
totals.pay_locally |
Paid on the day, per currency, never online. Lines with pay_at local. |
totals.grand_total |
Both together, for display. |
confirmation |
instant, or manual when the seller confirms by hand. |
remaining |
Places left before this booking, when counted. |
policy |
The cancellation policy this booking would have. |
The lines always add up to the totals exactly, after rounding.
When a quote is refused
Section titled “When a quote is refused”A selection that can’t be booked is refused with a 422 and a code
saying 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 }}Every refusal is listed in errors.