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
"cancellationPolicy": {
  "refundable": true,
  "deadlines": [
    { "before": "2026-11-08T12:00:00Z", "penaltyMinor": 17040, "currency": "USD" },
    { "before": "2026-11-10T00:00:00Z", "penaltyMinor": 51120, "currency": "USD" }
  ]
}
FieldMeaning
refundableWhether 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.
currentRefundabilityWhen present: refundable, partially_refundable or non_refundable right now.
nonrefundableDateRangesStay dates that are non-refundable from the moment of booking, whatever the tiers say. If present, some of the stay can't be refunded.
deadlinesWithheldPresent 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.

Working out the cost
// 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.)

Request · cURL
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"
Response · 201
{
  "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.statusMeaning
refundedCancelled, and the refundable amount has been returned.
no_refund_dueCancelled; nothing is due back — the booking was non-refundable at that point, or it was an unpaid hold.
manual_reviewThe 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.