Appearance
Cancellations
Two partner operations after confirm: cancel (free the slot, mark CANCELLED) and refund (mark REFUNDED, free the slot if it was still BOOKED).
Auth: the owning partner’s x-api-key. Another partner’s booking → 403 UNAUTHORIZED. Missing id → 404.
Neither call moves money at a payment processor. If you charged the customer, you refund them.
idempotency_key is required. Reusing that key for a different booking or for the other operation (cancel vs refund) is 409 IDEMPOTENCY_KEY_MISMATCH.
Cancel a booking
http
POST /api/v1/bookings/{bookingId}/cancel
Content-Type: application/json
x-api-key: <your-api-key>
{ "reason": "customer request", "idempotency_key": "cancel-9f2a-…" }200: { "message": "Booking cancelled successfully" }.
A second cancel without a matching stored key, after the booking is already CANCELLED, is 409 INVALID_HOLD_STATE. A retry with the original key is 200.
Refund a booking
http
POST /api/v1/bookings/{bookingId}/refund
Content-Type: application/json
x-api-key: <your-api-key>
{ "reason": "customer request", "idempotency_key": "refund-9f2a-…" }200: { "message": "Booking refunded successfully" }.
Which state allows which call
Booking status | Cancel | Refund |
|---|---|---|
BOOKED | 200 → CANCELLED, slot freed | 200 → REFUNDED, slot freed |
CANCELLED | 409 INVALID_HOLD_STATE (200 if it is a retry with the original key) | 200 → REFUNDED (marks a cancelled booking as refunded) |
REFUNDED | 409 INVALID_HOLD_STATE | 409 INVALID_HOLD_STATE (200 if it is a retry with the original key) |
The reason you send is stored and passed on in the webhook details; if you send none, a default is used: "Customer request" for a cancel, "Manager initiated refund" for a refund (the same text whoever refunds). Do not parse it.
Release a hold
Not a booking. See Holds: POST /api/v1/partner/holds/{holdId}/cancel.
