Skip to content

Take a deposit

Some bookings are worth money before anyone sits down. A corporate dinner, a party of thirty on New Year’s Eve, a table held over a bank holiday — the bookings a restaurant cannot afford to have evaporate. For those, a restaurant configures a guarantee: the guest saves a card, and the restaurant may charge it if nobody turns up.

You can book those slots. What you cannot do is take the card.

Ask once per venue, before you build anything. GET /v1/configuration carries card_guarantee_from_party_size:

  • null: no booking made now, on any service or date, is asked for a card. Either the restaurant uses no card guarantee, or it cannot take one at the moment. Skip the card step for this venue.
  • A number: some service, on some date, may ask a party of that size or larger, and no service asks a smaller party. Build the card step, then read the exact answer for the date the guest picks.

The exact answer is guarantee_from_party_size on each service in GET /v1/availability: the smallest party that service asks for a card on that date, whatever party_size you queried with. null there means no party is asked on that service that day. guarantee on the same service carries the amounts, as Read the quote sets out.

The venue value can change at any time with no change on your side: when the restaurant turns guarantees on or off, or when its card payments stop or resume. If you keep it for longer than a guest’s session, revalidate it with the ETag.

A restaurant whose card payments are not active asks for no card, and the create is not refused for it. The booking is taken without a guarantee: confirmed, or pending under manual approval. Both fields read null in that state, so they already describe what the create will do.

The card step ends on a page you do not own

Section titled “The card step ends on a page you do not own”

This is the constraint, and it decides the shape of the work before anything else does.

A booking on a guaranteed slot needs a payment credential. Your key reads the quote — mode, amount, cancellation terms — and never the credential behind it: no response to your key carries a client secret, a connected-account id or a publishable key. That is a v1 position rather than an oversight.

The guest saves their card on a Service-hosted page, by following a link we email them.

So a flow that expects to finish inside your own checkout cannot be built here today. What you get instead is this:

  1. Your create returns 201, with the booking in awaiting_guarantee.
  2. Service emails the guest a pay-link.
  3. The guest saves a card on our page.
  4. The booking becomes confirmed, or pending when the service asks the restaurant to approve its bookings. reservation.created fires then, with that status.

Your confirmation screen tells the guest to expect that email. If your product promises one uninterrupted checkout, decide about guaranteed slots on day one — either route them somewhere else, or set expectations in the copy. Finding this out after the funnel is built is expensive.

If you need the card taken inside a booking flow you control, embed the Booking Widget instead. It takes the card in-page, because it is our page.

?expand=modification_token on the create returns the guest’s own credential for their booking page. That page is not on api.useservice.app. It is on the booking host, the one public_booking_url in GET /v1/configuration names (https://book.useservice.app/r/{slug} in production), and the link is {public_booking_url}/{modification_token}: the same link the confirmation and pay-link emails carry.

Whoever holds the token can do on that page everything the guest can: read the booking, change it, cancel it, and, while the booking is awaiting_guarantee, save the card. The page’s own data carries what its card form is mounted with. So the promise above covers your key and not the token. Once you ask for the token, you hold the guest’s key to their booking.

Treat it like the guest’s password:

  • Pass it only to the guest, as the link to their own page.
  • Never log it, and never render it as text.
  • Keep it no longer than you need it. If you hand the guest the link once, do not store it afterwards.

The endpoints behind that page belong to the widget and are not part of this API. Build on the link, not on them. If you send the guest no link of your own, skip the expand. Whether Service sends the link follows guest_notifications, and on this flow it always does: partner_managed is refused on a slot that takes a card, so the confirmation and pay-link emails carry it.

Scope Why this flow needs it
reservations:read Availability — the only place the guarantee quote appears before you book.
reservations:write The create.
reservations:write:override Only to waive the guarantee with override: ["guarantee_required"] on the create — booking the table with no card held. A PATCH cannot waive it.
guests:read Only for ?expand=modification_token on POST /v1/reservations, the guest’s own key to their booking page. See The guest token is the guest’s own key. Reading back the contact details of the booking you just took needs it too.

There is no guarantee scope, because there is no guarantee endpoint. Charging, refunding and releasing a card are the restaurant’s acts, done from their back office, and your key reads the outcome without causing it. Asking for a card in the first place is a field on the create and the PATCH, and it needs nothing beyond reservations:write — see Ask for a card yourself. See Scopes.

Read the quote before you promise anything

Section titled “Read the quote before you promise anything”

GET /v1/availability carries a guarantee object per shift. null means no card will be asked for. Anything else describes what a booking in that shift must guarantee, priced for the party size you asked about.

{
"object": "availability_guarantee",
"mode": "imprint",
"currency": "EUR",
"per_guest_amount": 2000,
"amount": 60000,
"cancel_hours": 48
}
Field What it means
mode imprint today, and only imprint. See Imprint, not charge.
per_guest_amount Minor units per guest — 2000 is €20.00.
amount Minor units for the party size you asked about. The figure to show.
cancel_hours Hours before the seating after which a cancellation may be charged. null means cancelling is always free and only a no-show is charged.

It is party-size-aware and it is emitted only when an imprint would genuinely trigger, so honouring it is how a funnel avoids telling a guest a booking is free and then producing one that waits on a card. Put the figure and the cancellation terms on the page where the guest picks their time.

An imprint saves the card and authorises an amount. No money moves at booking time. The restaurant may charge some or all of it afterwards — a no-show, or a cancellation inside the cancel_hours window — and the guarantee on the booking reports both figures separately:

amount is the consented ceiling, charged_amount what has been taken and refunded_amount what has been given back. Every field, and the states a guarantee moves through, are on The guarantee object.

One value is yours to expect: origin is staff_request on every booking your key creates. The card is asked for by a pay-link, the same request a restaurant sends from its back office.

kind publishes two values, imprint and prepayment. Only imprint occurs today; charge-up-front is a later phase and no slot produces it. Read the field rather than assuming, and treat an unfamiliar value the way you treat any other forward-compatible enum.

No Stripe identifier appears anywhere in this object, and no dispute status. Those are the restaurant’s relationship with their payment provider, and there is nothing you could do with them.

The five reservation.guarantee_* webhooks — requested, charged, refunded, released, payment failed — are how you learn that any of this moved without polling for it.

Terminal window
curl -X POST "https://api.useservice.app/v1/reservations" \
-H "Authorization: Bearer $SERVICE_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"service_date": "2026-12-31",
"start_time": "20:00",
"party_size": 30,
"guest": {
"first_name": "Marie",
"last_name": "Dupont",
"email": "[email protected]",
"language": "fr"
}
}'

Nothing in that body mentions the guarantee. The slot decides, and the response tells you which fork it took: read status. awaiting_guarantee means the table is held and the card is not saved yet.

Four things follow from that booking existing in that state.

The guest has 48 hours, or less. The pay-link request carries a deadline of 48 hours, clamped to the booked time and to the free-cancellation deadline when either is sooner. The guarantee’s expires_at is the real figure — read it rather than computing your own.

The table is held for the whole window. This kind of request does not hand the table back when the deadline passes; it holds it until the request is resolved. So leaving unpaid requests standing costs the restaurant real capacity.

Unpaid means cancelled, automatically. If the guest never saves a card, the request lapses and the booking is cancelled. Auto-cancel is on, because a restaurant that configured a guarantee did so precisely to avoid seating a party that never guaranteed.

Your 201 arrives before the reservation.created webhook does — and if the guest never pays, that webhook never fires at all, nor does a reservation.cancelled. A booking your system was never told about does not get a cancellation. Reconcile from the API, as Keep your system in sync sets out.

The slot decides on its own, and so can you. Send a guarantee object on a create or on a PATCH, and the guest is asked for a card on a booking that would otherwise take none. It is the same request the restaurant’s own staff raise from their back office, on the same terms, and everything above about the pay-link, the deadline and the hold on the table applies to it unchanged.

Terminal window
curl -X POST "https://api.useservice.app/v1/reservations" \
-H "Authorization: Bearer $SERVICE_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"service_date": "2026-12-31",
"start_time": "20:00",
"party_size": 6,
"guest": { "first_name": "Marie", "last_name": "Dupont", "email": "[email protected]" },
"guarantee": { "amount_cents": 6000, "deadline_hours": 24, "auto_cancel_on_expiry": true }
}'

All three keys are optional, and {} asks for a card on the restaurant’s own terms.

Key What omitting it means
amount_cents The amount the restaurant’s own guarantee policy resolves for this service and this party size.
deadline_hours 48 hours. A deadline you send is clamped the same way — to the booked time, and to the free-cancellation deadline when that is still ahead.
auto_cancel_on_expiry true: an unpaid request cancels the booking when the deadline passes.

amount_cents and deadline_hours are whole numbers greater than zero. Anything else is a 400 naming guarantee.amount_cents or guarantee.deadline_hours, and auto_cancel_on_expiry takes true or false.

reservations:write is the whole of it. Asking for a card takes nothing from the restaurant, adds a protection it would otherwise not have, and cannot seat a party its rules would turn away — so it sits outside reservations:write:override, which is the scope for booking past those rules. Staff can withdraw the request from the back office at any point.

You cannot own the guest’s messages and ask for a card in the same breath. guarantee sent beside guest_notifications: "partner_managed" is a 400 naming guarantee. The pay-link is an email from Service, and partner_managed is you telling Service not to write to this guest. Send guest_notifications as service_managed, or drop guarantee.

On a PATCH the card is priced after the edits apply. One body that grows the party to six and asks for a card prices the card for six. The booking has to be an upcoming, confirmed one that carries no guarantee yet; guarantee_request_not_allowed refuses every other state.

A card slot and an explicit request part company when card payments are off. On a restaurant that cannot take charges, a slot that would ask for a card takes the booking and confirms it with no guarantee at all — you are told nothing, because nothing was refused. An explicit guarantee on that same restaurant is refused with payments_suspended. You asked a question, so you get an answer.

Four refusals belong to asking: payments_suspended, guarantee_request_not_allowed, guarantee_guest_email_required and guarantee_amount_invalid. A create with no email address to send the pay-link to is refused earlier, with email_required_for_guarantee, whose message names which of the two asked for the card.

Changing the booking before the card is saved

Section titled “Changing the booking before the card is saved”

A PATCH on a booking still in awaiting_guarantee applies like any other: the new slot is re-checked against the rules, the table is picked again, and the booking stays awaiting_guarantee until the card is saved.

The card request does not follow the change. amount, cancel_deadline_at and expires_at stay exactly as your create computed them, whatever you move. A party grown from four to six on a per-guest guarantee is still asked for the four-guest amount, and the guest’s page still shows that amount. If the change should be priced afresh, cancel the booking and take a new one.

A change that would newly put a card-less booking onto a guarantee is refused, with 422 guarantee_modify_requires_card. A modification cannot collect a card, on your door or on the guest’s: their own booking page answers the same refusal, and tells them to cancel and book again. Nothing is changed either way. See You cannot raise a consented amount for the neighbouring refusal on a booking that already has a card.

Until the card is saved — or the request lapses and the booking is cancelled — no reservation.* event about the booking reaches you, changes included. A PATCH in that window sends no reservation.updated, and neither ?expand=events nor the events feed lists it. The guest is not written to about it either: a change made before the booking is published sends them no message. The reservation.created you receive, and the confirmation the guest receives, describe the booking as it stands when it is published, not as it was first requested.

An email is mandatory here, whatever the flags say

Section titled “An email is mandatory here, whatever the flags say”

A create on a guaranteed slot with no email address is refused with email_required_for_guarantee. This is the single place on this API where a guest email is not optional.

The surprise is that the configuration’s advisory flags do not reach it. require_guest_email and require_guest_phone describe what the restaurant asks for on its own booking form and bind nothing here — a create with neither is accepted on an ordinary slot. On a guaranteed slot the requirement is mechanical rather than a policy: the pay-link is an email, and without an address there is nowhere to send it and no way for the booking to complete.

It arrives inside violations alongside guarantee_required, and the order is deliberate. email_required_for_guarantee comes first and is not overridable; guarantee_required follows and is. Read top-down and the first remedy you find is the one that costs no new privilege:

  • Send an email address, or reuse a guest_id whose profile carries one.
  • Or override: ["guarantee_required"], which books the table with no guarantee at all. That is the phone-booking case — you do not ask someone on the phone for a card imprint — and the restaurant then holds nothing against a no-show. Waive it deliberately, and see Book past the rules for what gets recorded when you do.

A related refusal sits next to it: guest_notifications_required_for_guarantee, returned when you assert guest_notifications: "partner_managed" — you own guest communication for this booking — on a guaranteed slot. The guarantee is an email: pay-link, reminder, receipt. So the assertion and the slot cannot both be honoured. Drop the suppression for that booking, or waive the guarantee and leave no pay-link to suppress.

Grow a party from four to eight on a booking whose card was saved through the restaurant’s own widget, and the PATCH is refused with guarantee_consent_guest_only. Nothing changes — the whole modify rolls back, including every other field in the same body.

The reason is not a permission model. A higher amount is a higher ceiling the restaurant may one day charge, and raising it needs the cardholder’s agreement to the new figure. That agreement is archived evidence from the person who gave it. Your key is not the guest, and neither is the restaurant’s own staff: nobody can assert it on their behalf.

Three ways forward, and the refusal names them:

  • Make a change that does not raise the amount. Moving the day or the time applies, and the free-cancellation deadline follows the new seating. So does shrinking the party — the imprint is re-priced downward with nobody asked, because the existing, higher mandate already covers the smaller figure.
  • Ask the restaurant. Back-office staff can change the party size, and the guarantee keeps its existing, lower ceiling.
  • Hand the guest their own booking page. It is the only surface that can collect a renewed mandate. Build the link from public_booking_url on the configuration and the modification_token you asked for on the create — the same pair the booking funnel uses.

This refusal cannot arise on a booking your key created. A booking taken through the restaurant’s own widget carries a card the guest entered and agreed an amount for, and a higher amount is a new agreement only the guest can give. A booking your key created carries a card requested through the pay-link (origin: "staff_request"), at the amount the restaurant’s rules set or the amount you asked for. No figure was ever put in front of that guest to agree to, and the card is never re-priced afterwards, so there is nothing for this code to refuse. You meet it anyway, because a sync hands you the restaurant’s widget bookings and your modify path then runs over them.

A pay-link card keeps the amount it was requested at. A create for four on a guarantee of €10 a guest asks for €40, and the guest saves the card at €40. Grow the party to six a week later and the change goes through, with the card still at €40. The cover is no longer proportional to the party, so a late cancellation or a no-show on that booking can be charged up to €40 and no more. A restaurant that minds the gap can release the card from its back office and request a new one at the new figure. A widget booking behaves the other way round: it re-prices, and asks the guest to agree to the higher amount.

A booking with no card behind it meets a different refusal when a PATCH would bring a guarantee on — a party grown past the size at which the restaurant asks for a card, or a move to a service that asks every booking for one. That is guarantee_modify_requires_card, and nothing is written. No modification has a card step — not yours, and not the guest’s own booking page, which answers the same refusal and tells them to cancel and book again. Only a create takes a card: cancel and rebook, and the new booking goes through the card step described above.

Code When What to do
email_required_for_guarantee 422, inside violations, overridable: false Send an email address, or waive the guarantee.
guarantee_required 422, inside violations, overridable: true Waiving books the table with no card held.
guest_notifications_required_for_guarantee 422, param: "guest_notifications", overridable: false Drop the suppression, or waive the guarantee.
guarantee_consent_guest_only 422 on a PATCH One of the three remedies above.
guarantee_modify_requires_card 422 on a PATCH, never overridable Cancel and rebook — only a create takes a card. The guest’s own page answers the same refusal.
guarantee_modify_locked 403 The free-cancel deadline has passed on an active imprint. The booking can still be cancelled, under the restaurant’s terms.
guarantee_guest_email_required 422 The guarantee step was reached with no contact email. Send a guest with one.
guarantee_amount_invalid 422 The restaurant’s configuration resolves to a non-positive amount. Send guarantee.amount_cents yourself, or report it to them.
payments_suspended 422 on a guarantee you sent The restaurant cannot take card payments. Drop guarantee and take the booking unguaranteed.
guarantee_request_not_allowed 422 on a guarantee you sent The booking is not an upcoming, confirmed one with no guarantee yet. Re-read it before asking again.

Card guarantees is the reference for all of them.

Before you put this in front of a restaurant

Section titled “Before you put this in front of a restaurant”

A demo books a guaranteed slot and screenshots the 201. Five things separate that from something a restaurant will let near New Year’s Eve.

It discloses the amount before the guest commits. The quote is on availability precisely so the guest sees the figure and the cancellation terms on the page where they choose the time, not in an email afterwards.

It renders three outcomes, not one. confirmed, pending and awaiting_guarantee are three different things to tell a guest, and only the first means the table is theirs. A booking that leaves awaiting_guarantee can land on either of the other two. A confirmation screen with one message is wrong two-thirds of the time on a restaurant that uses guarantees.

It tells the guest about the email. The pay-link is the next thing that has to happen and it happens somewhere else. A guest who does not know to expect it does not open it, and the booking cancels itself two days later.

It sends guest.language. The pay-link, the reminder and the receipt all go out in the guest’s language if you set it, and in the restaurant’s primary language if you do not. A payment request somebody cannot read is a payment request they do not action.

It never treats awaiting_guarantee as a booking. Not in its own reporting, not in a covers count, and not in anything it sends the guest. It is a table held against a promise that has not been kept yet.