Collect a posted cancellation fee off the booking hold and the stored card
const url = 'https://api.aerandil.com/v1/reservations/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/cancellation-fee/collection';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"payment_card_id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.aerandil.com/v1/reservations/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/cancellation-fee/collection \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "payment_card_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" }'The desk posts the fee (post_cancellation_fee) onto the cancelled booking’s own account; this is the leg that moves the money — the no-show fee’s collection, for the other retained fee. The booking’s own card hold pays first (captured for no more than the account owes), and the stored card is charged off-session only for what the account still owes after it: a deposit already taken sits on that account as a credit, so the card is asked for min(fee, outstanding), never the deposit again. An account that owes nothing is refused before the processor is asked. Name a card with payment_card_id or let the route pick, as for the no-show fee. The verb that records the outcome (collect_cancellation_fee) is the card rail’s own: this route is the only way to it, under actions:apply or its own grant actions:hospitality.action.collect_cancellation_fee:apply. Two of the three outcomes are not errors: all three answer 200 with an outcome. authentication_required is not a decline — the fee stays owed and the guest is chased (awaiting_guest). The answer’s amount is what this collection asked for — the hold’s share (hold_amount) and the card’s — and fee is the posted fee; the two differ by the deposit.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The cancelled reservation carrying the posted fee.
Header Parameters
Section titled “Header Parameters”Opaque idempotency token (Stripe-shape). Repeated POSTs with the same key replay the original response within the cache TTL rather than creating a duplicate object.
Request Body
Section titled “Request Body”object
Charge this card specifically. Omit it and the route picks the card bound to the reservation, then any active consented card the guest has on file.
Examplegenerated
{ "payment_card_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"}Responses
Section titled “Responses”The attempt was made and its outcome recorded — collected, authentication_required, or uncollectible.
object
What this collection asked for, as a decimal string: the booking hold’s share (hold_amount) and the card’s — the fee, capped at what the booking’s account still owed after the deposit on it. On collected, what was taken.
True when the outcome is authentication_required and the card rail recorded the pending authentication: a payment request with reason authentication_required (authentication_for = cancellation_fee) carries the house’s deadline, and its page goes to the guest at a house with the chase on. Paid, it collects the fee.
Why it did not, in the processor’s own words.
The posted cancellation fee (cancellation_fee_amount), as a decimal string.
What the booking’s hold has paid toward the fee, as a decimal string (0 when there was none).
authentication_required is not a decline — the issuer wants SCA, the fee is still owed, and it is collectable once the guest authenticates.
Which path paid a collected fee — booking_hold, stored_card or booking_hold_and_stored_card.
The processor’s reference when the card was charged — the only handle that survives into a dispute.
Example
{ "outcome": "collected"}Headers
Section titled “Headers”Always no-store. Object and link state is live data and is never cacheable.
The bearer authenticated but does not hold the scope this operation requires.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}No such object in the caller’s tenant — which is also the answer when it exists in another tenant, by design.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}merchant_account_not_ready, processor_key_reused, cancellation_fee_attempt_open (the last collection, on another card, never recorded its outcome — retry without payment_card_id) or cancellation_fee_attempt_unsettled (an open attempt the ledger cannot ask the processor about — the operator settles it against Stripe first), as for the no-show fee.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}cancellation_fee_not_assessed — no fee was posted for this reservation; cancellation_fee_already_collected — the money moved, and returning it is a refund; cancellation_fee_nothing_outstanding — the booking’s account owes nothing toward the fee (the deposit on it covered it), so there is nothing to take; or payment_card_not_chargeable — a card named through payment_card_id that cannot be charged. A fee left authentication_required or uncollectible is not refused — both are retryable. The fee’s own state is the at-most-once guard rather than Idempotency-Key.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}No card rail is configured on this deployment.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}rail_unavailable — the card processor could not be reached (a transport failure, a 5xx, a 429, or this request’s key still running at the processor). The fee is not queued: it stands, and the desk takes it again. Try again after Retry-After.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}Headers
Section titled “Headers”Seconds to wait before trying the same request again (RFC 9110 §10.2.3) — the queue’s first backoff.