Skip to content

Collect a posted cancellation fee off the booking hold and the stored card

POST
/v1/reservations/{reservation_id}/cancellation-fee/collection
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.

reservation_id
required
string format: uuid

The cancelled reservation carrying the posted fee.

Idempotency-Key
string
>= 1 characters

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.

Media typeapplication/json
object
payment_card_id

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.

string format: uuid
Examplegenerated
{
"payment_card_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"
}

The attempt was made and its outcome recorded — collected, authentication_required, or uncollectible.

Media typeapplication/json
object
amount
required

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.

string
awaiting_guest

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.

boolean
currency
required
string
failure_reason

Why it did not, in the processor’s own words.

string | null
fee
required

The posted cancellation fee (cancellation_fee_amount), as a decimal string.

string
hold_amount

What the booking’s hold has paid toward the fee, as a decimal string (0 when there was none).

string
outcome
required

authentication_required is not a decline — the issuer wants SCA, the fee is still owed, and it is collectable once the guest authenticates.

string
Allowed values: collected authentication_required uncollectible
paid_by

Which path paid a collected fee — booking_hold, stored_card or booking_hold_and_stored_card.

string | null
psp_reference

The processor’s reference when the card was charged — the only handle that survives into a dispute.

string | null
reservation_id
required
string format: uuid
Example
{
"outcome": "collected"
}
Cache-Control
string

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.

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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.

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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.

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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.

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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.

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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.

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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"
}
}
Retry-After
integer

Seconds to wait before trying the same request again (RFC 9110 §10.2.3) — the queue’s first backoff.