Skip to main content

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+)โ€‹

  1. The applicable cancellation policy is resolved using the same hierarchy the supplier portal uses (specific item โ†’ category โ†’ all-{type} โ†’ all).
  2. The number of hours between now (in the supplier's timezone) and the booking's start is computed.
  3. The matching policy tier is the highest applies_after_hours whose value is โ‰ค that delta.
  4. The refund amount is total_paid ร— matched_tier.refund_percentage โˆ’ non_refundable_fee (floored at 0).
  5. If the policy has tiers but no tier matches (cancellation is past every cutoff), the refund is 0%.
  6. 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.

Changed

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โ€‹

ParameterTypeDescription
idintegerBooking ID

Request Bodyโ€‹

All fields are optional.

FieldTypeDefaultDescription
reasonstringโ€”Cancellation reason code (see below)
notesstringโ€”Free-text notes (max 1000 chars)
process_refundbooleantrueWhen false, the booking is cancelled but no refund is processed
send_notificationsbooleantrueWhen false, the customer cancellation email is suppressed
location_idintegerโ€”Where a cash refund is handed back. It leaves the open till at that location. Must be one of your locations.

Cancellation Reasonsโ€‹

CodeDescription
weatherWeather conditions
healthHealth issues
change_in_travel_plansChange in travel plans
found_better_optionFound better option
equipment_unavailableEquipment unavailable
duplicate_bookingDuplicate booking
otherOther 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โ€‹

FieldTypeDescription
refund.successbooleanWhether the refund completed
refund.refund_amountnumberNet refund (after non-refundable fee)
refund.methodstringoriginal_payment, store_credit, manual, or none (when refund_amount is 0)
refund.non_refundable_feenumberPer-policy fixed fee deducted
refund.matched_tierobject|nullThe tier that ran. null when the policy has no tiers OR the cancellation missed every tier
refund.matched_tier.applies_after_hoursintegerThe threshold this tier covers
refund.matched_tier.human_thresholdstringe.g. "1 day", "1 week", "Within window"
refund.matched_tier.refund_percentagenumberThe 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โ€‹

StatusCodeDescription
401authentication_requiredMissing or invalid API key
403insufficient_scopeAPI key lacks bookings:write scope
403forbiddenBooking belongs to another supplier
404resource_not_foundBooking not found
409conflictBooking is already cancelled, returned, or completed โ€” or the applicable policy does not allow cancellations
422validation_errorInvalid 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: false to suppress.
  • Store credit refunds appear immediately on the customer's account balance.