- The
net_amountreturned byPOST /quotesis what you owe SafarAPI when you book. - Even if the underlying rate pack price changes during those 30 minutes, your quote stays at the quoted price.
- After 30 minutes, the
quote_idis rejected byPOST /bookingswith409 partner.booking.quote.expired.
Lifecycle
Quotes are single‑use: oncePOST /bookings succeeds with a quote_id, the same quote cannot be used twice.
Pricing logic
Thenet_amount exposed in your quote is the net amount due to SafarAPI — that is, the public Safariat price minus any partner‑specific margin negotiated in your contract.
You are free to:
- Show your customer the gross price (your markup on top, your decision)
- Show your customer the net price (zero markup)
- Show your customer a discounted price (you absorbing some of the cost)
net_amount you remit at booking time.
Breakdown
Thebreakdown array in the quote response shows you how the total is computed:
travelers is always present. room_supplement appears only when the room composition you sent
actually carries a supplement.
How travellers are priced
Each traveller is classified by age against the adventure’sage_brackets, then charged the rate
that matches their class. A child rate replaces the adult rate, it is never added to it:
So a pack at 2 400 MAD with a
child_price of 1 440 MAD, quoted for two adults and one child,
totals 2 × 2400 + 1 × 1440 = 6 240 MAD on the travelers line.
What the pack tells you before you quote
Four fields on the rate pack say in advance what a composition will do, so you never have to discover a refusal at quote time:
Both flags are always present, both caps count across the whole booking rather than per room, and
0 is a meaningful cap that forbids the category outright. Read the two flags before offering a
child or infant selector, and the two caps before offering a quantity.
How many travellers fit
Two limits apply, and they count differently.room_types[].max_occupancy caps one room, extra bed included. A room holding more than that is
refused with 400 adventure.room.occupancy.exceeded. Infants do not count against it, since they
take no bed: two adults and an infant fit in a room whose max_occupancy is 2.
capacity caps the departure. Every traveller counts against it, infants included, and the same
count is applied again when the booking is created, so a composition that quotes will not be
turned away for size at booking time.
How rooms are priced
The room supplement is a separate line, driven byrate_packs[].room_allocation:
PER_ROOM— oneroom_types[].supplementper room booked. Three single rooms cost three single supplements, one triple room costs one triple supplement.child_supplementdoes not apply in this mode.PER_PERSON— every occupant pays the supplement of the room type they occupy, adults atsupplementand children atchild_supplement. Used for Umrah, Hajj and charters.
child_supplement. An infant pays no room supplement
at all, and a traveller older than age_brackets.child_max_age pays the adult supplement, the
same way they pay the adult rate on the travelers line.
child_price and room_types[].child_supplement are different things. child_price replaces
the traveller’s base price. child_supplement adds a room charge for a child, and only under
PER_PERSON. A room type whose supplement is 0.00 genuinely costs nothing extra: the operator
declared no difference between that room type and the base price.Unknown fields are rejected
A request body carrying a field the endpoint does not declare is refused with400 and the code
partner.request.field.unknown, naming the field. Nothing is ever ignored silently.
This matters most on POST /quotes. The room composition is children, an array of { "age": n }
objects. Sending children_ages, or any other near-miss, used to return a perfectly valid quote for
the adults alone, with the children dropped and no error anywhere. A wrong field name now fails loudly
instead of producing a quote for the wrong party.
Anti‑fraud check
When you book withPOST /bookings, SafarAPI validates that payment_confirmation.paid_amount_net strictly equals the quote’s net_amount. If they differ — by even 1 centime — the booking is rejected with 422 payment.amount.mismatch.
This is the cornerstone of the trust between SafarAPI and your bank: we trust your bank’s payment confirmation because the amount cannot be tampered with after the quote.