Skip to content

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)
}

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.

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.

What two adults and a child pay
Terminal window
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"
}'
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.

A selection that can’t be booked is refused with a 422 and a code saying why, with the facts in details:

Too many travellers for the places left
Terminal window
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"
}'
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.