Refund a booking
POST /v1/bookings/{id}/refunds refunds all or part of a booking’s
succeeded payment back to the guest’s original payment method.
Full refund
Section titled “Full refund”Omit amountMinorUnits to refund whatever remains on the payment:
curl -X POST https://api.<domain>/v1/bookings/<RESERVATION_ID>/refunds \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ -d '{}'const { data: refund, error } = await client.POST("/v1/bookings/{reservationId}/refunds", { params: { path: { reservationId: "<RESERVATION_ID>" } }, body: {},});if (error) throw error;{ "id": "...", "reservationId": "<RESERVATION_ID>", "amountMinorUnits": "4500000", "currency": "IDR", "state": "succeeded", "stripeRefundId": "re_..."}Partial refund
Section titled “Partial refund”Pass amountMinorUnits as a string of minor units, same convention as
every other amount on this API — see Quotes on why
it’s a string, not a number:
curl -X POST https://api.<domain>/v1/bookings/<RESERVATION_ID>/refunds \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ -d '{ "amountMinorUnits": "1500000" }'const { data: refund, error } = await client.POST("/v1/bookings/{reservationId}/refunds", { params: { path: { reservationId: "<RESERVATION_ID>" } }, body: { amountMinorUnits: "1500000" },});if (error) throw error;You can refund the same booking more than once — for example, one night at
a time — as long as the running total of successful refunds never exceeds
what was actually paid. Ask for more than what’s left and you get
409 refund_exceeds_payment, which carries the actual remaining amount so
you can retry with a corrected value.
What this needs
Section titled “What this needs”- A booking with a succeeded payment. A booking that never took a
payment, or whose payment failed, returns
409 no_succeeded_payment. - The tenant needs a connected Stripe account. If they don’t,
409 stripe_not_configured. - Your
sk_key needs thepayment:refund:createscope. Ask your operator contact if a refund call comes back404and you’re sure the booking ID is right — a scope-less key and a wrong tenant look identical from the outside, by design (see Errors’s 404 policy).
Errors
Section titled “Errors”| Status | code |
Meaning |
|---|---|---|
| 404 | booking_not_found |
Wrong ID, wrong tenant’s key, or a pk_ key (refunds never reach pk_) |
| 409 | no_succeeded_payment |
This booking has nothing paid to refund |
| 409 | stripe_not_configured |
The tenant has no Stripe Connect account yet |
| 409 | refund_exceeds_payment |
You asked for more than what’s left — retry with a smaller amount |
| 409 | stripe_refund_failed |
Stripe refused the refund; the response’s detail carries Stripe’s own reason |
| 409 | nothing_to_refund |
Nothing is left to refund on this payment — a repeat call after a prior full refund |
Retrying safely
Section titled “Retrying safely”Checking what’s already been refunded
Section titled “Checking what’s already been refunded”GET /v1/payments (sk_ with payments:read, or an operator session)
lists the tenant’s payment intents, each with its own
refundedMinorUnits — the running total refunded on that payment so far.
Check it before a partial refund if you’re not sure how much is left,
rather than guessing and hitting refund_exceeds_payment.
Next steps
Section titled “Next steps”- Errors — the full RFC 9457 error shape and the 404 policy.
- Holds and confirmation — how a booking gets to a succeeded payment in the first place.