API referencepayments
Refund payment
POST
/v1/payments/{paymentId}/refundIdempotency-Key requiredAccepts X-Api-Key · oauthClientCredentials · bearer
Execute a provider-level refund of captured money
The provider-execution layer that the order's cancel and amend verbs drive, plus console goodwill refunds — never a second client-facing refund truth: the order stays the money-back source of truth…Show more
The provider-execution layer that the order's cancel and amend verbs drive, plus console goodwill refunds — never a second client-facing refund truth: the order stays the money-back source of truth and order.refund_issued its event. amount absent = refund everything captured and not yet refunded. A refund beyond the captured-and-unrefunded amount is 409 CONFLICT, and on a cancelled order so is a refund beyond the order's committed cancellation refund. A refund that leaves part of the captured amount unrefunded moves a CAPTURED payment (or a PARTIALLY_CAPTURED one whose authorization has lapsed) to PARTIALLY_REFUNDED, which accepts further refunds; the refund that brings cumulative refunds to the captured amount moves it to REFUNDED (terminal). A PARTIALLY_CAPTURED payment that can still capture keeps that state after a partial refund (refundedAmount shows it). payment.refunded is unchanged: one event per refund, amount = the rise. Harness provider only today. A post-booking operation on an intent that already exists: it stays up for a SUSPENDED tenant and, on the production deployment, for a tenant whose activation has not been approved, so its 403 is PaymentForbidden — never TENANT_SUSPENDED or TENANT_NOT_LIVE.Path parameters
- paymentIdstringRequiredThe paymentId returned by POST /v1/payments/intent
Headers
- Idempotency-KeystringRequired
Mandatory on mutations: **48h TTL**; replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate…Show more
Mandatory on mutations: **48h TTL**; replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS. - X-Actor-KindstringOptional
The channel that initiated a money verb: ai marks an AI-initiated call, which must then carry X-Approval-Token.Show more
The channel that initiated a money verb: ai marks an AI-initiated call, which must then carry X-Approval-Token. The header is accepted for compatibility and can only mark a call ai, never un-mark one. New routes (POST /v1/approvals, /v1/agent-proposals) do not take it. - X-Approval-TokenstringOptional
Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit).Show more
Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit). Human sessions and system keys are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token.
Request body
- amountMoneyOptionalInteger minor units + ISO-4217, never floats (platform law)
5 child attributes
- amountintegerRequiredMinor units
- currencystringRequiredISO-4217
- fxAppliedbooleanOptionalTrue when the amount was converted from the supplier's currency.
- fxSnapshotIdstringOptionalThe exchange-rate snapshot the amount was converted with, fixed when the price was quoted.
- breakdownarray of PriceBreakdownLineOptional
Sell-side component lines summing to amount; the platform may send it empty.Show more
Sell-side component lines summing to amount; the platform may send it empty. One line shape everywhere — the same PriceBreakdownLine the rule simulation answers. Never carries a supplier net.13 child attributes
- typestringRequiredThe kind of line, for example BASE, TAX, FEE or DISCOUNT.
- amountintegerRequiredMinor units
- feeTypestringOptionalFor FEE lines produced by a FEE_LINE rule — the tenant's fee label
- labelstringOptionalDisplay label in the request locale
- taxComponentstringOptionalFor GST / TAX lines — CGST | SGST | IGST | UTGST | CESS | VAT | SALES_TAX | OCCUPANCY_TAX; open string
- taxCodeTaxCodeOptionalThe statutory classification of a taxed line (SAC/HSN in India, VAT codes elsewhere)
2 child attributes
- schemestringRequiredSAC | HSN | VAT | GST | OTHER; open string
- codestringRequiredThe tax code, for example a SAC or HSN code in India or a VAT code elsewhere.
- rateBasisPointsintegerOptionalThe tax or fee rate in basis points when the line is rate-based (1800 = 18%)
- collectedBystringOptionalPLATFORM | SUPPLIER | PROPERTY — who takes the money; a non-PLATFORM line outside Offer.price is a pay-at-property disclosure
- payableAtstringOptionalBOOKING | PROPERTY | DESK | AFTER_SERVICE
- includedbooleanOptionalTrue when the line is inside the enclosing amount (default); false for a disclosed line outside it
- quantityintegerOptionalHow many units the line covers, when more than one.
- unitAmountintegerOptionalMinor units per unit when quantity is given
- currencystringOptional
ISO-4217 code of this line's amount and unitAmount.Show more
ISO-4217 code of this line's amount and unitAmount. Set ONLY on a disclosed line (included false) that is NOT in the enclosing price's currency, for example a resort fee payable at the property in its local currency. Absent means the enclosing price's currency, as before; a line inside the total never carries it.
- reasonstringOptionalWhy — e.g. CANCELLATION | AMENDMENT | GOODWILL; open string, recorded for audit
Responses
- 200The payment intent after the refund; refundedAmount is cumulativePaymentIntentResponse
- 400ApiErrorResponse
VALIDATION on a capture or refund amount.Show more
VALIDATION on a capture or refund amount. details.field names the member and details.reason is AMOUNT_NOT_INTEGER_MINOR_UNITS, AMOUNT_NOT_POSITIVE, or CURRENCY_MISMATCH (details.currency is the payment's). Nothing was sent to the PSP. - 401ApiErrorResponse
UNAUTHENTICATED — missing or expired token.Show more
UNAUTHENTICATED — missing or expired token. Declared on every operation because `security` is declared globally at the document root: every route is authenticated, so every route can answer 401. - 403PERMISSION_DENIED.ApiErrorResponse
- 404NOT_FOUND — no such payment intent for this tenantApiErrorResponse
- 405ApiErrorResponse
VALIDATION — the public edge serves GET, POST, PUT, PATCH and DELETE only; any other method is refused before the platform sees it (details.reason METHOD_NOT_ALLOWED, details.source edge).Show more
VALIDATION — the public edge serves GET, POST, PUT, PATCH and DELETE only; any other method is refused before the platform sees it (details.reason METHOD_NOT_ALLOWED, details.source edge). Not retryable as sent. - 409ApiErrorResponse
CONFLICT | IDEMPOTENCY_CONFLICT | IDEMPOTENCY_IN_PROGRESS on capture, refund and void (a void of a payment that has captured money is INVALID_STATE with details.state CAPTURED: refund it instead).Show more
CONFLICT | IDEMPOTENCY_CONFLICT | IDEMPOTENCY_IN_PROGRESS on capture, refund and void (a void of a payment that has captured money is INVALID_STATE with details.state CAPTURED: refund it instead). CONFLICT carries details.reason: EXCEEDS_AUTHORIZED_REMAINDER (capture beyond what is authorized and not yet captured), EXCEEDS_REFUNDABLE, INVALID_STATE (details.state names the payment's state) or CONCURRENT_UPDATE. Never an over-capture or an over-refund. A payment whose cumulative refunds equal its captured amount moves to state REFUNDED; one whose refunds are below its captured amount and that has nothing left to capture is PARTIALLY_REFUNDED. Void adds BOUND_TO_ORDER (the payment is bound to an order — cancel the order) and PROVIDER_REFUSED. - 413ApiErrorResponse
VALIDATION — the request body is larger than the surface's limit.Show more
VALIDATION — the request body is larger than the surface's limit. Not retryable as sent: shrink the body. Declared on every operation that takes a request body. details carries exactly one key, maxBodyBytes (integer): the limit that applies to this surface, in bytes. - 414ApiErrorResponse
VALIDATION — the request URI (path and query string) is longer than 8 KiB; the public edge refuses it before the platform sees it (details.reason URI_TOO_LONG, details.source edge).Show more
VALIDATION — the request URI (path and query string) is longer than 8 KiB; the public edge refuses it before the platform sees it (details.reason URI_TOO_LONG, details.source edge). Not retryable as sent: send long input in the body of a POST. - 415ApiErrorResponse
VALIDATION — a request body that is not JSON (application/json or application/*+json; a form body only on the OAuth token routes).Show more
VALIDATION — a request body that is not JSON (application/json or application/*+json; a form body only on the OAuth token routes). The public edge refuses it before the platform sees it (details.reason UNSUPPORTED_MEDIA_TYPE, details.source edge). Not retryable as sent: send Content-Type application/json. Declared on every operation that takes a JSON request body. - 429ApiErrorResponse
RATE_LIMITED — the API key or its tenant is over a rate limit; wait for Retry-After, then retry.Show more
RATE_LIMITED — the API key or its tenant is over a rate limit; wait for Retry-After, then retry. Every routed request is counted against two token buckets, the key's and the tenant's (all its keys together): 50 requests a second with a burst of 100 on the standard plan. Every travel verb except polling a search also spends from a supplier budget, per key and per tenant: 10 requests a second with a burst of 20, and a plan may add a daily quota of supplier calls. Answers carry the IETF RateLimit header fields for the tightest bucket consulted (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, RateLimit-Policy); a refusal adds Retry-After and details.scope (KEY or TENANT), details.budget (PLATFORM or SUPPLIER), details.reason (RATE or DAILY_QUOTA), details.tier and details.retryAfterMs. A sandbox may run with the limiter off, in which case none of these headers is sent. A search that is still warming is a different answer with a different meaning (keep polling). with the key-velocity check on, also details.reason VELOCITY_LIMIT, a soft breach of the API key's own scope (components/responses/VelocityLimited), nextAction slow_down. - 500INTERNAL — a bug, and traceId is the support handle.ApiErrorResponse
- 501ApiErrorResponse
NOT_IMPLEMENTED — this deployment serves no payments: no payment provider is configured (provider=none), so every payment route answers 501.Show more
NOT_IMPLEMENTED — this deployment serves no payments: no payment provider is configured (provider=none), so every payment route answers 501. Not a capabilities-doc bug: payments are deployment configuration. - 502Also the order read routes (getOrder, listOrderDocuments, listOrderEvents, searchOrders) when the order store times out: details.store names the store, retryable true.ApiErrorResponse
- 503ApiErrorResponse
Errors you should handle
Branch on code and honour retryable — never on the HTTP status alone.
| 400 | VALIDATION amount.amount must be an integer number of minor units | not retryable |
| 401 | UNAUTHENTICATED The bearer token is expired. Re-authenticate and retry. | not retryable |
| 403 | PERMISSION_DENIED Role lacks the required scope: payments:write | not retryable |
| 404 | NOT_FOUND No such payment intent. | not retryable |
| 405 | VALIDATION This method is not served. | not retryable |
| 409 | CONFLICT The capture exceeds the authorized amount not yet captured. | not retryable |
| 409 | IDEMPOTENCY_CONFLICT Idempotency-Key 7f0c33d1-9a1e-4d2c-9f77-1c0f2b3a4d55 was used with a different body; never executed | not retryable |
| 409 | IDEMPOTENCY_IN_PROGRESS A request with this Idempotency-Key is still in flight. Retry shortly. | retryable |
| 413 | VALIDATION The request body is larger than this API accepts (262144 bytes). | not retryable |
| 414 | VALIDATION The request URI is longer than 8 KiB. | not retryable |
| 415 | VALIDATION Send application/json. | not retryable |
| 429 | RATE_LIMITED Too many requests for this key on the supplier budget. Wait for the Retry-After interval before trying again. | retryable |
| 500 | INTERNAL An unexpected error occurred. Quote the traceId to support. | retryable |
| 501 | NOT_IMPLEMENTED This deployment serves no payments: no payment provider is configured. | not retryable |
| 502 | UPSTREAM_UNAVAILABLE Every supplier for HTL is circuit-open; no fan-out was attempted | retryable |
| 503 | PROVIDER_DOWN EXAMPLEHOTELS is unavailable; the order was not attempted and nothing was captured | retryable |