Hotel guides
Cancellations and refunds
Every booking carries the cancellation terms it was sold on. Check them, show the cost, then cancel with one idempotent call.
Read the terms
A booking's terms are in data.cancellationPolicy on GET /bookings/:id — the same terms the price-check showed:
"cancellationPolicy": {
"refundable": true,
"deadlines": [
{ "before": "2026-11-08T12:00:00Z", "penaltyMinor": 17040, "currency": "USD" },
{ "before": "2026-11-10T00:00:00Z", "penaltyMinor": 51120, "currency": "USD" }
]
}| Field | Meaning |
|---|---|
refundable | Whether the rate is sold as refundable. Don't rely on it alone: read it together with the tiers below, which say when cancelling starts to cost money. |
deadlines[] | Penalty tiers. From before onward (until end, when given, or the next tier), cancelling costs penaltyMinor in currency. A tier's kind can instead be percent or nights, with percent or nights set and no amount. |
currentRefundability | When present: refundable, partially_refundable or non_refundable right now. |
nonrefundableDateRanges | Stay dates that are non-refundable from the moment of booking, whatever the tiers say. If present, some of the stay can't be refunded. |
deadlinesWithheld | Present and true when the tiers aren't shown. Treat the cost as unknown and don't promise a free cancellation. |
The inventory source's original policy is also kept on each line item, as details.bookingCancellationPolicy. When it has a freeCancelBefore, that is the authoritative free-cancellation cutoff: cancelling is only free before that moment, even if no penalty tier has started yet. Pass it to the cost check below; without it, don't tell the traveller a cancellation is free.
In the example, cancelling before 8 November 12:00 UTC is free, from then until 10 November it costs $170.40, and after that it costs the full $511.20.
All times are UTC. Convert them to the traveller's time zone for display, and say which zone you're showing.
// What would cancelling cost right now? Deadlines are penalty tiers:
// from `before` onward, cancelling costs `penaltyMinor`.
// freeCancelBefore: the stated free-cancellation cutoff, when the inventory gives
// one — from the line item's details.bookingCancellationPolicy (or the price-check).
// Returns null when the cost can't be worked out — don't promise a free cancellation.
function cancellationCost(policy, freeCancelBefore, now = new Date()) {
if (policy.deadlinesWithheld || policy.refundable === undefined) return null;
const started = policy.deadlines
.filter((tier) => tier.before && new Date(tier.before) <= now)
.sort((a, b) => new Date(b.before) - new Date(a.before));
if (started.length > 0) return started[0].penaltyMinor ?? null; // percent/nights tiers carry no amount
// No penalty tier has started. That's only "free" inside a stated free window:
// past the cutoff, or with no cutoff stated, the cost is unknown.
if (!policy.refundable || !freeCancelBefore) return null;
return now < new Date(freeCancelBefore) ? 0 : null;
}Cancel a booking
Cancel with an empty POST and an idempotency key. (A round-trip transfer booked as one booking can also cancel a single leg; see Transfers.)
curl -X POST "https://api.roaveagents.com/api/v1/bookings/66f1d2c4e4b0c1d2e3f4b010/cancel" \
-H "X-API-Key: $ROAVE_API_KEY" \
-H "Idempotency-Key: 9d3e7c1a-5b2f-4a8e-b6d0-1c4f2e8a7b93"{
"data": {
"bookingId": "66f1d2c4e4b0c1d2e3f4b010",
"state": "refunded",
"refund": { "status": "refunded" }
},
"meta": {},
"error": null
}You can cancel bookings that are confirmed or held. Cancelling a hold just releases it; nothing was paid, so nothing is charged. Anything else — a failed booking, one already cancelled — returns 409 booking_invalid_transition.
If the call times out, retry with the same idempotency key. You'll get the real outcome without sending the cancellation twice.
The outcome
refund.status | Meaning |
|---|---|
refunded | Cancelled, and the refundable amount has been returned. |
no_refund_due | Cancelled; nothing is due back — the booking was non-refundable at that point, or it was an unpaid hold. |
manual_review | The cancellation couldn't be completed automatically. Our operations team is finishing it; the booking's state is manual_review until they do. |
The response doesn't itemise the cancellation charge. The refund is worked out from the cancellation cost the inventory source reports, which normally matches the terms at the moment you cancelled — the amount your cost check showed.
The state in the response is the state at that moment: a cancellation with nothing to refund can report cancelled and then move to refunded. Read GET /bookings/:id for the final state.
Changing a booking
Bookings can't be amended through the API. To change dates, rooms or guests, check the terms, book the new arrangement, then cancel the old booking — in that order, so the traveller is never left without a room. For name corrections, contact support.