Skip to content

Hold an amount against a card

POST
/v1/folios/{folio_id}/card-authorisations
curl --request POST \
--url https://api.aerandil.com/v1/folios/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/card-authorisations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "amount": { "amount": "example", "currency": "example" }, "contested_network_signal_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "customer": "example", "payment_card_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "payment_method": "example", "purpose": "booking" }'

Authorises without capturing — the pre-arrival or check-in guarantee. Draws on a card already on file (payment_card_id, the path OPERA and Mews both take) or on a single-use payment_method token collected by a hosted surface and never kept. Never a PAN: the payment_token_no_pan_shape validator refuses anything Luhn-shaped, which is the structural half of the SAQ-A posture. The fraud assessment is filed against the booking behind the folio, or — for a group master or house account, which has none — against the folio itself, under the property it sits at. The hold records why it was placed (purpose, defaulted from the folio). A channel’s virtual card is refused (422 virtual_card_not_holdable): it is charged once on its activation date, never held. Where the house takes part in Watchtower, the card rail’s fraud network, a card the network blocks is refused before the processor is asked (422 refused_by_watchtower); its parameters name the answer (fraud_network_signal_id) the desk can contest and carry guest_message, the words a guest may be given — never the reason. 501 unless AERANDIL_STRIPE_ENABLED=1.

folio_id
required
string format: uuid

The folio the hold guarantees.

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
amount
required

The hold to take. The folio’s own currency is not assumed — a guest may be held in the card’s currency, and guessing here charges the wrong number.

object
amount
required

Exact decimal as a string. Money is never a float — binary floating point cannot hold 0.10.

string format: decimal
currency
required
string format: iso-4217
contested_network_signal_id

A Watchtower answer that refused this card (refused_by_watchtower, its fraud_network_signal_id) and that the desk has since contested with contest_fraud_network_signal because it believes the guest. A block the network gives again takes the hold to review (risk_band high) instead of refusing it.

string format: uuid
customer

The saved-card customer, when a raw token names one.

string
payment_card_id

A card already on file. The ordinary path, and the one OPERA and Mews both take.

string format: uuid
payment_method

A processor token collected by a hosted surface and used once without being kept — the fallback for a guarantee nobody will draw on again. Never a PAN.

string
purpose

Why the hold is placed, recorded on the payment as hold_purpose (OPERA’s source of authorisation). Omitted, the route says it from the folio: check_in on a stay’s folio, booking on the booking’s own account, incidentals on a folio with no booking behind it.

string
Allowed values: booking check_in top_up re_placement incidentals no_show post_stay other

The hold was taken and recorded as a payment leg.

Media typeapplication/json
object
authorisation_expires_at

When the hold lapses at the processor, when it says. Absent is not proof of no expiry.

string | null format: date-time
fraud_risk_assessment_id
required

The assessment this authorisation was scored against. Always present: every hold is scored, including the ones that sail through.

string format: uuid
payment_id
required
string format: uuid
risk_band
required

The band that decided whether a human had to look.

string
Examplegenerated
{
"authorisation_expires_at": "2026-04-15T12:00:00Z",
"fraud_risk_assessment_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"payment_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"risk_band": "example"
}
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 — the property’s card payments settle to its own merchant account, which Stripe has not yet let take charges.

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"
}
}

The request parsed but a validator refused it. The envelope’s code names which rule.

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). A hold is never queued — the desk needs the issuer’s answer now: check the guest in without one, or try 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.