Cancel Booking
Cancel an existing booking. As of Phase 1.9, cancellation honours the supplier's multi-tier cancellation policy and the response includes the refund breakdown (matched tier, refund amount, non-refundable fee).
DELETE /v1/bookings/{id}
Authenticationโ
Requires a valid API key with bookings:write scope.
Refund behaviour (Phase 1.9+)โ
- The applicable cancellation policy is resolved using the same hierarchy the supplier portal uses (specific item โ category โ
all-{type}โ all). - The number of hours between now (in the supplier's timezone) and the booking's start is computed.
- The matching policy tier is the highest
applies_after_hourswhose value is โค that delta. - The refund amount is
total_paid ร matched_tier.refund_percentage โ non_refundable_fee(floored at 0). - If the policy has tiers but no tier matches (cancellation is past every cutoff), the refund is 0%.
- If
process_refund: false, no refund runs but the booking is still cancelled.
To preview these numbers without committing to the cancellation, call GET /v1/bookings/{id}/refund-preview first.
Bookings that have already startedโ
A booking whose start time is in the past can be cancelled. An API key is a staff credential, so this endpoint behaves exactly like the supplier portal Cancel modal: a late call-in still has to be closed off. For a customer who never came, use Mark a No Show instead, which keeps the booking paid and refunds nothing. A booking already marked no_show can still be cancelled here, for example to give money back as a goodwill gesture. The cancellation policy decides the refund on its own: a cancellation past the start time falls past every tier and refunds 0% unless the policy says otherwise.
Only the booking's status blocks a cancellation: cancelled, returned, and completed return 409 Conflict.
This endpoint used to return 409 Conflict with code booking_started once the start time had passed. That code is no longer emitted. If your integration branches on it, that branch is now dead and can be removed.
Path Parametersโ
| Parameter | Type | Description |
|---|---|---|
id | integer | Booking ID |
Request Bodyโ
All fields are optional.
| Field | Type | Default | Description |
|---|---|---|---|
reason | string | โ | Cancellation reason code (see below) |
notes | string | โ | Free-text notes (max 1000 chars) |
process_refund | boolean | true | When false, the booking is cancelled but no refund is processed |
send_notifications | boolean | true | When false, the customer cancellation email is suppressed |
location_id | integer | โ | Where a cash refund is handed back. It leaves the open till at that location. Must be one of your locations. |
Cancellation Reasonsโ
| Code | Description |
|---|---|
weather | Weather conditions |
health | Health issues |
change_in_travel_plans | Change in travel plans |
found_better_option | Found better option |
equipment_unavailable | Equipment unavailable |
duplicate_booking | Duplicate booking |
other | Other reason |
Request Exampleโ
curl -X DELETE "https://app.equipdash.com/api/v1/bookings/123" \
-H "Authorization: Bearer eqd_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"reason": "change_in_travel_plans",
"notes": "Customer called to cancel due to flight cancellation.",
"process_refund": true,
"send_notifications": true
}'
Responseโ
Returns 200 OK with the cancelled booking under data and a refund summary at the top level (when a refund ran).
{
"data": {
"id": 123,
"type": "experience",
"status": "cancelled",
"payment_status": "refunded",
"start_date": "2026-05-01T14:00:00+00:00",
"end_date": "2026-05-01T16:00:00+00:00",
"currency": "USD",
"pricing": {
"subtotal": 200.00,
"total": 200.00
},
"customer": {
"id": 456,
"first_name": "Alex",
"last_name": "Doe",
"email": "alex@example.com"
},
"cancellation": {
"reason": "change_in_travel_plans",
"notes": "Customer called to cancel due to flight cancellation."
},
"created_at": "2026-04-10T14:30:00+00:00",
"updated_at": "2026-04-22T11:00:00+00:00"
},
"refund": {
"success": true,
"refund_amount": 95.00,
"method": "original_payment",
"non_refundable_fee": 5.00,
"matched_tier": {
"id": 12,
"applies_after_hours": 24,
"human_threshold": "1 day",
"refund_percentage": 50.0,
"refund_method": "original_payment"
}
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-04-22T11:00:00+00:00"
}
}
The refund object is emitted as a top-level sibling of data (not nested inside it) so clients that only care about the booking record can keep consuming data unchanged. It is only present when a refund was actually attempted (i.e. the booking had payments AND process_refund is true).
Refund response shapeโ
| Field | Type | Description |
|---|---|---|
refund.success | boolean | Whether the refund completed |
refund.refund_amount | number | Net refund (after non-refundable fee) |
refund.method | string | original_payment, store_credit, manual, or none (when refund_amount is 0) |
refund.non_refundable_fee | number | Per-policy fixed fee deducted |
refund.matched_tier | object|null | The tier that ran. null when the policy has no tiers OR the cancellation missed every tier |
refund.matched_tier.applies_after_hours | integer | The threshold this tier covers |
refund.matched_tier.human_threshold | string | e.g. "1 day", "1 week", "Within window" |
refund.matched_tier.refund_percentage | number | The tier's refund % |
refund is omitted entirely when no refund was attempted (e.g. process_refund: false, or no payments existed on the booking).
Error Responsesโ
| Status | Code | Description |
|---|---|---|
| 401 | authentication_required | Missing or invalid API key |
| 403 | insufficient_scope | API key lacks bookings:write scope |
| 403 | forbidden | Booking belongs to another supplier |
| 404 | resource_not_found | Booking not found |
| 409 | conflict | Booking is already cancelled, returned, or completed โ or the applicable policy does not allow cancellations |
| 422 | validation_error | Invalid cancellation reason or body |
conflict exampleโ
{
"error": {
"code": "conflict",
"message": "Cannot cancel a completed booking"
},
"meta": {
"request_id": "req_abc123"
}
}
Notesโ
- Cancellation is permanent and cannot be undone via the API.
- Refunds may take 5โ10 business days to appear on the customer's statement when refunded to the original payment method.
- The cancellation email cites the matched tier (e.g. "You cancelled 30 hours before, which matches the 24h+ tier of your Flexible policy") โ pass
send_notifications: falseto suppress. - Store credit refunds appear immediately on the customer's account balance.