Skip to content

Record the channel's virtual card a completed SetupIntent saved

POST
/v1/reservations/{reservation_id}/virtual-cards
curl --request POST \
--url https://api.aerandil.com/v1/reservations/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/virtual-cards \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "activation_date": "2026-04-15", "funded_amount": { "amount": "example", "currency": "example" }, "retain_until": "2026-04-15", "setup_intent_id": "example" }'

Takes the SetupIntent id and the channel’s terms, never the card’s details: the intent must carry this deployment’s tags for this booking (setup_intent_not_for_this_booking otherwise) and the merchant account the house settles to now — none when it has none (setup_intent_not_for_this_account otherwise) — and have succeeded, and the card is read back off Stripe and must hang off the intent’s own Customer (card_belongs_to_another_customer). Recorded through the card rail’s verb record_saved_payment_card as card_format: virtual, bound to the booking, with the Customer — the one way a virtual card gets the Customer its off-session activation charge needs. A token handed in through the public store_payment_card names no Customer and is refused at activation.

reservation_id
required
string format: uuid

The booking the channel’s card was funded for.

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
activation_date

The first day the channel lets the house charge the card (the house’s calendar); the activation charge sweeps it from the house’s midnight on it.

string format: date
funded_amount
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
retain_until
required

Storage-limitation clock.

string format: date
setup_intent_id
required

The SetupIntent from POST /v1/reservations/{reservation_id}/virtual-card-setup-intents the desk completed with the channel’s card.

string
Examplegenerated
{
"activation_date": "2026-04-15",
"funded_amount": {
"amount": "example",
"currency": "example"
},
"retain_until": "2026-04-15",
"setup_intent_id": "example"
}

The channel’s card is on file for the booking. The display triple only; the token is payment.token-marked.

Media typeapplication/json
object
brand
required
string
Allowed values: visa mastercard amex discover diners jcb unionpay cartes_bancaires eftpos_au interac maestro unknown
expiry_month
required
integer
>= 1 <= 12
expiry_year
required
integer
last4
required

For a wallet card this is the device account number’s last four, not the funding card’s — the desk must say so rather than read it out as the guest’s card.

string
payment_card_id
required
string format: uuid
wallet
string | null
Allowed values: apple_pay google_pay samsung_pay link click_to_pay other
Example
{
"brand": "visa",
"wallet": "apple_pay"
}
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"
}
}

invalid_setup_intent_id (not shaped seti_ plus letters and digits; refused before the processor is asked), setup_intent_mismatch, setup_intent_not_for_this_booking, setup_intent_not_for_this_account (minted while the house settled to a different merchant account than it does now), card_not_collected or card_belongs_to_another_customer; or the channel’s terms refused (funded_amount not positive).

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 channel’s card was not stored. 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.