API referenceTenvelo API
https://api.tenvelo.com The contract, as OpenAPI

Contract 2026-10-10. Every path below carries its own major version.

Customers · Reference

Bookings

Reservations to hold a table at a time. Server-authoritative.

Operations
20
Base
https://api.tenvelo.com
  1. GET/v1/booking-flags

    List booking flags

    No. listBookingFlagsAuth bookings:read

    Returns the booking flags nobody has dealt with yet, newest first.

    A flag is what the server says instead of refusing. Nothing a device recorded is turned away — the booking happened — so where the booking projector cannot apply a fact, it applies the booking as far as it goes and a flag says what did not fit. That makes this the only place some of them appear: a seat on a cancelled booking looks settled on the booking itself, and nothing else says it was.

    A flag's booking_id may name a booking that does not exist. That is the most serious kind there is: the projector could not place the reservation at all, so it is on the device that recorded it and in no books. Those are returned like any other.

    Parameters

    • cursorquery · string

      Opaque cursor from a previous page's next_cursor. Omit to start from the beginning.

      Pass it back exactly as received. It is base64 and its structure is not part of this contract — decoding it, or constructing one by hand, will break without notice.

    • limitquery · integer · int32

      Maximum items to return. The server may return fewer, so always drive the loop from has_more rather than from the size of the page you got back.

    Responses

    • 200

      The open booking flags, newest first.

      BookingFlagPage · application/json
      • has_moreboolean

        Required

        Whether another page follows.

      • itemsarray of BookingFlag

        Required

        • idstring · uuid

          Required

          The flag's own id, minted by the server when it was raised.

        • booking_idstring · uuid

          Required

          The booking this is about, as the fact named it.

          The booking may not exist. A flag is raised for a fact the projector could not place at all, and that is the most serious kind there is: the reservation is on the device that recorded it and in no books. Read this as the booking it refers to, not as one you can always fetch.

        • created_atTimestamp

          RequiredRead only

          Server-assigned RFC 3339 timestamp, always UTC.

          Server-assigned because device clocks drift and cannot be trusted for ordering — a till that has been powered off for a week may come back with a clock hours out. Where the device's own view of time matters it is carried separately in client_created_at.

        • detailstring

          Required

          What is wrong, in the server's own words — what the write path would have said had it refused. Meant to be shown to staff as it is.

          ≤ 1024 chars

        • fact_idstring · uuid

          The fact that raised it, so a client can say what happened rather than only that something did. Absent when the flag is not about one particular fact.

      • next_cursorstring

        Nullable

        Opaque cursor for the next page, or null on the last page. Pass it back verbatim as the cursor query parameter; its contents are not part of this contract.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

  2. POST/v1/booking-flags/{id}/clear

    Record that a booking flag was dealt with

    No. clearBookingFlagAuth bookings:write

    Records that a person has dealt with the flag: a flag_cleared fact on the booking's subject, folded like any other — the clear is as much a fact as the flag was, and the row stays in place, so the same fact can never raise the flag again. This is the operation the list points at. It is not doing nothing: it is the person saying the reservation's money was handled.

    Idempotent: the fact's id is derived from the flag, so a retry records the same fact once, and a flag already cleared answers as clear. A device token is refused with 403 — a booking flag is a person's to deal with.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    Responses

    • 204

      The flag is recorded as dealt with.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

  3. GET/v1/bookings

    List reservations in a window

    No. getBookingsAuth bookings:read

    Returns the merchant's reservations in a time window, optionally for one location. A till uses this to see who is booked so staff can greet and seat a party; the back office manages the same resource.

    Bookings are server-authoritative: the front of house records them and the change feed publishes them (scoped to the booking's location, without the party's phone and email), so a till's host stand holds today and tomorrow offline. The window query is for backfilling a device that missed changes. The result is bounded by the window, which is its own pagination.

    A till sees only its own location's reservations, and asking for another location is forbidden. The party's phone and email are left out for a till and for staff: seating a party needs its name, size, time, table and note, not how to reach it.

    Parameters

    • fromquery · string · date-time

      The start of the window, inclusive. Defaults to the start of today.

    • toquery · string · date-time

      The end of the window, exclusive. Defaults to the end of today.

    • location_idquery · string · uuid

      Only reservations for this branch. A till may name only its own.

    Responses

    • 200

      The reservations in the window.

      BookingPage · application/json
      • bookedinteger · int64

        Required

        How many of them are on the book — confirmed, seated or finished — and not canceled or a no-show.

        ≥ 0

      • coversinteger · int64

        Required

        How many people the booked parties seat.

        ≥ 0

      • itemsarray of Booking

        Required

        • idstring · uuid

          Required

          The reservation's own id, minted when it is taken.

        • namestring

          Required

          The name it was taken under.

        • customer_idstring · uuid

          The directory guest this reservation is for, if one was chosen. When set, the name, phone and email shown on the resource are the guest's from the directory, read fresh: the guest's record is authoritative, never a copy taken when the booking was made. A booking taken by name alone has no customer_id and keeps its own details.

        • depositBookingDeposit

          The card-not-present authorisation held against a booking (bookings item 13), when one has been taken: an amount held on a card, released when the party comes or the booking is cancelled, captured as the fee when it does not.

          • amountMoney

            A monetary amount in a specific currency: Square's Money, and Stripe's amount beside its currency.

            The amount is always an integer count of the currency's minor unit — pence for GBP, cents for USD. Zero-decimal currencies such as JPY have a minor unit equal to their major unit, so {"amount": 500, "currency": "JPY"} is ¥500 while {"amount": 500, "currency": "GBP"} is £5.00.

            Floating point is never used for money anywhere in this API. Binary floats cannot represent most decimal fractions exactly, so accumulating them across the line items of an order produces totals that disagree with the printed receipt by a penny. Integer minor units make every arithmetic operation exact.

            • amountinteger · int64

              Required

              The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.

            • currencyCurrency

              Required

              ISO 4217 currency code.

              Determines the minor unit that Money.amount is expressed in. Zero-decimal currencies such as JPY have a minor unit equal to the major unit.

              One of 182 values; see the schema.

          • card_brandstring

            The card's scheme, as the processor reported it.

          • card_last4string

            The last four digits of the card, as the processor reported them.

          • statusstring · enum

            pending — the ask was recorded, the processor has not answered.

            held — the amount is authorised, not taken.

            declined — the processor refused the authorisation.

            unknown — contact was lost; only the processor knows.

            released — the hold was lifted; nothing was taken.

            captured — the fee was taken on a no-show.

            capture_declined — the party did not come and the card did not pay.

            release_failed — the processor refused the release.

            pendinghelddeclinedunknownreleasedcapturedcapture_declinedrelease_failed

        • emailstring · email

          A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

        • ends_atstring · date-time

          When the table stops being held, which is what decides whether anything else can be booked over the table. On a write it may be left to the location's turn time for a party this size; a host who has agreed a long lunch states it.

        • location_idstring · uuid

          Required

          The room the table is in.

        • missedboolean

          Read only

          The server has decided this confirmed party did not come: its end has passed and it was never seated or canceled. Derived, never written — the next real act (seat, cancel or mark no-show) corrects it, so the decision is never a fact a host cannot undo.

        • notestring

          Anything the front of house needs to remember for the visit.

        • occasionstring

          The occasion the booking is for (birthday, anniversary), if the guest named one — carried to the host view and the kitchen ticket (bookings item 24).

        • pacing_overriddenboolean

          Whether a host deliberately took this booking over the service's pacing limit. Recorded so the decision is visible afterwards rather than invisible the moment it is made — a kitchen looking at a swamped service can see which tables were let through and who let them.

        • party_sizeinteger

          Required

          How many people are covered by it.

          ≥ 1

        • phonestring

          A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

        • special_requeststring

          What the guest asked for, in their words. Apart from note, which is what the front of house wants to remember: one is the guest's request and the other is the house's reminder, and folding them together loses which is which when somebody asks whether a request was met.

        • starts_atstring · date-time

          Required

          When the party is expected.

        • statusstring · enum

          Required

          confirmed — the party is expected.

          seated — the party has been shown to its table.

          finished — the seated party has been and gone.

          canceled — the party is not coming; the slot is free again.

          no_show — the party did not arrive and did not cancel.

          confirmedseatedfinishedcanceledno_show

        • table_idstring · uuid

          The table being held, if one was chosen. It is one of table_ids: the primary table, which is the one a host reads off the sheet when a party of two arrives.

        • table_idsarray of string · uuid

          Every table this party holds, including the primary one. A party of twelve across three tables holds three, and the overlap check that stops a double booking runs per table — so table_id alone would let somebody book over the second and third.

          Empty when the booking has no table yet.

        • versioninteger · int64

          Required

          The version the caller last saw. Sent back as If-Match when changing the reservation, so a booking changed on another device is refused rather than overwritten.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  4. POST/v1/bookings/batch-upsert

    Take or replace reservations

    No. batchUpsertBookingsAuth bookings:write

    Takes new reservations and replaces existing ones. One item per booking, judged on its own: a party whose table is already held fails in its own result and every other item still applies.

    The caller mints each id, so a form submitted twice by an impatient host is one booking rather than two, and a retry after a lost response converges on the record that already exists rather than double-booking the table.

    An item with no version takes a new booking; one with a version replaces what it names and is refused if anybody has written since.

    Two refusals worth expecting. A table already held for an overlapping window is a 409 in that item's result, checked per table — a party across three tables is refused if any one of them is taken. And a booking that would put the service over its pacing limit is refused unless the item says override_pacing, which is recorded on the booking so the decision can be seen afterwards.

    Where a booking is in its life is not written here: seat, cancel and a merge patch move it. A host editing a phone number must not be able to un-seat a party with a stale form.

    Parameters

    • Idempotency-Keyheader · string

      Required

      A client-generated key making this request safe to retry.

      Required on every mutation that creates a resource or moves money. A till on bad wifi cannot tell a lost response from a lost request; without a key its only options are to risk a duplicate sale or to drop a real one. With a key it retries freely: the first request to arrive is processed, and every replay returns that same stored response.

      Rules:

      • Mint the key once, when the operation is first queued, and reuse it for every retry. A fresh key per attempt defeats the mechanism entirely.
      • Keys are scoped per merchant and per operation, and are retained for 30 days — long enough that a till switched off mid-sync can retry with the same key however long it stayed on the shelf.
      • Replaying a key with a different request body is a client bug and is rejected with 422, rather than silently returning the first result — quietly serving the old response would hide a real inconsistency in the caller.

      A UUIDv7 or ULID is a good choice.

    Request body application/json · BookingBatchUpsertRequest

    • itemsarray of BookingBatchUpsertItem

      Required

      ≥ 1 items · ≤ 200 items

      • bookingBookingWrite

        Required

        A reservation as a caller states it.

        The id is the caller's, minted as a UUIDv7 before the request is sent, so a form submitted twice by an impatient host is one booking rather than two and a retry after a lost response converges on the record that already exists.

        status is not here. Where a booking is in its life is moved by the acts that move it — seat, cancel, and a merge patch for finished or no_show — never by restating the whole resource: a host editing a party's phone number must not be able to un-seat them by sending a stale form.

        • idstring · uuid

          Required

          UUIDv7, minted by the caller.

        • namestring

          Required

          The name to take it under.

          1–200 chars

        • customer_idstring · uuid

          The directory guest this is for, if one is chosen. When set, the name, phone and email on the resource are read fresh from the guest's record rather than copied here, so a guest who changes their number does not have to be edited on every future booking.

        • emailstring · email

          A contact for the party, and what a confirmation is sent to. Ignored when customer_id is set.

        • ends_atstring · date-time

          When the table stops being held.

          Absent leaves it to the location's turn time for a party this size — so a caller need not know how long a shop gives a table of four. A location that sets no turn time has nothing to fall back on, and the write is then refused naming that rather than guessing at an hour and a half: how long a table is held decides what else can be booked over it.

          Before starts_at is refused.

        • location_idstring · uuid

          Required

          The room the table is in. A location of another merchant is refused.

        • notestring

          Anything the front of house needs to remember for the visit.

          ≤ 2000 chars

        • occasionstring

          What the booking is for — a birthday, an anniversary — carried to the host view and the kitchen ticket.

          ≤ 100 chars

        • override_pacingboolean

          Take the booking even though the service is already seating as many covers as it will at that time. A host on the phone to a regular may decide that; it is recorded on the booking as pacing_overridden so the decision can be seen afterwards.

          A guest booking through the ordering site is never offered a slot that needs this, so a caller acting for a guest should leave it alone.

        • party_sizeinteger

          Required

          How many people it covers.

          1–200

        • phonestring

          A contact for the party. Ignored when customer_id is set — the guest's record holds it.

          ≤ 32 chars

        • special_requeststring

          What the guest asked for, in their words.

          ≤ 2000 chars

        • starts_atstring · date-time

          Required

          When the party is expected.

        • table_idstring · uuid

          The primary table to hold, if one is chosen. A booking with no table is taken and seated onto one later, which is how a host takes a reservation before knowing the floor.

        • table_idsarray of string · uuid

          Every table the party holds, for a booking spread across more than one. table_id is included if it is set; a caller may send either.

          ≤ 20 items

      • idempotency_keyItemIdempotencyKey

        Required

        A key for one item in a batch, minted by the client when it queues the item and reused on every attempt to send it. The item, not the request, is the unit of work: a retried batch returns the stored result for every item already applied under its key. A fresh key per attempt defeats the whole mechanism.

        Scoped per merchant, so keys need only be unique within a merchant's own traffic.

        16–255 chars

      • versioninteger · int64

        The version the caller last read, for a replacement. Absent or 0 means a new booking; a mismatch is refused in this item's result and every other item still applies.

        ≥ 0

    Responses

    • 200

      The batch was processed. results says what happened to each item.

      BatchResponse · application/json
      • resultsarray of BatchResult

        Required

        • idstring · uuid

          The resource the item created or changed. Absent when it failed.

        • idempotency_keystring

          Required

          The item's key, echoed so a result can be matched by key as well as position.

        • problemProblem

          Nullable

          Why the item failed, as an RFC 9457 problem. A 422 means the item is malformed and will never succeed — drop it and show it to staff rather than retrying for ever. A 409 or a 5xx means try again later.

          • typestring · uri

            Required

            A stable URI identifying the problem kind. This is the field to branch on.

            default about:blank

          • api_versionstring

            Present on an api-version-retired or api-version-unknown refusal: the API-Version this server speaks (docs/decisions/0011).

          • detailstring

            A human-readable explanation specific to this occurrence. Safe to show to a developer; never contains internal state, SQL, or stack traces.

          • instancestring · uri-reference

            A URI reference identifying the specific occurrence.

          • invalid_paramsarray of InvalidParam

            Present on 422 responses. Lists every field that failed validation, rather than only the first, so a client can highlight an entire form in one pass.

            • codestring

              Stable machine-readable code for this failure, for clients that localise their own messages rather than showing reason.

            • pointerstring

              Required

              RFC 6901 JSON Pointer to the offending value within the request body. A pointer rather than a field name so that failures nested inside arrays — the third line item of an order — are addressable at all.

            • reasonstring

              Required

              Why this value was rejected, in human-readable form.

          • refusalsarray of Location refusal

            Present on 409 responses to a withdrawal refused by the state it depends on (deleting a location with tills still assigned, open orders, …). Lists every reason at once, each with how much stands in the way, so a client can say what to put right and link to it rather than walking a person through one refusal after another.

            • kindstring

              Required

              A stable refusal kind — devices, open_orders, drawer_sessions, bookings or stock — that a client links to the screen that puts it right. Branch on this field, never on the surrounding prose.

            • countinteger

              Required

              How many things stand in the way of the withdrawal, so a client can repeat the number rather than re-read prose.

              ≥ 1

          • statusinteger

            Required

            The HTTP status code, repeated here so the body is self-contained.

            100–599

          • titlestring

            Required

            A short, human-readable summary of the problem kind.

          • trace_idstring

            The OpenTelemetry trace id for this request. Quote it in a support ticket and the whole request can be pulled up.

        • statusstring · enum

          Required

          created — the item made a new resource. updated — it changed an existing one.

          unchanged — this key was already applied and the original outcome stands. A success, not an error: it is what a client retrying a batch it was unsure landed should expect, and it clears the item from its queue exactly as created does.

          failed — the item was refused, and nothing else in the batch was affected. See problem.

          createdupdatedunchangedfailed

        • versioninteger · int64

          The resource's version after the item. Absent when it failed.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 415

      The request Content-Type is not supported. This API accepts application/json on all mutations.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  5. GET/v1/bookings/{id}

    Get a reservation

    No. getBookingAuth bookings:read

    Returns one reservation.

    GET /v1/bookings has always answered a window — which is right for a host stand backfilling a day, and no use to a caller holding a booking's id from a confirmation email, a webhook or a batch result. This is that read.

    The party's phone and email are returned only to an owner, a manager or an integration holding a bookings scope; a till and a staff member seat a party without them.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    Responses

    • 200

      The reservation.

      Booking · application/json
      • idstring · uuid

        Required

        The reservation's own id, minted when it is taken.

      • namestring

        Required

        The name it was taken under.

      • customer_idstring · uuid

        The directory guest this reservation is for, if one was chosen. When set, the name, phone and email shown on the resource are the guest's from the directory, read fresh: the guest's record is authoritative, never a copy taken when the booking was made. A booking taken by name alone has no customer_id and keeps its own details.

      • depositBookingDeposit

        The card-not-present authorisation held against a booking (bookings item 13), when one has been taken: an amount held on a card, released when the party comes or the booking is cancelled, captured as the fee when it does not.

        • amountMoney

          A monetary amount in a specific currency: Square's Money, and Stripe's amount beside its currency.

          The amount is always an integer count of the currency's minor unit — pence for GBP, cents for USD. Zero-decimal currencies such as JPY have a minor unit equal to their major unit, so {"amount": 500, "currency": "JPY"} is ¥500 while {"amount": 500, "currency": "GBP"} is £5.00.

          Floating point is never used for money anywhere in this API. Binary floats cannot represent most decimal fractions exactly, so accumulating them across the line items of an order produces totals that disagree with the printed receipt by a penny. Integer minor units make every arithmetic operation exact.

          • amountinteger · int64

            Required

            The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.

          • currencyCurrency

            Required

            ISO 4217 currency code.

            Determines the minor unit that Money.amount is expressed in. Zero-decimal currencies such as JPY have a minor unit equal to the major unit.

            One of 182 values; see the schema.

        • card_brandstring

          The card's scheme, as the processor reported it.

        • card_last4string

          The last four digits of the card, as the processor reported them.

        • statusstring · enum

          pending — the ask was recorded, the processor has not answered.

          held — the amount is authorised, not taken.

          declined — the processor refused the authorisation.

          unknown — contact was lost; only the processor knows.

          released — the hold was lifted; nothing was taken.

          captured — the fee was taken on a no-show.

          capture_declined — the party did not come and the card did not pay.

          release_failed — the processor refused the release.

          pendinghelddeclinedunknownreleasedcapturedcapture_declinedrelease_failed

      • emailstring · email

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • ends_atstring · date-time

        When the table stops being held, which is what decides whether anything else can be booked over the table. On a write it may be left to the location's turn time for a party this size; a host who has agreed a long lunch states it.

      • location_idstring · uuid

        Required

        The room the table is in.

      • missedboolean

        Read only

        The server has decided this confirmed party did not come: its end has passed and it was never seated or canceled. Derived, never written — the next real act (seat, cancel or mark no-show) corrects it, so the decision is never a fact a host cannot undo.

      • notestring

        Anything the front of house needs to remember for the visit.

      • occasionstring

        The occasion the booking is for (birthday, anniversary), if the guest named one — carried to the host view and the kitchen ticket (bookings item 24).

      • pacing_overriddenboolean

        Whether a host deliberately took this booking over the service's pacing limit. Recorded so the decision is visible afterwards rather than invisible the moment it is made — a kitchen looking at a swamped service can see which tables were let through and who let them.

      • party_sizeinteger

        Required

        How many people are covered by it.

        ≥ 1

      • phonestring

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • special_requeststring

        What the guest asked for, in their words. Apart from note, which is what the front of house wants to remember: one is the guest's request and the other is the house's reminder, and folding them together loses which is which when somebody asks whether a request was met.

      • starts_atstring · date-time

        Required

        When the party is expected.

      • statusstring · enum

        Required

        confirmed — the party is expected.

        seated — the party has been shown to its table.

        finished — the seated party has been and gone.

        canceled — the party is not coming; the slot is free again.

        no_show — the party did not arrive and did not cancel.

        confirmedseatedfinishedcanceledno_show

      • table_idstring · uuid

        The table being held, if one was chosen. It is one of table_ids: the primary table, which is the one a host reads off the sheet when a party of two arrives.

      • table_idsarray of string · uuid

        Every table this party holds, including the primary one. A party of twelve across three tables holds three, and the overlap check that stops a double booking runs per table — so table_id alone would let somebody book over the second and third.

        Empty when the booking has no table yet.

      • versioninteger · int64

        Required

        The version the caller last saw. Sent back as If-Match when changing the reservation, so a booking changed on another device is refused rather than overwritten.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  6. PATCH/v1/bookings/{id}

    Change a reservation

    No. updateBookingAuth bookings:write

    Applies a JSON Merge Patch (RFC 7396) to a reservation, guarded by If-Match.

    Send only what changes. The patch is refused with 412 if the booking has moved since the version you read — which for a booking is not a formality: two hosts on two phones editing the same party is the ordinary case, and last-write-wins would quietly lose one of them.

    status may be moved to finished or no_show here. Seating and cancelling have their own operations because they do more than set a field: seating opens the party's tab on its table in the same transaction, and cancelling frees the table and may tell the guest.

    Moving a party to a table somebody else holds at that time is refused with 409, per table.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Request body application/merge-patch+json · BookingPatch

    • namestring

      1–200 chars

    • emailstring · email
    • ends_atstring · date-time
    • notestring

      ≤ 2000 chars

    • occasionstring

      ≤ 100 chars

    • party_sizeinteger

      1–200

    • phonestring

      ≤ 32 chars

    • special_requeststring

      ≤ 2000 chars

    • starts_atstring · date-time
    • statusstring · enum

      finished — the party has eaten and gone, and the table is free. no_show — they never arrived, which is a different fact from a cancellation and is worth keeping apart: one is the guest telling you, the other is you finding out.

      finishedno_show

    • table_idstring · uuid

      Nullable

      Move the party to another table, or null to take the table away and seat them later.

    • table_idsarray of string · uuid

      Replace the set of tables the party holds.

      ≤ 20 items

    Responses

    • 200

      The changed reservation.

      Booking · application/json
      • idstring · uuid

        Required

        The reservation's own id, minted when it is taken.

      • namestring

        Required

        The name it was taken under.

      • customer_idstring · uuid

        The directory guest this reservation is for, if one was chosen. When set, the name, phone and email shown on the resource are the guest's from the directory, read fresh: the guest's record is authoritative, never a copy taken when the booking was made. A booking taken by name alone has no customer_id and keeps its own details.

      • depositBookingDeposit

        The card-not-present authorisation held against a booking (bookings item 13), when one has been taken: an amount held on a card, released when the party comes or the booking is cancelled, captured as the fee when it does not.

        • amountMoney

          A monetary amount in a specific currency: Square's Money, and Stripe's amount beside its currency.

          The amount is always an integer count of the currency's minor unit — pence for GBP, cents for USD. Zero-decimal currencies such as JPY have a minor unit equal to their major unit, so {"amount": 500, "currency": "JPY"} is ¥500 while {"amount": 500, "currency": "GBP"} is £5.00.

          Floating point is never used for money anywhere in this API. Binary floats cannot represent most decimal fractions exactly, so accumulating them across the line items of an order produces totals that disagree with the printed receipt by a penny. Integer minor units make every arithmetic operation exact.

          • amountinteger · int64

            Required

            The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.

          • currencyCurrency

            Required

            ISO 4217 currency code.

            Determines the minor unit that Money.amount is expressed in. Zero-decimal currencies such as JPY have a minor unit equal to the major unit.

            One of 182 values; see the schema.

        • card_brandstring

          The card's scheme, as the processor reported it.

        • card_last4string

          The last four digits of the card, as the processor reported them.

        • statusstring · enum

          pending — the ask was recorded, the processor has not answered.

          held — the amount is authorised, not taken.

          declined — the processor refused the authorisation.

          unknown — contact was lost; only the processor knows.

          released — the hold was lifted; nothing was taken.

          captured — the fee was taken on a no-show.

          capture_declined — the party did not come and the card did not pay.

          release_failed — the processor refused the release.

          pendinghelddeclinedunknownreleasedcapturedcapture_declinedrelease_failed

      • emailstring · email

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • ends_atstring · date-time

        When the table stops being held, which is what decides whether anything else can be booked over the table. On a write it may be left to the location's turn time for a party this size; a host who has agreed a long lunch states it.

      • location_idstring · uuid

        Required

        The room the table is in.

      • missedboolean

        Read only

        The server has decided this confirmed party did not come: its end has passed and it was never seated or canceled. Derived, never written — the next real act (seat, cancel or mark no-show) corrects it, so the decision is never a fact a host cannot undo.

      • notestring

        Anything the front of house needs to remember for the visit.

      • occasionstring

        The occasion the booking is for (birthday, anniversary), if the guest named one — carried to the host view and the kitchen ticket (bookings item 24).

      • pacing_overriddenboolean

        Whether a host deliberately took this booking over the service's pacing limit. Recorded so the decision is visible afterwards rather than invisible the moment it is made — a kitchen looking at a swamped service can see which tables were let through and who let them.

      • party_sizeinteger

        Required

        How many people are covered by it.

        ≥ 1

      • phonestring

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • special_requeststring

        What the guest asked for, in their words. Apart from note, which is what the front of house wants to remember: one is the guest's request and the other is the house's reminder, and folding them together loses which is which when somebody asks whether a request was met.

      • starts_atstring · date-time

        Required

        When the party is expected.

      • statusstring · enum

        Required

        confirmed — the party is expected.

        seated — the party has been shown to its table.

        finished — the seated party has been and gone.

        canceled — the party is not coming; the slot is free again.

        no_show — the party did not arrive and did not cancel.

        confirmedseatedfinishedcanceledno_show

      • table_idstring · uuid

        The table being held, if one was chosen. It is one of table_ids: the primary table, which is the one a host reads off the sheet when a party of two arrives.

      • table_idsarray of string · uuid

        Every table this party holds, including the primary one. A party of twelve across three tables holds three, and the overlap check that stops a double booking runs per table — so table_id alone would let somebody book over the second and third.

        Empty when the booking has no table yet.

      • versioninteger · int64

        Required

        The version the caller last saw. Sent back as If-Match when changing the reservation, so a booking changed on another device is refused rather than overwritten.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 409

      The request conflicts with current state — a uniqueness constraint, or an attempt to mutate an order that is already completed or voided.

      Unlike 412, re-reading and retrying unchanged will not resolve it; the request itself has to change.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 415

      The request Content-Type is not supported. This API accepts application/json on all mutations.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  7. DELETE/v1/bookings/{id}

    Remove a reservation

    No. deleteBookingAuth bookings:write

    Removes a reservation and publishes a tombstone to the devices at its location, guarded by If-Match.

    Prefer POST /v1/bookings/{id}/cancel for a party that is not coming: a cancellation is a fact about the service — it is what the no-show rate is measured against, and what tells you a table was freed rather than never booked. Removing is for a booking that should not exist at all, a test entry or one taken against the wrong merchant's sheet.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Responses

    • 204

      The reservation was removed.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 409

      The request conflicts with current state — a uniqueness constraint, or an attempt to mutate an order that is already completed or voided.

      Unlike 412, re-reading and retrying unchanged will not resolve it; the request itself has to change.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  8. POST/v1/bookings/{id}/cancel

    Cancel a reservation

    No. cancelBookingAuth bookings:write

    Marks a reservation canceled and frees the tables it held, guarded by If-Match.

    An act rather than a field assignment, for the same reason seating is one: cancelling releases the table for the rest of the service and is the fact a no-show rate is measured against. A booking already canceled is a replay and returns it as it is.

    Apart from DELETE /v1/bookings/{id}, which removes the record. A cancellation is kept, because "this table was booked and freed" and "this table was never booked" are different things to anybody looking at a service afterwards.

    A till may cancel only a booking at its own location.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Responses

    • 200

      The canceled booking.

      Booking · application/json
      • idstring · uuid

        Required

        The reservation's own id, minted when it is taken.

      • namestring

        Required

        The name it was taken under.

      • customer_idstring · uuid

        The directory guest this reservation is for, if one was chosen. When set, the name, phone and email shown on the resource are the guest's from the directory, read fresh: the guest's record is authoritative, never a copy taken when the booking was made. A booking taken by name alone has no customer_id and keeps its own details.

      • depositBookingDeposit

        The card-not-present authorisation held against a booking (bookings item 13), when one has been taken: an amount held on a card, released when the party comes or the booking is cancelled, captured as the fee when it does not.

        • amountMoney

          A monetary amount in a specific currency: Square's Money, and Stripe's amount beside its currency.

          The amount is always an integer count of the currency's minor unit — pence for GBP, cents for USD. Zero-decimal currencies such as JPY have a minor unit equal to their major unit, so {"amount": 500, "currency": "JPY"} is ¥500 while {"amount": 500, "currency": "GBP"} is £5.00.

          Floating point is never used for money anywhere in this API. Binary floats cannot represent most decimal fractions exactly, so accumulating them across the line items of an order produces totals that disagree with the printed receipt by a penny. Integer minor units make every arithmetic operation exact.

          • amountinteger · int64

            Required

            The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.

          • currencyCurrency

            Required

            ISO 4217 currency code.

            Determines the minor unit that Money.amount is expressed in. Zero-decimal currencies such as JPY have a minor unit equal to the major unit.

            One of 182 values; see the schema.

        • card_brandstring

          The card's scheme, as the processor reported it.

        • card_last4string

          The last four digits of the card, as the processor reported them.

        • statusstring · enum

          pending — the ask was recorded, the processor has not answered.

          held — the amount is authorised, not taken.

          declined — the processor refused the authorisation.

          unknown — contact was lost; only the processor knows.

          released — the hold was lifted; nothing was taken.

          captured — the fee was taken on a no-show.

          capture_declined — the party did not come and the card did not pay.

          release_failed — the processor refused the release.

          pendinghelddeclinedunknownreleasedcapturedcapture_declinedrelease_failed

      • emailstring · email

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • ends_atstring · date-time

        When the table stops being held, which is what decides whether anything else can be booked over the table. On a write it may be left to the location's turn time for a party this size; a host who has agreed a long lunch states it.

      • location_idstring · uuid

        Required

        The room the table is in.

      • missedboolean

        Read only

        The server has decided this confirmed party did not come: its end has passed and it was never seated or canceled. Derived, never written — the next real act (seat, cancel or mark no-show) corrects it, so the decision is never a fact a host cannot undo.

      • notestring

        Anything the front of house needs to remember for the visit.

      • occasionstring

        The occasion the booking is for (birthday, anniversary), if the guest named one — carried to the host view and the kitchen ticket (bookings item 24).

      • pacing_overriddenboolean

        Whether a host deliberately took this booking over the service's pacing limit. Recorded so the decision is visible afterwards rather than invisible the moment it is made — a kitchen looking at a swamped service can see which tables were let through and who let them.

      • party_sizeinteger

        Required

        How many people are covered by it.

        ≥ 1

      • phonestring

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • special_requeststring

        What the guest asked for, in their words. Apart from note, which is what the front of house wants to remember: one is the guest's request and the other is the house's reminder, and folding them together loses which is which when somebody asks whether a request was met.

      • starts_atstring · date-time

        Required

        When the party is expected.

      • statusstring · enum

        Required

        confirmed — the party is expected.

        seated — the party has been shown to its table.

        finished — the seated party has been and gone.

        canceled — the party is not coming; the slot is free again.

        no_show — the party did not arrive and did not cancel.

        confirmedseatedfinishedcanceledno_show

      • table_idstring · uuid

        The table being held, if one was chosen. It is one of table_ids: the primary table, which is the one a host reads off the sheet when a party of two arrives.

      • table_idsarray of string · uuid

        Every table this party holds, including the primary one. A party of twelve across three tables holds three, and the overlap check that stops a double booking runs per table — so table_id alone would let somebody book over the second and third.

        Empty when the booking has no table yet.

      • versioninteger · int64

        Required

        The version the caller last saw. Sent back as If-Match when changing the reservation, so a booking changed on another device is refused rather than overwritten.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 409

      The request conflicts with current state — a uniqueness constraint, or an attempt to mutate an order that is already completed or voided.

      Unlike 412, re-reading and retrying unchanged will not resolve it; the request itself has to change.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  9. POST/v1/bookings/{id}/deposit

    Take a booking deposit

    No. holdBookingDepositAuth bookings:write

    Holds a card-not-present authorisation against the reservation (bookings item 13), for the amount named. The card is keyed here and forwarded — its number is never stored by the API, never written down and never returned. One deposit per booking, under an id derived from the booking, so a retry asks that same authorisation again rather than taking a second one.

    What releases or captures it is the party's visit: seating or cancelling releases the hold, marking a no-show captures it as the fee, and a booking taken off the book releases it. A chargeback or refund of the fee is not this contract's — it is the payment that took it.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Request body application/json · BookingDepositRequest

    • amountMoney

      Required

      A monetary amount in a specific currency: Square's Money, and Stripe's amount beside its currency.

      The amount is always an integer count of the currency's minor unit — pence for GBP, cents for USD. Zero-decimal currencies such as JPY have a minor unit equal to their major unit, so {"amount": 500, "currency": "JPY"} is ¥500 while {"amount": 500, "currency": "GBP"} is £5.00.

      Floating point is never used for money anywhere in this API. Binary floats cannot represent most decimal fractions exactly, so accumulating them across the line items of an order produces totals that disagree with the printed receipt by a penny. Integer minor units make every arithmetic operation exact.

      • amountinteger · int64

        Required

        The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.

      • currencyCurrency

        Required

        ISO 4217 currency code.

        Determines the minor unit that Money.amount is expressed in. Zero-decimal currencies such as JPY have a minor unit equal to the major unit.

        One of 182 values; see the schema.

    • cardCard details

      Required

      A card keyed in rather than tokenised: what somebody reads off a card and types at the till or over the phone. The details travel to the processor the payment account names and are never stored by the API — no card number is written down, logged or returned (one of card and card_details is required on a charge; a card read by a terminal or a guest page arrives as a token instead).

      • cvcstring

        Required

        The card verification code on the back.

        3–4 chars

      • expiry_monthinteger

        Required

        The month the card expires, 1-12.

        1–12

      • expiry_yearinteger

        Required

        The year the card expires, as two or four digits (26 or 2026).

        ≥ 0

      • holder_namestring

        The name as it appears on the card, when the processor wants it.

        ≤ 255 chars

      • numberstring

        Required

        The card number, digits only; the server checks Luhn before the processor is asked.

        12–19 chars

    Responses

    • 200

      The booking, carrying its deposit.

      Booking · application/json
      • idstring · uuid

        Required

        The reservation's own id, minted when it is taken.

      • namestring

        Required

        The name it was taken under.

      • customer_idstring · uuid

        The directory guest this reservation is for, if one was chosen. When set, the name, phone and email shown on the resource are the guest's from the directory, read fresh: the guest's record is authoritative, never a copy taken when the booking was made. A booking taken by name alone has no customer_id and keeps its own details.

      • depositBookingDeposit

        The card-not-present authorisation held against a booking (bookings item 13), when one has been taken: an amount held on a card, released when the party comes or the booking is cancelled, captured as the fee when it does not.

        • amountMoney

          A monetary amount in a specific currency: Square's Money, and Stripe's amount beside its currency.

          The amount is always an integer count of the currency's minor unit — pence for GBP, cents for USD. Zero-decimal currencies such as JPY have a minor unit equal to their major unit, so {"amount": 500, "currency": "JPY"} is ¥500 while {"amount": 500, "currency": "GBP"} is £5.00.

          Floating point is never used for money anywhere in this API. Binary floats cannot represent most decimal fractions exactly, so accumulating them across the line items of an order produces totals that disagree with the printed receipt by a penny. Integer minor units make every arithmetic operation exact.

          • amountinteger · int64

            Required

            The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.

          • currencyCurrency

            Required

            ISO 4217 currency code.

            Determines the minor unit that Money.amount is expressed in. Zero-decimal currencies such as JPY have a minor unit equal to the major unit.

            One of 182 values; see the schema.

        • card_brandstring

          The card's scheme, as the processor reported it.

        • card_last4string

          The last four digits of the card, as the processor reported them.

        • statusstring · enum

          pending — the ask was recorded, the processor has not answered.

          held — the amount is authorised, not taken.

          declined — the processor refused the authorisation.

          unknown — contact was lost; only the processor knows.

          released — the hold was lifted; nothing was taken.

          captured — the fee was taken on a no-show.

          capture_declined — the party did not come and the card did not pay.

          release_failed — the processor refused the release.

          pendinghelddeclinedunknownreleasedcapturedcapture_declinedrelease_failed

      • emailstring · email

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • ends_atstring · date-time

        When the table stops being held, which is what decides whether anything else can be booked over the table. On a write it may be left to the location's turn time for a party this size; a host who has agreed a long lunch states it.

      • location_idstring · uuid

        Required

        The room the table is in.

      • missedboolean

        Read only

        The server has decided this confirmed party did not come: its end has passed and it was never seated or canceled. Derived, never written — the next real act (seat, cancel or mark no-show) corrects it, so the decision is never a fact a host cannot undo.

      • notestring

        Anything the front of house needs to remember for the visit.

      • occasionstring

        The occasion the booking is for (birthday, anniversary), if the guest named one — carried to the host view and the kitchen ticket (bookings item 24).

      • pacing_overriddenboolean

        Whether a host deliberately took this booking over the service's pacing limit. Recorded so the decision is visible afterwards rather than invisible the moment it is made — a kitchen looking at a swamped service can see which tables were let through and who let them.

      • party_sizeinteger

        Required

        How many people are covered by it.

        ≥ 1

      • phonestring

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • special_requeststring

        What the guest asked for, in their words. Apart from note, which is what the front of house wants to remember: one is the guest's request and the other is the house's reminder, and folding them together loses which is which when somebody asks whether a request was met.

      • starts_atstring · date-time

        Required

        When the party is expected.

      • statusstring · enum

        Required

        confirmed — the party is expected.

        seated — the party has been shown to its table.

        finished — the seated party has been and gone.

        canceled — the party is not coming; the slot is free again.

        no_show — the party did not arrive and did not cancel.

        confirmedseatedfinishedcanceledno_show

      • table_idstring · uuid

        The table being held, if one was chosen. It is one of table_ids: the primary table, which is the one a host reads off the sheet when a party of two arrives.

      • table_idsarray of string · uuid

        Every table this party holds, including the primary one. A party of twelve across three tables holds three, and the overlap check that stops a double booking runs per table — so table_id alone would let somebody book over the second and third.

        Empty when the booking has no table yet.

      • versioninteger · int64

        Required

        The version the caller last saw. Sent back as If-Match when changing the reservation, so a booking changed on another device is refused rather than overwritten.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 409

      The request conflicts with current state — a uniqueness constraint, or an attempt to mutate an order that is already completed or voided.

      Unlike 412, re-reading and retrying unchanged will not resolve it; the request itself has to change.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  10. POST/v1/bookings/{id}/seat

    Seat a booking from the till

    No. seatBookingAuth bookings:write

    Opens the party's tab and marks the booking seated in one transaction, exactly as the back office seats one (item 6). The tab is idempotent under a key derived from the booking id, so a retry returns the same tab rather than opening a second one, and it reaches this till's floor through the change feed like any tab opened here. A booking already seated is a replay and returns it as it is.

    A till may seat only a booking at its own location: a device nominating another branch's reservation is refused.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Responses

    • 200

      The seated booking.

      Booking · application/json
      • idstring · uuid

        Required

        The reservation's own id, minted when it is taken.

      • namestring

        Required

        The name it was taken under.

      • customer_idstring · uuid

        The directory guest this reservation is for, if one was chosen. When set, the name, phone and email shown on the resource are the guest's from the directory, read fresh: the guest's record is authoritative, never a copy taken when the booking was made. A booking taken by name alone has no customer_id and keeps its own details.

      • depositBookingDeposit

        The card-not-present authorisation held against a booking (bookings item 13), when one has been taken: an amount held on a card, released when the party comes or the booking is cancelled, captured as the fee when it does not.

        • amountMoney

          A monetary amount in a specific currency: Square's Money, and Stripe's amount beside its currency.

          The amount is always an integer count of the currency's minor unit — pence for GBP, cents for USD. Zero-decimal currencies such as JPY have a minor unit equal to their major unit, so {"amount": 500, "currency": "JPY"} is ¥500 while {"amount": 500, "currency": "GBP"} is £5.00.

          Floating point is never used for money anywhere in this API. Binary floats cannot represent most decimal fractions exactly, so accumulating them across the line items of an order produces totals that disagree with the printed receipt by a penny. Integer minor units make every arithmetic operation exact.

          • amountinteger · int64

            Required

            The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.

          • currencyCurrency

            Required

            ISO 4217 currency code.

            Determines the minor unit that Money.amount is expressed in. Zero-decimal currencies such as JPY have a minor unit equal to the major unit.

            One of 182 values; see the schema.

        • card_brandstring

          The card's scheme, as the processor reported it.

        • card_last4string

          The last four digits of the card, as the processor reported them.

        • statusstring · enum

          pending — the ask was recorded, the processor has not answered.

          held — the amount is authorised, not taken.

          declined — the processor refused the authorisation.

          unknown — contact was lost; only the processor knows.

          released — the hold was lifted; nothing was taken.

          captured — the fee was taken on a no-show.

          capture_declined — the party did not come and the card did not pay.

          release_failed — the processor refused the release.

          pendinghelddeclinedunknownreleasedcapturedcapture_declinedrelease_failed

      • emailstring · email

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • ends_atstring · date-time

        When the table stops being held, which is what decides whether anything else can be booked over the table. On a write it may be left to the location's turn time for a party this size; a host who has agreed a long lunch states it.

      • location_idstring · uuid

        Required

        The room the table is in.

      • missedboolean

        Read only

        The server has decided this confirmed party did not come: its end has passed and it was never seated or canceled. Derived, never written — the next real act (seat, cancel or mark no-show) corrects it, so the decision is never a fact a host cannot undo.

      • notestring

        Anything the front of house needs to remember for the visit.

      • occasionstring

        The occasion the booking is for (birthday, anniversary), if the guest named one — carried to the host view and the kitchen ticket (bookings item 24).

      • pacing_overriddenboolean

        Whether a host deliberately took this booking over the service's pacing limit. Recorded so the decision is visible afterwards rather than invisible the moment it is made — a kitchen looking at a swamped service can see which tables were let through and who let them.

      • party_sizeinteger

        Required

        How many people are covered by it.

        ≥ 1

      • phonestring

        A contact for the party, if given. Returned only to an owner, a manager or an integration holding a bookings scope; a till or a staff member seats the party without it.

      • special_requeststring

        What the guest asked for, in their words. Apart from note, which is what the front of house wants to remember: one is the guest's request and the other is the house's reminder, and folding them together loses which is which when somebody asks whether a request was met.

      • starts_atstring · date-time

        Required

        When the party is expected.

      • statusstring · enum

        Required

        confirmed — the party is expected.

        seated — the party has been shown to its table.

        finished — the seated party has been and gone.

        canceled — the party is not coming; the slot is free again.

        no_show — the party did not arrive and did not cancel.

        confirmedseatedfinishedcanceledno_show

      • table_idstring · uuid

        The table being held, if one was chosen. It is one of table_ids: the primary table, which is the one a host reads off the sheet when a party of two arrives.

      • table_idsarray of string · uuid

        Every table this party holds, including the primary one. A party of twelve across three tables holds three, and the overlap check that stops a double booking runs per table — so table_id alone would let somebody book over the second and third.

        Empty when the booking has no table yet.

      • versioninteger · int64

        Required

        The version the caller last saw. Sent back as If-Match when changing the reservation, so a booking changed on another device is refused rather than overwritten.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 409

      The request conflicts with current state — a uniqueness constraint, or an attempt to mutate an order that is already completed or voided.

      Unlike 412, re-reading and retrying unchanged will not resolve it; the request itself has to change.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  11. GET/v1/sms-settings

    Read the SMS settings

    No. getSmsSettingsAuth bookings:read

    Whether the merchant texts its guests about their bookings, and who from.

    Responses

    • 200

      The SMS settings.

      SMSSettings · application/json
      • availableboolean

        RequiredRead only

        Whether this deployment can send texts at all.

      • enabledboolean

        Required

        Whether a booking's confirmation and reminder are texted.

      • senderstring

        Required

        Who the texts come from: a number in international format, or up to eleven letters and digits. Empty uses the platform's number. A guest can reply to a number, not to a name.

        ≤ 16 chars

      • versionVersion

        RequiredRead only

        Optimistic concurrency version, raised by the server on every write. A version is never lower than one already returned for the resource, though it may rise by more than one.

        Returned in the ETag response header as well as in the body. Send it back in If-Match on an update and the write is rejected with 412 if anyone else has written since — which is what stops two tills silently overwriting each other's edit to the same product when both come back online at once.

        ≥ 1

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  12. PATCH/v1/sms-settings

    Change the SMS settings

    No. updateSmsSettingsAuth bookings:write

    Applies a JSON Merge Patch (RFC 7396) to the SMS settings, guarded by If-Match on the version read. An owner or a manager, or an integration holding bookings:write.

    Parameters

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Request body application/merge-patch+json · SMSSettingsPatch

    • enabledboolean
    • senderstring

      ≤ 16 chars

    Responses

    • 200

      The changed SMS settings.

      SMSSettings · application/json
      • availableboolean

        RequiredRead only

        Whether this deployment can send texts at all.

      • enabledboolean

        Required

        Whether a booking's confirmation and reminder are texted.

      • senderstring

        Required

        Who the texts come from: a number in international format, or up to eleven letters and digits. Empty uses the platform's number. A guest can reply to a number, not to a name.

        ≤ 16 chars

      • versionVersion

        RequiredRead only

        Optimistic concurrency version, raised by the server on every write. A version is never lower than one already returned for the resource, though it may rise by more than one.

        Returned in the ETag response header as well as in the body. Send it back in If-Match on an update and the write is rejected with 412 if anyone else has written since — which is what stops two tills silently overwriting each other's edit to the same product when both come back online at once.

        ≥ 1

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 415

      The request Content-Type is not supported. This API accepts application/json on all mutations.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  13. GET/v1/table-blocks

    List table blocks

    No. listTableBlocksAuth bookings:read

    Returns the blocks that have not been lifted, soonest first, optionally for one table.

    Parameters

    • cursorquery · string

      Opaque cursor from a previous page's next_cursor. Omit to start from the beginning.

      Pass it back exactly as received. It is base64 and its structure is not part of this contract — decoding it, or constructing one by hand, will break without notice.

    • limitquery · integer · int32

      Maximum items to return. The server may return fewer, so always drive the loop from has_more rather than from the size of the page you got back.

    • table_idquery · string · uuid

      Only this table's blocks.

    Responses

    • 200

      A page of table blocks.

      TableBlockPage · application/json
      • has_moreboolean

        Required

      • itemsarray of TableBlock

        Required

        • idstring · uuid

          Required

        • created_atTimestamp

          RequiredRead only

          Server-assigned RFC 3339 timestamp, always UTC.

          Server-assigned because device clocks drift and cannot be trusted for ordering — a till that has been powered off for a week may come back with a clock hours out. Where the device's own view of time matters it is carried separately in client_created_at.

        • ends_atstring · date-time

          Required

          After starts_at, and at most a year later.

        • location_idstring · uuid

          RequiredRead only

          The table's location.

        • reasonstring

          Required

          1–200 chars

        • starts_atstring · date-time

          Required

        • table_idstring · uuid

          Required

        • versionVersion

          RequiredRead only

          Optimistic concurrency version, raised by the server on every write. A version is never lower than one already returned for the resource, though it may rise by more than one.

          Returned in the ETag response header as well as in the body. Send it back in If-Match on an update and the write is rejected with 412 if anyone else has written since — which is what stops two tills silently overwriting each other's edit to the same product when both come back online at once.

          ≥ 1

      • next_cursorstring

        Nullable

        Opaque cursor for the next page, or null on the last page. Pass it back verbatim as the cursor query parameter; its contents are not part of this contract.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  14. POST/v1/table-blocks/batch-create

    Block tables

    No. batchCreateTableBlocksAuth bookings:write

    Places blocks at client-minted ids. A block is never edited: lift it and place another. One that overlaps a booking or another block is judged with them, and refused in its own result. Each item carries its own idempotency_key and stands or falls on its own.

    Request body application/json · TableBlockBatchCreateRequest

    • itemsarray of TableBlockBatchCreateItem

      Required

      ≥ 1 items · ≤ 500 items

      • idempotency_keyItemIdempotencyKey

        Required

        A key for one item in a batch, minted by the client when it queues the item and reused on every attempt to send it. The item, not the request, is the unit of work: a retried batch returns the stored result for every item already applied under its key. A fresh key per attempt defeats the whole mechanism.

        Scoped per merchant, so keys need only be unique within a merchant's own traffic.

        16–255 chars

      • table_blockTableBlock

        Required

        A window when a table is not offered, with why: a repair, a private party. Nothing may be booked over it, and a host reads its reason.

        • idstring · uuid

          Required

        • ends_atstring · date-time

          Required

          After starts_at, and at most a year later.

        • reasonstring

          Required

          1–200 chars

        • starts_atstring · date-time

          Required

        • table_idstring · uuid

          Required

    Responses

    • 200

      The batch was processed. results says what happened to each item.

      BatchResponse · application/json
      • resultsarray of BatchResult

        Required

        • idstring · uuid

          The resource the item created or changed. Absent when it failed.

        • idempotency_keystring

          Required

          The item's key, echoed so a result can be matched by key as well as position.

        • problemProblem

          Nullable

          Why the item failed, as an RFC 9457 problem. A 422 means the item is malformed and will never succeed — drop it and show it to staff rather than retrying for ever. A 409 or a 5xx means try again later.

          • typestring · uri

            Required

            A stable URI identifying the problem kind. This is the field to branch on.

            default about:blank

          • api_versionstring

            Present on an api-version-retired or api-version-unknown refusal: the API-Version this server speaks (docs/decisions/0011).

          • detailstring

            A human-readable explanation specific to this occurrence. Safe to show to a developer; never contains internal state, SQL, or stack traces.

          • instancestring · uri-reference

            A URI reference identifying the specific occurrence.

          • invalid_paramsarray of InvalidParam

            Present on 422 responses. Lists every field that failed validation, rather than only the first, so a client can highlight an entire form in one pass.

            • codestring

              Stable machine-readable code for this failure, for clients that localise their own messages rather than showing reason.

            • pointerstring

              Required

              RFC 6901 JSON Pointer to the offending value within the request body. A pointer rather than a field name so that failures nested inside arrays — the third line item of an order — are addressable at all.

            • reasonstring

              Required

              Why this value was rejected, in human-readable form.

          • refusalsarray of Location refusal

            Present on 409 responses to a withdrawal refused by the state it depends on (deleting a location with tills still assigned, open orders, …). Lists every reason at once, each with how much stands in the way, so a client can say what to put right and link to it rather than walking a person through one refusal after another.

            • kindstring

              Required

              A stable refusal kind — devices, open_orders, drawer_sessions, bookings or stock — that a client links to the screen that puts it right. Branch on this field, never on the surrounding prose.

            • countinteger

              Required

              How many things stand in the way of the withdrawal, so a client can repeat the number rather than re-read prose.

              ≥ 1

          • statusinteger

            Required

            The HTTP status code, repeated here so the body is self-contained.

            100–599

          • titlestring

            Required

            A short, human-readable summary of the problem kind.

          • trace_idstring

            The OpenTelemetry trace id for this request. Quote it in a support ticket and the whole request can be pulled up.

        • statusstring · enum

          Required

          created — the item made a new resource. updated — it changed an existing one.

          unchanged — this key was already applied and the original outcome stands. A success, not an error: it is what a client retrying a batch it was unsure landed should expect, and it clears the item from its queue exactly as created does.

          failed — the item was refused, and nothing else in the batch was affected. See problem.

          createdupdatedunchangedfailed

        • versioninteger · int64

          The resource's version after the item. Absent when it failed.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 415

      The request Content-Type is not supported. This API accepts application/json on all mutations.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  15. DELETE/v1/table-blocks/{id}

    Lift a table block

    No. deleteTableBlockAuth bookings:write

    Lifts a block from the version read, so the table is offered again. The block is kept as lifted, not erased.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Responses

    • 204

      The block is lifted.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  16. GET/v1/waitlist

    List the queue at a location

    No. getWaitlistAuth bookings:read

    Returns the front-of-house queue at one location. A till uses this to see who is waiting and seat the next party; the back office manages the same queue.

    Waitlist entries are server-authoritative: the front of house records them and the change feed publishes them (scoped to the entry's location), so a till's host stand holds the queue offline. This list is for refreshing a device that missed changes.

    A till sees only its own location's queue, and asking for another location is forbidden.

    Parameters

    • location_idquery · string · uuid

      Only this branch's queue. A till may name only its own.

    Responses

    • 200

      The queue.

      WaitlistPage · application/json
      • itemsarray of WaitlistEntry

        Required

        • idstring · uuid

          Required

          The queue entry's own id, minted when the party was taken.

        • namestring

          Required

          The name the party was taken under.

        • created_atstring · date-time

          RequiredRead only

        • customer_idstring · uuid

          The directory guest this party is, if one was matched.

        • location_idstring · uuid

          Required

          The room the party is waiting at.

        • notestring

          Anything the front of house needs to remember for the visit.

        • party_sizeinteger

          Required

          How many people are in the party.

          ≥ 1

        • phonestring

          A contact for the party, so the host can call them back when a table frees.

        • statusstring · enum

          Required

          waiting — the party is in the queue.

          seated — the party has been shown to its table; its tab is open.

          left — the party gave up or was not seated.

          waitingseatedleft

        • updated_atstring · date-time

          Read only

        • versioninteger · int64

          Required

          The version the caller last saw, sent back as If-Match when changing it.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  17. POST/v1/waitlist

    Add a party to the queue

    No. createWaitlistEntryAuth bookings:write

    Records a walk-in party on the queue at the caller's location. A till takes a party at its own site; the server records the entry and publishes it on the change feed, so the host stand holds it offline.

    Request body application/json · WaitlistEntryCreate

    • idstring · uuid

      Minted by the caller, so a party added again after a lost response is answered with the entry it already made rather than queued twice. The server mints one when absent.

    • namestring

      Required

      The name the party is taken under.

    • customer_idstring · uuid

      The directory guest this party is, if one was matched.

    • location_idstring · uuid

      The room the party is waiting at. A till may name only its own.

    • notestring

      Anything the front of house needs to remember for the visit.

    • party_sizeinteger

      Required

      How many people are in the party.

      ≥ 1

    • phonestring

      A contact for the party, so the host can call them back.

    Responses

    • 201

      The party added to the queue.

      WaitlistEntry · application/json
      • idstring · uuid

        Required

        The queue entry's own id, minted when the party was taken.

      • namestring

        Required

        The name the party was taken under.

      • created_atstring · date-time

        RequiredRead only

      • customer_idstring · uuid

        The directory guest this party is, if one was matched.

      • location_idstring · uuid

        Required

        The room the party is waiting at.

      • notestring

        Anything the front of house needs to remember for the visit.

      • party_sizeinteger

        Required

        How many people are in the party.

        ≥ 1

      • phonestring

        A contact for the party, so the host can call them back when a table frees.

      • statusstring · enum

        Required

        waiting — the party is in the queue.

        seated — the party has been shown to its table; its tab is open.

        left — the party gave up or was not seated.

        waitingseatedleft

      • updated_atstring · date-time

        Read only

      • versioninteger · int64

        Required

        The version the caller last saw, sent back as If-Match when changing it.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  18. PATCH/v1/waitlist/{id}

    Mark a waiting party as gone

    No. updateWaitlistEntryAuth bookings:write

    Applies a JSON Merge Patch (RFC 7396) to a queue entry, guarded by If-Match. Its one use is marking a party that walked away as left: a party is seated with seat, which opens its tab, and nothing else moves it.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Request body application/merge-patch+json · WaitlistEntryPatch

    • statusstring · enum

      A party that walked away before it was seated.

      left

    Responses

    • 200

      The changed queue entry.

      WaitlistEntry · application/json
      • idstring · uuid

        Required

        The queue entry's own id, minted when the party was taken.

      • namestring

        Required

        The name the party was taken under.

      • created_atstring · date-time

        RequiredRead only

      • customer_idstring · uuid

        The directory guest this party is, if one was matched.

      • location_idstring · uuid

        Required

        The room the party is waiting at.

      • notestring

        Anything the front of house needs to remember for the visit.

      • party_sizeinteger

        Required

        How many people are in the party.

        ≥ 1

      • phonestring

        A contact for the party, so the host can call them back when a table frees.

      • statusstring · enum

        Required

        waiting — the party is in the queue.

        seated — the party has been shown to its table; its tab is open.

        left — the party gave up or was not seated.

        waitingseatedleft

      • updated_atstring · date-time

        Read only

      • versioninteger · int64

        Required

        The version the caller last saw, sent back as If-Match when changing it.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 415

      The request Content-Type is not supported. This API accepts application/json on all mutations.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  19. DELETE/v1/waitlist/{id}

    Take a party off the queue

    No. deleteWaitlistEntryAuth bookings:write

    Removes a queue entry added in error, guarded by If-Match. The removal is recorded, with who made it; a party that walked away is marked left instead.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Responses

    • 204

      The party is off the queue.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  20. POST/v1/waitlist/{id}/seat

    Seat a waitlist party from the till

    No. seatWaitlistEntryAuth bookings:write

    Seats the waiting party onto the table the host chose and opens its tab in one transaction, exactly as the back office seats one (item 11). The tab is idempotent under a key derived from the entry id, so a retry returns the same tab rather than opening a second one, and it reaches this till's floor through the change feed like any tab opened here. A party already seated is a replay and returns it as it is.

    A till may seat only a party at its own location: a device nominating another branch's party is refused.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    • If-Matchheader · string

      Required

      The ETag from the read that produced the value you are updating.

      Required on updates, not optional, because "last write wins" is the wrong default for a catalogue two managers might edit at once. If anything has been written since your read, the request is rejected with 412 and you re-read and re-apply.

      Use * to mean "write regardless of current version" — an explicit, greppable override rather than the silent default.

    Request body application/json · WaitlistSeatRequest

    • table_idstring · uuid

      Required

      The table to seat the party at.

    Responses

    • 200

      The seated queue entry.

      WaitlistEntry · application/json
      • idstring · uuid

        Required

        The queue entry's own id, minted when the party was taken.

      • namestring

        Required

        The name the party was taken under.

      • created_atstring · date-time

        RequiredRead only

      • customer_idstring · uuid

        The directory guest this party is, if one was matched.

      • location_idstring · uuid

        Required

        The room the party is waiting at.

      • notestring

        Anything the front of house needs to remember for the visit.

      • party_sizeinteger

        Required

        How many people are in the party.

        ≥ 1

      • phonestring

        A contact for the party, so the host can call them back when a table frees.

      • statusstring · enum

        Required

        waiting — the party is in the queue.

        seated — the party has been shown to its table; its tab is open.

        left — the party gave up or was not seated.

        waitingseatedleft

      • updated_atstring · date-time

        Read only

      • versioninteger · int64

        Required

        The version the caller last saw, sent back as If-Match when changing it.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 409

      The request conflicts with current state — a uniqueness constraint, or an attempt to mutate an order that is already completed or voided.

      Unlike 412, re-reading and retrying unchanged will not resolve it; the request itself has to change.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 412

      The If-Match version did not match the resource's current version: someone else wrote since you read.

      This is the expected, healthy outcome of two clients editing the same record — not an error condition to suppress. Re-read the resource, re-apply your change on top of the new state, and retry with the fresh ETag.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.