Skip to content

Give money back

POST
/v1/payments/{payment_id}/refunds
curl --request POST \
--url https://api.aerandil.com/v1/payments/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/refunds \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "refund_amount": { "amount": "example", "currency": "example" }, "refund_reason": "duplicate" }'

Creates a new negative-amount payment leg — proposal 0019’s shape, which is why this is 201 and returns a second payment id rather than mutating the original. Partial refunds are ordinary; the kernel computes the remaining refundable balance from the legs already recorded, so the caller does not have to track it. The leg is pending until the processor confirms it on the webhook.

payment_id
required
string format: uuid

The captured payment being refunded.

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

Partial refunds are ordinary; the kernel computes the remaining refundable balance from the legs already recorded.

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
refund_reason
required
string
Allowed values: duplicate fraudulent requested_by_customer reservation_cancelled deposit_return service_recovery overcharge_correction no_show_policy_exception package_component_refund membership_cancellation chargeback_prevention other

The refund leg was recorded.

Media typeapplication/json
object
original_payment_id
required
string format: uuid
refund_payment_id
required

The new negative-amount leg. Proposal 0019’s shape: a refund is a second payment, not an edit of the first.

string format: uuid
status

pending until the processor confirms it on the webhook.

string
Examplegenerated
{
"original_payment_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"refund_payment_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"status": "example"
}
Cache-Control
string

Always no-store. Object and link state is live data and is never cacheable.

Stripe could not be reached (a transport failure, a 5xx, a 429, or this attempt’s key still running at Stripe), so the work was written down as a rail instruction and is taken when the rail is back — under the same processor key as this attempt, so it is never taken twice. A second request while it is queued answers the same instruction (already_queued); the desk may cancel it while it is still queued (cancel_rail_instruction).

Media typeapplication/json
object
already_queued
boolean
amount
string | null format: decimal
currency
string | null
idempotent_replay
boolean
kind
required
string
Allowed values: capture void refund charge_off_session top_up
message
string
payment_id
string | null format: uuid
queued
required
boolean
rail_instruction_id
required
string format: uuid
state
required
string
Allowed values: queued sending
stay_id
string | null format: uuid
Example
{
"kind": "capture",
"queued": true,
"state": "queued"
}
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"
}
}

rail_instruction_open — another verb is already queued for this hold or stay (a release behind a queued capture); it is taken first, when the card rail is back.

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