Changelog
cancel-reservation now cancels only through a quote the user has seen. The caller reads reservation-detail with include_cancellation_quote=true, shows the refund, fee, additional charge and expiring points, and after explicit consent sends that quote's cancellation_token. The token is an opaque handle issued by reservation-detail and stays usable for 30 minutes; the quote is re-read just before dispatch, so changed terms return CANCELLATION_QUOTE_CHANGED instead of cancelling on terms the user did not accept.
BREAKING for every caller that still sends reservation_ref. The bound transition runs from the 2026-07-20 contract to the token-only release, so it also carries the reviewed-cancellation change of 2026-10-14. (1) Request: reservation_ref is no longer an accepted field and cancellation_token is required. Read reservation-detail with {reservation_ref, include_cancellation_quote: true}, show the user the refund, fee, additional charge and expiring points it returns, and after explicit consent call cancel-reservation with {cancellation_token} copied exactly as issued; reason, reason_code and reason_text stay optional. A token is usable for 30 minutes; the quote is re-read immediately before dispatch. A request without cancellation_token fails input validation (HTTP 400 invalid_request) and sends nothing to CatchTable. (2) Compatibility window: the 2026-10-14 release kept reservation_ref as a deprecated input (contract.deprecatedFields, removalAfter 2026-10-28) that cancelled only free cancellations and otherwise returned CANCELLATION_PREVIEW_REQUIRED; this release removes that input, its deprecatedFields declaration and the INVALID_INPUT and CANCELLATION_PREVIEW_REQUIRED codes of cancel-reservation (cancel-all-reservations still declares CANCELLATION_PREVIEW_REQUIRED). (3) Errors since 2026-07-20: CANCELLATION_QUOTE_UNAVAILABLE (409, terms cannot be verified, use CatchTable); CANCELLATION_QUOTE_CHANGED (409, obtain consent to a new quote); CANCELLATION_OUTCOME_UNKNOWN (409, verify reservation and payment history before another write); CANCELLATION_TOKEN_EXPIRED_UNSENT (400, details.dispatched false and restart_via reservation-detail: read a new quote and ask again); CANCELLATION_REFUSED (409, CatchTable refused with a reason code in details.upstream_result_code and details.dispatched refused; the reservation is still active and nothing was cancelled, so a newly consented quote may be sent later); reauth_required (reconnect the CatchTable session); the rate-limit code is RATE_LIMITED (429) instead of 429. AUTH_REQUIRED and UPSTREAM_ERROR are unchanged. (4) Response: unchanged (reservation_ref, status, optional cancelled_at).