Skip to content

Deposits and no-shows

The booking policy protects a slot with a deposit (paid up front, taken off the bill), a card on file (saved uncharged, for a later late-cancellation or no-show fee), or both from one card entry. Payments count towards any fee: a £10.00$10.00€10.00 deposit and a £15.00$15.00€15.00 late fee leave £5.00$5.00€5.00 to charge, never £25.00$25.00€25.00.

ClientTuppenceYour serverbooks, pays deposit (card saved)booking.deposit_paidattends: deposit off billor POST …/no_showfee.status: duePOST …/charge_feecharges fee − depositbooking.fee_charged

To take a deposit when a client books, set the policy’s deposit to fixed, percent (rounded down) or full, and choose whether it’s refundable on a timely cancellation.

Booked…What happens
On the booking page or widgetShown before the client commits (Continue to pay the £10.00$10.00€10.00 deposit). The booking exists only once it’s paid. If the card is declined, the client tries another or the slot is released.
Any other way (desk, API, agent)Confirmed at once with a deposit_amount due, not collected. Use POST /v1/bookings/{id}/payments with kind: "deposit"; the client pays at its approval_url.

The deposit comes off amount_due:

OutcomeThe deposit
Client cancels in timeRefunded, if refundable
Client cancels late, or doesn’t comeKept, towards the fee
You cancelRefunded in full, even a non-refundable one

Set card_required (always, new_clients or over_amount), card_after_no_shows (1–10 no-shows), or both.

  • Without a deposit, a card step saves the card uncharged, and saving it makes the booking.
  • The card’s mandate uses the policy’s words and names its possible fees. The largest caps any one charge.
  • Clients with a covering card (saved under a booking policy, valid in the visit’s month) aren’t asked again.

When a client doesn’t come, or cancels late

Section titled “When a client doesn’t come, or cancels late”
HowNotes
No-showNo-show on the calendar, or POST /v1/bookings/{id}/no_show, after the startauto_no_show marks a confirmed, unchecked-in booking 2 hours after its end. Undo with …/complete within a day, unless the fee is charged.
Late cancelPOST /v1/bookings/{id}/cancel with by: "client" inside the free-cancellation window, or the client’s own linkKeeps the deposit; the late fee becomes due. GET /v1/bookings/{id}/cancellation?by=client previews refund_amount, deposit_kept, fee_due.

The booking’s fee shows what’s due:

{ "kind": "no_show", "amount": 2500, "deposit_applied": 0, "status": "due", "clause": "If you don't turn up, a fee of 100% (£25.00) applies." }
{ "kind": "no_show", "amount": 2500, "deposit_applied": 0, "status": "due", "clause": "If you don't turn up, a fee of 100% ($25.00) applies." }
{ "kind": "no_show", "amount": 2500, "deposit_applied": 0, "status": "due", "clause": "If you don't turn up, a fee of 100% (€25.00) applies." }

kind is no_show or late_cancel; clause is the policy sentence behind it.

Dashboard: Fees groups fees by state: Due, Waiting for the client, Refused, Charged, Waived, Refunded. Charge confirms the amount, clause and mode; Waive records a reason. The app requires Face ID to charge.

API: POST /v1/bookings/{id}/charge_fee charges the card on file once, within 14 days of the appointment (then 410 fee_window_closed). Send an Idempotency-Key, and amount to charge less. It returns the payment; check its status:

ResultThe feeEvent
The bank took itcharged; booking payment_status → fee_chargedbooking.fee_charged
The bank wants the client to confirmpending_authentication; the client gets an email link to confirmbooking.fee_charged once they do
The bank refusedfailed, reason in fee.failurebooking.fee_failed
Other calls
409 no_card_on_fileNo covering card. Send a pay link: POST /v1/bookings/{id}/payments with kind: "no_show_fee" or "late_cancel_fee".
POST /v1/bookings/{id}/waive_feeWaives it, with a reason. You can still charge it within the 14 days.
POST /v1/refundsRefunds a charged fee like any payment.

Automatically: auto_charge charges a no-show fee 2 hours after marking and a late-cancel fee at once, the same way and once. Refusals, bank authentication and missing cards are left to you. A booking you moved after the client’s window closed is never charged.

Disputes: evidence is drafted from the booking: the accepted policy (words, version, when and where), the card mandate, who marked the no-show, the receipt and every message. You review and submit it.

Mark a no-show, try to charge without a card, then waive the fee. POST /v1/test_helpers/bookings/{id}/force_no_show skips the wait. The SDKs’ request covers endpoints without a method.

Terminal window
api() { # GET a path, or POST it with a body, using your key and a fresh Idempotency-Key
if [ $# -eq 1 ]; then
curl -sS "https://api.tuppence.ai$1" -H "Authorization: Bearer $TUPPENCE_SECRET_KEY"
else
curl -sS "https://api.tuppence.ai$1" -H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d "$2"
fi
}
# Not turning up costs the whole price.
api /v1/booking_policy '{"no_show_fee": {"type": "percent", "value": 100}}' > /dev/null
# A service, someone who does it, and tomorrow's first free time with them.
location=$(api /v1/locations | jq -r '.data[0].id')
service=$(api /v1/services '{"name": "Coaching session", "duration_minutes": 30, "amount": 2500}' | jq -r .id)
kai=$(api /v1/staff "{\"name\": \"Kai\", \"locations\": [\"$location\"]}" | jq -r .id)
d='[["09:00", "17:00"]]'
api "/v1/staff/$kai/schedules" "{\"location\": \"$location\", \"effective_from\": \"$(date +%F)\",
\"pattern\": [{\"1\": $d, \"2\": $d, \"3\": $d, \"4\": $d, \"5\": $d, \"6\": $d, \"7\": $d}]}" > /dev/null
tomorrow=$(date -v+1d +%F 2>/dev/null || date -d "+1 day" +%F)
starts_at=$(api "/v1/availability?service=$service&location=$location&staff_member=$kai&from=$tomorrow&to=$tomorrow" \
| jq -r '.slots[0].starts_at')
booking=$(api /v1/bookings "{\"location\": \"$location\", \"items\": [{\"service\": \"$service\"}],
\"staff_member\": \"$kai\", \"starts_at\": \"$starts_at\",
\"client\": {\"name\": \"Sam Patel\", \"email\": \"sam@example.test\"}}" | jq -r .id)
# The client doesn't come.
api "/v1/test_helpers/bookings/$booking/force_no_show" '{}' \
| jq -r '"\(.status) \(.fee.amount) \(.fee.status)"' # no_show 2500 due
# No card was kept, so there is nothing to charge: a pay link is the way.
api "/v1/bookings/$booking/charge_fee" '{}' | jq -r .error.code # no_card_on_file
# Or let it go.
api "/v1/bookings/$booking/waive_fee" '{"reason": "Their first visit"}' | jq -r .fee.status # waived

Docs assistant

Open as a page
Loading the assistant…