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

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

POS · Reference

Table plan

A restaurant's floor plan. Server-authoritative — devices pull tables and park open tabs on them.

Operations
13
Base
https://api.tenvelo.com
  1. GET/v1/table-areas

    List table areas

    No. listTableAreasAuth pos:tables:read

    Returns a page of a location's table areas, in plan order.

    Devices should not call this to sync — use listChanges, which returns only what has changed. This is the dashboard's floor-plan read.

    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.

    • location_idquery · string · uuid

      Only areas at this location.

    Responses

    • 200

      A page of table areas.

      TableAreaPage · application/json
      • has_moreboolean

        Required

        Whether another page follows.

      • itemsarray of TableArea

        Required

        • idstring · uuid

          Required

        • namestring

          Required

          1–255 chars

        • 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.

        • location_idstring · uuid

          Required

          The room or site this area groups tables in.

        • merchant_idstring · uuid

          RequiredRead only

        • sort_orderinteger · int32

          Required

          The position of the area on the plan, so the floor renders the way it was arranged.

          ≥ 0 · default 0

        • updated_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.

        • 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.

    • 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.

  2. POST/v1/table-areas

    Create a table area

    No. createTableAreaAuth pos:tables:write

    Adds a section to a location's floor plan.

    Table areas are server-authoritative — this is a back-office operation, not something a till performs. A new area reaches devices through the change feed.

    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 · TableArea

    • idstring · uuid

      Required

    • namestring

      Required

      1–255 chars

    • location_idstring · uuid

      Required

      The room or site this area groups tables in.

    • sort_orderinteger · int32

      Required

      The position of the area on the plan, so the floor renders the way it was arranged.

      ≥ 0 · default 0

    Responses

    • 201

      The area was created.

      TableArea · application/json
      • idstring · uuid

        Required

      • namestring

        Required

        1–255 chars

      • 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.

      • location_idstring · uuid

        Required

        The room or site this area groups tables in.

      • merchant_idstring · uuid

        RequiredRead only

      • sort_orderinteger · int32

        Required

        The position of the area on the plan, so the floor renders the way it was arranged.

        ≥ 0 · default 0

      • updated_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.

      • 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.

    • 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.

    • 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.

  3. GET/v1/table-areas/{id}

    Get a table area

    No. getTableAreaAuth pos:tables:read

    Returns a single area.

    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 area.

      TableArea · application/json
      • idstring · uuid

        Required

      • namestring

        Required

        1–255 chars

      • 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.

      • location_idstring · uuid

        Required

        The room or site this area groups tables in.

      • merchant_idstring · uuid

        RequiredRead only

      • sort_orderinteger · int32

        Required

        The position of the area on the plan, so the floor renders the way it was arranged.

        ≥ 0 · default 0

      • updated_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.

      • 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.

    • 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.

  4. PATCH/v1/table-areas/{id}

    Change a table area

    No. updateTableAreaAuth pos:tables:write

    Applies a JSON Merge Patch (RFC 7396) to an area, guarded by If-Match. Send only the fields that change.

    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 · TableAreaPatch

    • namestring

      The area's new name.

      1–255 chars

    • sort_orderinteger · int32

      The area's new position on the plan.

      ≥ 0

    Responses

    • 200

      The changed area.

      TableArea · application/json
      • idstring · uuid

        Required

      • namestring

        Required

        1–255 chars

      • 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.

      • location_idstring · uuid

        Required

        The room or site this area groups tables in.

      • merchant_idstring · uuid

        RequiredRead only

      • sort_orderinteger · int32

        Required

        The position of the area on the plan, so the floor renders the way it was arranged.

        ≥ 0 · default 0

      • updated_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.

      • 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.

    • 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.

  5. DELETE/v1/table-areas/{id}

    Delete a table area

    No. deleteTableAreaAuth pos:tables:write

    Soft-deletes an area and emits a deleted change so devices remove it from their floor plans.

    The row is retained rather than erased, because tables may reference it and should not be orphaned. Tables already in the area keep pointing at it; a caller unassigns them first.

    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 area was deleted.

    • 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.

  6. GET/v1/table-qr-codes

    List table QR codes

    No. listTableQRCodesAuth pos:tables:write

    Returns the QR codes issued for a location's tables, for printing a sheet of them. A table with no code issued is not listed. A code lets a guest order to its table, so only the people who may change the floor plan read them.

    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.

    • location_idquery · string · uuid

      Required

      The location whose tables' codes are listed.

    Responses

    • 200

      A page of table QR codes.

      TableQRCodePage · application/json
      • has_moreboolean

        Required

      • itemsarray of TableQRCode

        Required

        • table_idstring · uuid

          Required

        • table_namestring

          Required

        • urlstring · uri

          Required

          What the code encodes. A new code makes the old one stop working.

      • 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.

  7. GET/v1/tables

    List tables

    No. listTablesAuth pos:tables:read

    Returns the merchant's tables, cursor page first.

    Devices should not call this to sync — use listChanges, which returns only what has changed. This is the dashboard's floor-plan read.

    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.

    • location_idquery · string · uuid

      Only tables at this location.

    Responses

    • 200

      A page of tables.

      TablePage · application/json
      • has_moreboolean

        Required

        Whether another page follows.

      • itemsarray of Table

        Required

        • idstring · uuid

          Required

        • namestring

          Required

          1–255 chars

        • capacityinteger

          Required

          The number of covers it seats.

          ≥ 1

        • 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.

        • location_idstring · uuid

          Required

          The room or site this table stands in. A table is only offered to devices at its own location, so a table at one branch never appears on another till.

        • merchant_idstring · uuid

          RequiredRead only

        • statusstring · enum

          An inactive table stops appearing in the floor plan but is never deleted, because open tabs may reference it.

          activeinactive

        • table_area_idstring · uuid

          Nullable

          The section of this location's floor the table is grouped under, or null if it stands in no area. The area must be in the table's own location.

        • updated_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.

        • 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

        • xinteger

          Nullable

          The table's position on the plan, from the origin; null until the floor is designed. x and y are set together.

          ≥ 0

        • yinteger

          Nullable

          The table's position on the plan; null until the floor is designed. x and y are set together.

          ≥ 0

      • 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.

    • 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/tables

    Create a table

    No. createTableAuth pos:tables:write

    Adds a table to a location's floor plan.

    Tables are server-authoritative — this is a back-office operation, not something a till performs. The new table reaches devices through the change feed.

    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 · Table

    • idstring · uuid

      Required

    • namestring

      Required

      1–255 chars

    • capacityinteger

      Required

      The number of covers it seats.

      ≥ 1

    • location_idstring · uuid

      Required

      The room or site this table stands in. A table is only offered to devices at its own location, so a table at one branch never appears on another till.

    • statusstring · enum

      An inactive table stops appearing in the floor plan but is never deleted, because open tabs may reference it.

      activeinactive

    • table_area_idstring · uuid

      Nullable

      The section of this location's floor the table is grouped under, or null if it stands in no area. The area must be in the table's own location.

    • xinteger

      Nullable

      The table's position on the plan, from the origin; null until the floor is designed. x and y are set together.

      ≥ 0

    • yinteger

      Nullable

      The table's position on the plan; null until the floor is designed. x and y are set together.

      ≥ 0

    Responses

    • 201

      The table was created.

      Table · application/json
      • idstring · uuid

        Required

      • namestring

        Required

        1–255 chars

      • capacityinteger

        Required

        The number of covers it seats.

        ≥ 1

      • 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.

      • location_idstring · uuid

        Required

        The room or site this table stands in. A table is only offered to devices at its own location, so a table at one branch never appears on another till.

      • merchant_idstring · uuid

        RequiredRead only

      • statusstring · enum

        An inactive table stops appearing in the floor plan but is never deleted, because open tabs may reference it.

        activeinactive

      • table_area_idstring · uuid

        Nullable

        The section of this location's floor the table is grouped under, or null if it stands in no area. The area must be in the table's own location.

      • updated_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.

      • 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

      • xinteger

        Nullable

        The table's position on the plan, from the origin; null until the floor is designed. x and y are set together.

        ≥ 0

      • yinteger

        Nullable

        The table's position on the plan; null until the floor is designed. x and y are set together.

        ≥ 0

    • 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.

    • 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.

    • 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.

  9. GET/v1/tables/{id}

    Get a table

    No. getTableAuth pos:tables:read

    Returns a single 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.

    Responses

    • 200

      The table.

      Table · application/json
      • idstring · uuid

        Required

      • namestring

        Required

        1–255 chars

      • capacityinteger

        Required

        The number of covers it seats.

        ≥ 1

      • 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.

      • location_idstring · uuid

        Required

        The room or site this table stands in. A table is only offered to devices at its own location, so a table at one branch never appears on another till.

      • merchant_idstring · uuid

        RequiredRead only

      • statusstring · enum

        An inactive table stops appearing in the floor plan but is never deleted, because open tabs may reference it.

        activeinactive

      • table_area_idstring · uuid

        Nullable

        The section of this location's floor the table is grouped under, or null if it stands in no area. The area must be in the table's own location.

      • updated_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.

      • 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

      • xinteger

        Nullable

        The table's position on the plan, from the origin; null until the floor is designed. x and y are set together.

        ≥ 0

      • yinteger

        Nullable

        The table's position on the plan; null until the floor is designed. x and y are set together.

        ≥ 0

    • 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.

  10. PATCH/v1/tables/{id}

    Change a table

    No. updateTableAuth pos:tables:write

    Applies a JSON Merge Patch (RFC 7396) to a table, guarded by If-Match. Send only the fields that change. An occupied table cannot be withdrawn or moved, and a location_id change is refused if the target location is not owned.

    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 · TablePatch

    • namestring

      The table's new name.

      1–255 chars

    • capacityinteger

      The new number of covers it seats.

      ≥ 1

    • location_idstring · uuid

      The location this table now stands in, moving it.

    • statusstring · enum

      Active reappears in the floor plan; inactive withdraws it.

      activeinactive

    • table_area_idstring · uuid

      Nullable

      The area this table is grouped under, or null to place it in no area. The area must be in the table's own location.

    • xinteger

      Nullable

      Where the table stands on the plan, or null to put it back in the grid.

      ≥ 0

    • yinteger

      Nullable

      Where the table stands on the plan, or null to put it back in the grid.

      ≥ 0

    Responses

    • 200

      The updated table.

      Table · application/json
      • idstring · uuid

        Required

      • namestring

        Required

        1–255 chars

      • capacityinteger

        Required

        The number of covers it seats.

        ≥ 1

      • 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.

      • location_idstring · uuid

        Required

        The room or site this table stands in. A table is only offered to devices at its own location, so a table at one branch never appears on another till.

      • merchant_idstring · uuid

        RequiredRead only

      • statusstring · enum

        An inactive table stops appearing in the floor plan but is never deleted, because open tabs may reference it.

        activeinactive

      • table_area_idstring · uuid

        Nullable

        The section of this location's floor the table is grouped under, or null if it stands in no area. The area must be in the table's own location.

      • updated_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.

      • 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

      • xinteger

        Nullable

        The table's position on the plan, from the origin; null until the floor is designed. x and y are set together.

        ≥ 0

      • yinteger

        Nullable

        The table's position on the plan; null until the floor is designed. x and y are set together.

        ≥ 0

    • 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.

  11. DELETE/v1/tables/{id}

    Delete a table

    No. deleteTableAuth pos:tables:write

    Soft-deletes a table and emits a deleted change so devices remove it from their floor plans.

    The row is retained rather than erased, because open tabs may reference it and a tab should not be orphaned by a tidy-up.

    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 table was deleted.

    • 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.

  12. POST/v1/tables/{id}/rotate

    Issue a table's QR code

    No. rotateTableAuth pos:tables:write

    Issues a new QR code for the table. The code before it, if there was one, stops working at once: a code is rotated when a printed one has gone astray.

    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.

    • 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.

    Responses

    • 201

      The new code.

      TableQRCode · application/json
      • table_idstring · uuid

        Required

      • table_namestring

        Required

      • urlstring · uri

        Required

        What the code encodes. A new code makes the old one stop working.

    • 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.

  13. POST/v1/tables/{id}/seat

    Seat a walk-in

    No. seatWalkInAuth pos:tables:write

    Opens a tab for a walk-in on the table the host chose, with the covers the host counted (floor and tables, item 11) — so revenue per cover counts the party, not only bookings and the waitlist. The tab is an open order on the table, its covers on the fact that began it, and the order's id derives from the Idempotency-Key, so a retry seats once. An owner or a manager, or an integration holding pos:tables:write.

    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.

    • 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

    • namestring

      Whose party it is, if they gave a name; nil is an anonymous walk-in.

      ≤ 80 chars

    • coversinteger

      Required

      How many people were seated, the host's count.

      1–100

    Responses

    • 201

      The open tab.

      Order · application/json
      • idstring · uuid

        Required

        UUIDv7, minted by the device before the sale is synced. This is what makes an offline order idempotent: replaying it targets the same id, so a retry over flaky wifi cannot create a duplicate sale.

      • namestring

        Nullable

        Whose sale it is, while it is open: a customer's name, whatever the shop shouts across the bar. Null is a walk-in being served at a counter right now.

        ≤ 128 chars

      • acceptancestring · enum

        Read only

        Where an asked-for order stands with the venue that must make it, beside its lifecycle: state keeps its four values. A till's own order is accepted as it is created; a channel's — the ordering page, an integration — is pending until a person or a rule takes it on, or it is rejected with a reason. Set by the server, never from the body: acceptance is a fact (engineering, recording what happened, section 12), not a field a client sets.

        pendingacceptedrejected

      • age_confirmedboolean

        Whether a sale carrying an age-restricted line recorded that the till confirmed the guest's age before it completed. False both for a sale with no age-restricted line and for one that needed the check and did not record it — the reports compare it with age_minimum to flag the second.

        default false

      • age_minimuminteger · int32

        The oldest buyer the sale's age-restricted lines allowed, as the till recorded it: the highest minimum_age among them, 0 when the sale carried none. A completed sale with age_minimum above 0 and age_confirmed false is a counter law miss.

        ≥ 0 · default 0

      • business_datestring · date

        Read only

        The trading day the sale belongs to, in its location's own reckoning: its time zone and the hour its day starts. Set by the server when the sale completes, from when the till says it happened, so a sale rung at 1am on a late night books to the night before. Absent on an order that has not completed.

      • channelstring · enum

        Read only

        Where the order came from, set by the server from the caller and never accepted from the body: pos is a till, table a table-side device, online an online storefront, integration a third-party integration. A till cannot attribute a sale to another channel. What the channel is tells an integration whose order arrived from where, which an order_type cannot.

        postableonlineintegration

      • channel_costMoney

        What the channel took from the order, at the stated cost in force on the order's business date (online-ordering item 18). Present only for an online order whose channel states a cost, and derived on read from the stated rates and the order's total — never stored, so it can never drift from them.

        • 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.

      • client_created_atstring · date-time

        Required

        When the device believes the sale happened. Trusted for display — it is what a receipt shows and what staff recognise — but never for ordering or for resolving conflicts, because till clocks drift. Compare with received_at to measure how long a device was offline.

      • closed_atstring · date-time

        Nullable

        When the order left open — was completed. A void does not change it; voided_at is when the sale was voided.

      • coversinteger · int32

        RequiredRead only

        How many people the party is, as counted at seating on the created fact; zero where nobody counted them. The covers-per-labour-hour and average-spend-per-cover figures are divided by it, so a tab opened without a count adds no covers rather than one.

        0–1000 · default 0

      • 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.

      • 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.

      • customerCustomerLoyalty

        This order's guest as a till may see them: name, loyalty balance and what it can afford. Carried on the order and its change-feed entry so a till that picks a tab up on another device shows the badge and offers a redemption without a second call. Absent for a walk-in and for a guest since erased or merged; customer_id remains the record. Additive: a client that predates it simply omits it.

        • idstring · uuid

          Required

        • namestring

          Required

        • loyalty_pointsinteger · int64

          The guest's current loyalty balance, earned minus redeemed.

        • rewardsarray of Reward

          The reward catalogue entries this guest can afford, so a till can offer them at redemption. Server-computed from the balance; a guest not enrolled sees none.

          • idstring · uuid

            Required

          • namestring

            Required

            What the reward is called to the guest and on the receipt.

          • cost_pointsinteger · int64

            Required

            What redeeming it costs, in points.

          • discount_idstring · uuid

            Required

            The discount definition the reward is backed by.

        • tagsstring

          The guest's labels, carried so a counter can offer them; still no contact details and no note.

        • tierLoyaltyTierStatus

          A guest's standing in a loyalty programme's ladder at one moment (loyalty item 16): the tier they are on, the next tier above, and how much more they need to reach it.

          • currentLoyaltyTier

            One named level in a loyalty programme's ladder (loyalty item 16). Exactly one of threshold_spend and threshold_visits is set; window_days is the rolling window over which the qualifying spend or visits are counted, and earn_rate_percentage is the multiplier on the programme's base earn rate (10000 is 1x).

            • idstring · uuid

              Required

            • namestring

              Required

            • earn_rate_percentageRelativePercentage

              A percentage that may be negative or above 100, such as a change on a period before or a multiplier, in a decimal string with at most four decimal places: a fall of an eighth is "-12.5", and one and a half times is "150". Read and computed as a Percentage is, in parts per million.

              pattern ^-?(0|[1-9][0-9]{0,8})(\.[0-9]{1,4})?$

            • threshold_spendMoney

              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.

            • threshold_visitsinteger · int64

              The number of completed linked orders that reaches the tier.

            • window_daysinteger · int32
          • nextLoyaltyTier

            One named level in a loyalty programme's ladder (loyalty item 16). Exactly one of threshold_spend and threshold_visits is set; window_days is the rolling window over which the qualifying spend or visits are counted, and earn_rate_percentage is the multiplier on the programme's base earn rate (10000 is 1x).

            • idstring · uuid

              Required

            • namestring

              Required

            • earn_rate_percentageRelativePercentage

              A percentage that may be negative or above 100, such as a change on a period before or a multiplier, in a decimal string with at most four decimal places: a fall of an eighth is "-12.5", and one and a half times is "150". Read and computed as a Percentage is, in parts per million.

              pattern ^-?(0|[1-9][0-9]{0,8})(\.[0-9]{1,4})?$

            • threshold_spendMoney

              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.

            • threshold_visitsinteger · int64

              The number of completed linked orders that reaches the tier.

            • window_daysinteger · int32
          • spend_neededMoney

            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.

          • visits_neededinteger · int64

            The visits still needed to reach next, when next is reached by visits.

        • versioninteger · int64

          Required

          The guest record's version, for the optimistic write a picker makes; it may be stale by the time the write lands, and the batch then refuses.

      • customer_idstring · uuid

        Nullable

        The guest this sale is recorded against, if any. Null for a walk-in.

      • delivery_addressAddress

        Where a delivery order goes, as the writer stated it, when the order's fulfillment is delivery.

        • address_line_1string

          Required

          First line of the address — street number, street name, building name.

          ≤ 255 chars

        • address_line_2string

          Second line of the address, if any.

          ≤ 255 chars

        • address_line_3string

          Third line of the address, if any.

          ≤ 255 chars

        • administrative_district_level_1string

          A civil entity within the country. The state in the US; the county in the UK.

          ≤ 128 chars

        • administrative_district_level_2string

          A civil entity within administrative_district_level_1.

          ≤ 128 chars

        • countryCountry

          Required

          ISO 3166-1 alpha-2 country code.

          ZZ is the unknown/unspecified placeholder.

          One of 250 values; see the schema.

        • localitystring

          The city or town.

          ≤ 128 chars

        • postal_codestring

          The postal or ZIP code.

          ≤ 32 chars

        • sublocalitystring

          A civil region within the locality, if any.

          ≤ 128 chars

      • delivery_feeMoney

        Nullable

        The venue's delivery charge on a delivered order (online-ordering: a shop may charge for delivery), snapshotted from its settings at write, or null when none. Part of the total; never folded into goods or their tax.

        • 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.

      • device_idstring · uuid

        The till that last wrote the order. On an open order this is not ownership — any device at the location may pick a tab up, which is the entire point.

      • device_seqinteger · int64

        Nullable

        The device's monotonic sequence for this write, if the till recorded one.

      • documentOrderDocument

        The fiscal document a completed order is recorded as, in the per-till series 0016 builds. Every sales document a till issues belongs to a series it was issued at enrolment, and the number is a gap-free counter in that series, incremented in the same transaction that completes the order. Printed on the receipt and sent with the order; the server stores it and refuses a duplicate (series, number).

        This is the number a market's fiscal rules are about — the correlative number within a series. It is not the order's reference, which stays the short per-day call number for staff to find a sale.

        • kindstring · enum

          Required

          What the document is. invoice is the ordinary receipt — the simplified invoice most markets call a receipt. A full invoice and a rectifying document will be separate kinds, each in its own series.

          invoice

          default invoice

        • numberinteger · int64

          Required

          The gap-free sequence number within the series, starting at 1. Never reused: a void keeps its number, and a correction is a new document in a rectifying series.

          ≥ 1

        • seriesstring

          Required

          The series this document belongs to: a short code issued to one till at enrolment (e.g. B1-T3), one per device and kind of document. The ordinary receipt is in the invoice series.

          ≤ 32 chars

      • drawer_session_idstring · uuid

        Nullable

        The drawer session this sale was rung in, if any. Null otherwise.

      • email_receiptboolean

        Whether the customer on this order asked for its receipt to be emailed. The address is always the customer record's — never typed onto the order, which is a financial record — and only honoured when the guest has consented. Additive, so a till that predates it simply omits it.

        default false

      • external_referencestring

        Read only

        The writer's own reference for the order, when one was given.

        ≤ 128 chars

      • flagsarray of OrderFlagKind

        Read only

        The kinds of the flags still open on the order, which the books could not settle; absent when none is. Read beside the order as refunded is, and left out of the change feed.

      • fulfillment_statusstring · enum

        Read only

        Where an asked-for order has got to, for the guest and the venue: received until the venue takes it on, preparing once accepted, ready once said to be, collected once completed, or rejected or canceled. Derived from acceptance, ready_at and state when the order is read, never stored, so a client shows it rather than working it out again. Toast's fulfillmentStatus. Absent on a till's own sale, which is not asked for.

        receivedpreparingreadycollectedrejectedcanceled

      • fulfillment_typestring · enum

        Required

        How the sale leaves the building, as the glossary names it and a tax rule is keyed by (docs/decisions/0014): on_premises is what the law calls eat-in, pickup is takeaway and delivery is delivery — Square's fulfillment type, Toast's dining option. A sale that does not say is on premises.

        on_premisespickupdelivery

      • line_itemsarray of OrderLineItem

        Required

        What was sold. A completed order has at least one line; an open order may be empty for a moment, while the tab it is has just been started.

        • idstring · uuid

          Required

          UUIDv7, minted by the device.

        • namestring

          Required

          Product name as sold. Snapshot; never re-read from the product.

          1–255 chars

        • approved_by_memberMemberSnapshot

          Nullable

          The manager or owner who authorised a staff member's discount or comp above the merchant's discount_approval_threshold_minor/discount_approval_threshold_percentage or a comp when comp_requires_manager is set, as they were then; null when no approval was needed.

          • idstring · uuid

            Required

            The roster's id, checked against the merchant's memberships.

          • namestring

            Required

            What the member was called.

            ≥ 1 chars

          • emailstring · email

            Required

            The member's address.

          • rolestring · enum

            Required

            The role the member held.

            ownermanagerstaff

        • category_idstring · uuid

          RequiredNullable

          The category the product was filed under when the line was rung, for reporting. Snapshot: moving the product to another category later moves no sale. Null on an ad-hoc line or an uncategorised product.

        • category_namestring

          Required

          The category's name when the line was rung, so a report of past sales reads as they were filed. Snapshot; empty when category_id is null.

          ≤ 255 chars

        • comp_reasonstring

          Why this line was given away. Present on a comp — an item given away at no charge — and means something different from a discount, so it is reported separately. Required when the line is a comp.

          ≤ 512 chars

        • courseinteger · int32

          Nullable

          The course this line belongs to, in a full-service kitchen — starters, mains, desserts. Set on the till when ringing to a course, and used to hold or fire a course from the till. Null for a single-course counter sale or a course-less line. Held courses are not sent (sent_at null) until they are fired.

          ≥ 1

        • discountMoney

          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.

        • discount_idstring · uuid

          Nullable

          The discount definition applied to this line, for reporting. Null for a line with no discount. Snapshot — the discount is recorded so its use can be attributed even if the definition later changes.

        • discount_namestring

          The discount's name at the time the line was priced, snapshotted on the line — the reason a guest can be told, without re-resolving a definition that may have been renamed or deleted. Empty for a line with no discount.

          ≤ 200 chars

        • gift_card_codestring

          Nullable

          The code on the card being sold, when the line's product is a gift_card — typed or scanned at the till. The server issues the card (or refuses a code already live) when the order completes. Null for any other line.

          ≤ 64 chars

        • gift_card_token_hashstring

          Nullable

          The hash of the balance-page token for the card being sold: SHA-256 over the token's text, in base64url without padding. The device mints the token — 32 random bytes, unpadded base64url — and prints it on the receipt's balance link; only its hash leaves the device, so the token is never on the record of the sale, and the server checks a link by hashing the token it carries (W3C, "Good Practices for Capability URLs"). Absent leaves the card with no balance link until one is made (gift-card-balance-links). Null for any other line.

          43–43 chars · pattern ^[A-Za-z0-9_-]{43}$

        • modifiersarray of OrderLineModifier

          The options chosen on this line, snapshotted at the moment of sale. The line's unit_price already includes each option's delta.

          The server checks only the shape of these — a negative delta is refused, an option chosen twice is refused — and never refuses a sale because the menu has changed since the till synced. A sale that was correct when rung stays correct.

          • idstring · uuid

            Required

            The modifier that was chosen. Kept for reporting, never re-resolved.

          • namestring

            Required

            Option name as sold. Snapshot.

            1–255 chars

          • price_deltaMoney

            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.

        • multibuyMultibuyRule

          Nullable

          The product's multibuy rule as it stood when the line was rung, snapshotted on the line so the server resolves the gross by the same deal the till did — re-pricing a product never rewrites a sale that was correct. Null on a line with no deal. A rule never applies to a weighed line.

          • everyinteger

            Required

            How many units make a run; 3 is the ordinary buy-two-get-one.

            2–1000

          • priceMoney

            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.

        • notestring

          Free text from the till — "oat milk", "no sugar".

          ≤ 512 chars

        • originstring · enum

          Read only

          Where this line was rung from, as the server saw the writer: the till at the counter, a guest's phone on a tab, an ordering channel, an integration. It is the server's word and never the writer's, and it is how the share of a table's spend the table rang itself is told. Absent on a line recorded before origins were.

          tillcustomeronlineintegration

        • product_idstring · uuid

          Nullable

          The catalogue product, for reporting. Null for an ad-hoc line — a miscellaneous item rung up at a custom price, which every real till needs.

        • product_typestring · enum

          Required

          What the product was when the line was rung: a gift_card sold is money taken for a promise, not revenue, and a combo is a set. Square's item_type. Snapshot, copied from the product at the moment of sale; standard on an ad-hoc line.

          standardgift_cardcombo

        • quantityQuantity

          Required

          How much of something, as a decimal string in the unit_of_measure beside it (each where there is none), as Square's quantity is: three croissants are "3", 1.4 kg of beans is "1.4". A string because a JSON number is a binary float to most parsers.

          It has at most as many decimal places as its unit's base unit holds — three in each, g and ml, six in kg and l — and a finer one is refused, never rounded. Tenvelo holds every quantity as an integer of that base unit (thousandths of an each, milligrams, microlitres; docs/decisions/0063); a client that computes with one should read the digits the same way, never through a float.

          ≤ 26 chars · pattern ^-?(0|[1-9][0-9]*)(\.[0-9]{1,6})?$

        • seatinteger · int32

          Nullable

          The seat this line is for, when a table's seats are numbered. Set on the till and carried to the kitchen and to split bills. Null for a counter sale or an unnumbered seat.

          ≥ 1

        • sent_atstring · date-time

          Nullable

          When this line was sent to the kitchen — set by the till when a tab is held or a sale completed, and immutable once set. A kitchen knows what to cook from the lines of a ticket, and a new round on a tab is a new set of sent_at values rather than the old lines re-sent. Null on a line the till has not yet held or completed.

        • serialsarray of string

          The serial numbers of the units this line sold (retail item: serial numbers) — one per unit, in the order they were entered, printed on a product whose track_serial is set. Empty on any other line. A snapshot like the name and price: the record of which physical units left the shelf, so a specific unit sold is traceable to its sale.

          default []

        • taxMoney

          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.

        • tax_breakdownarray of TaxBreakdownItem

          Each rate's share of this line's tax, in the order its rule names the rates, when the line is taxed over several rates (each a separate calculation, each rounding with its own mode). Absent — null — on a line taxed at a single rate, where tax_rate_percentage and tax are the whole story.

          • 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.

          • inclusiveboolean

            Required

            Whether this rate is inside the price (true) or added on top (false).

          • percentagePercentage

            Required

            A share of a whole, from "0" to "100", as a percentage in a decimal string with at most four decimal places, as Square's percentage and Stripe Tax's percentage_decimal are: UK VAT at 20% is "20", and an 8.875% US sales tax is "8.875".

            A string because a JSON number is a binary float to most parsers, and a float cannot hold most decimal fractions exactly (RFC 7493 §2.2). Tenvelo computes with a percentage as an integer in parts per million ("8.875" is 88750), which every four-place percentage is exactly. A client that computes with one should read the digits the same way, never through a float.

            pattern ^(100(\.0{1,4})?|(0|[1-9][0-9]?)(\.[0-9]{1,4})?)$

          • rate_idstring · uuid

            Required

            The rate this share belongs to.

        • tax_inclusiveboolean

          Whether unit_price already contains the tax. Snapshot.

        • tax_rate_percentagePercentage

          A share of a whole, from "0" to "100", as a percentage in a decimal string with at most four decimal places, as Square's percentage and Stripe Tax's percentage_decimal are: UK VAT at 20% is "20", and an 8.875% US sales tax is "8.875".

          A string because a JSON number is a binary float to most parsers, and a float cannot hold most decimal fractions exactly (RFC 7493 §2.2). Tenvelo computes with a percentage as an integer in parts per million ("8.875" is 88750), which every four-place percentage is exactly. A client that computes with one should read the digits the same way, never through a float.

          pattern ^(100(\.0{1,4})?|(0|[1-9][0-9]?)(\.[0-9]{1,4})?)$

        • totalMoney

          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.

        • unit_of_measurestring · enum

          Nullable

          The unit this line was sold in, snapshotted from the product at the moment of sale (the product's own unit on a product line; any unit on an ad-hoc line). Absent means each.

          eachkgglml

        • unit_priceMoney

          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.

        • variant_idstring · uuid

          Nullable

          The variant that was sold, for reporting and stock. Null when the product itself is the sellable unit — a product with no variants, or with none picked. Like product_id, it is retained for reporting and never re-resolved: the descriptive and monetary fields on this line are the snapshot that is replayed, so a sale that was correct when rung stays correct even if the catalogue has changed since.

      • location_idstring · uuid

        Required

      • loyalty_earnedinteger · int64

        Read only

        The points this sale earned its customer, recorded on the order.

      • loyalty_spentinteger · int64

        Read only

        The points this sale redeemed from its customer, recorded on the order.

      • memberMemberSnapshot

        Nullable

        The staff member who recorded this order — the person who rang it up — as they were then; nil when no person was attributed.

        • idstring · uuid

          Required

          The roster's id, checked against the merchant's memberships.

        • namestring

          Required

          What the member was called.

          ≥ 1 chars

        • emailstring · email

          Required

          The member's address.

        • rolestring · enum

          Required

          The role the member held.

          ownermanagerstaff

      • merchant_idstring · uuid

        RequiredRead only

      • owner_memberMemberSnapshot

        Nullable

        The staff member a tab belongs to — who opened it, and whom to ask when nobody has touched it. Checked against the merchant's memberships; nil for an order that was never a tab. Set once at open and only changed through a till's explicit transfer.

        • idstring · uuid

          Required

          The roster's id, checked against the merchant's memberships.

        • namestring

          Required

          What the member was called.

          ≥ 1 chars

        • emailstring · email

          Required

          The member's address.

        • rolestring · enum

          Required

          The role the member held.

          ownermanagerstaff

      • paidMoney

        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.

      • paymentsarray of Payment

        How the sale was paid for. Present on a completed order, and the thing that makes a day's takings reconcilable: an order that says completed without saying how it was paid records that something was sold and nothing about whether any money arrived.

        An open order — a tab being split or part-paid — may also carry payments: a partial payment is appended while the tab stays open, and what remains is derived from the lines minus these payments and never stored. The server refuses a payment larger than what remains, and completes the order in the write whose payments cover the sale.

        • idstring · uuid

          The payment's id. Minted by the device, so a retry of the same tender converges on the same row instead of recording the money twice; the server mints one when a writer does not supply it. A refund names it as payment_id, and a card refund is reversed against it, so it is returned with every read of the order and never renumbered.

        • amountMoney

          Required

          The total taken by this payment, including any tip.

          Following Square: the tender's amount is what the customer parted with, and tip is the portion of it that is not for goods. Modelling it the other way — an amount that excludes the tip — means every reconciliation has to remember to add the two back together, and one place that forgets is a day's takings that do not balance.

          • 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.

        • buyer_suppliedMoney

          Nullable

          What the customer physically handed over. Cash only.

          Recorded rather than assumed so that change is derived from it: a note tendered for a smaller sale is the ordinary case, and the drawer at close must agree with the sum of what went in.

          • 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

          Read only

          The scheme the processor read off the card, in its own words ("visa", "mastercard", "amex"). Card payments only; absent where the processor reports none.

        • card_confirmationstring · enum

          Read only

          How the card payment was confirmed against the checkout the processor says took it: confirmed when the approved checkout's amount matched and it belongs to this sale, unconfirmed when it is not approved, belongs to another sale, or took a different amount. An unconfirmed sale is still recorded — the money may already have moved — but the payment is flagged for a person to reconcile. The server's word, never the till's. Absent for a payment that is not a card, and for a card the till recorded by reference on a third-party terminal, which has no checkout to confirm against.

          confirmedunconfirmed

        • card_last4string

          Read only

          The last four digits of the card that paid, as the processor masked them — what a payment shows as "Visa •• 4242", and what a person types into the order list's search to find the sale. Card payments only; absent where the processor reports none. Never the PAN.

          ≤ 4 chars

        • change_backMoney

          NullableRead only

          Change handed back, calculated by the server as buyer_supplied - amount and never accepted from a client.

          Derived rather than submitted because it is the one figure a till could get wrong in a way that balances on screen and not in the drawer.

          • 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.

        • fingerprintstring

          Read only

          The processor's card-alias token, never the PAN, for a card payment it could identify. It is what lets a returning card be recognised without the card number. Card payments only; absent for cash and vouchers, and for a card payment the processor did not identify.

          ≤ 256 chars

        • gift_card_hold_idstring · uuid

          The hold a till took on the voucher's card before tendering it (batchCreateGiftCardHolds), which this payment settles. Absent for a voucher tendered offline, which is spent against what the till last knew of the card.

        • line_idsarray of string · uuid

          The lines this payment settled, when the till knows the split: a share pays whole lines, so a reopened tab shows the unpaid lines rather than a number, and a mistake cannot charge somebody twice (tabs item 13).

          Empty when the payment named no lines — a whole-sale tender, an even split, a card share settling what is left, and every payment recorded before this existed. Empty and absent mean the same thing and are accepted alike, and a till sends the empty array.

        • memberMemberSnapshot

          Nullable

          The member who took this tender — the terminal operator who took the tip, not whoever rang the sale. The kept tip rule follows it. Absent for a payment recorded by something that is not a person.

          • idstring · uuid

            Required

            The roster's id, checked against the merchant's memberships.

          • namestring

            Required

            What the member was called.

            ≥ 1 chars

          • emailstring · email

            Required

            The member's address.

          • rolestring · enum

            Required

            The role the member held.

            ownermanagerstaff

        • methodstring · enum

          Required

          cash — taken into the drawer. Settled by definition: nothing to capture, nothing that can later fail.

          card — captured through the PSP. Recorded here as what the terminal reported, not as proof of settlement.

          voucher — a gift card or credit note redeemed against the sale.

          account — put on a customer's account to settle later. Requires the sale to name the customer (customer_id) and the merchant to have an open customer account for them; the till chooses the account at the tender and links the sale to its customer (retail: customer accounts).

          A loyalty redemption is not a payment method any more: a reward redeems against its discount definition, the order line carries the discount, and the server spends the guest's points when the order is written. The points method was retired.

          external — money a channel took before the order reached the venue: an online storefront, a delivery platform. The merchant settles with that channel, so the tender records the platform's reference rather than a card payment this merchant never captured. Only valid on an order a channel wrote, and the reference is required.

          cashcardvoucheraccountexternal

        • pinstring

          A voucher's PIN — the scratch-off second factor that proves possession of the card whose code is in reference (gift cards item 20). Required only when the card carries a PIN (a pre-printed or merchant-supplied code); a card with a generated code needs none, and the server refuses a sale whose voucher cannot prove the PIN. Only the hash is stored and compared; the plaintext never comes back.

          ≤ 12 chars

        • referencestring

          The PSP's identifier for a card payment, or a voucher's code. Cash has neither and needs neither. Required for an external tender: it is the platform's own transaction or order reference, which is what reconciliation against that channel relies on.

          ≤ 128 chars

        • tipMoney

          Nullable

          The part of amount that is a gratuity rather than payment for goods.

          Present because hospitality is not an edge case here: a bar or restaurant tab is the core flow, and a till that cannot record a tip cannot be used in one.

          • 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.

      • pickup_atstring · date-time

        Read only

        When the guest was told they could collect. Set by whoever took the order — the ordering page, or a till ringing a takeaway for collection at a time — and kept however the order is written afterwards: a later write that omits it leaves the promised time alone. Absent on an order nobody promised a time for.

      • ready_atstring · date-time

        Read only

        When the venue said an asked-for order was ready for the guest, recorded as a ready fact — the Ready button, the kitchen bumping the order's last ticket, or an integration. Absent until then, and again after a ready_undone takes it back. Read from the facts, never from whether the kitchen has any tickets for the order.

      • ready_to_pay_atstring · date-time

        Read only

        When a guest at the table said they were ready to pay (order-and-pay), on an open order. Absent until they tap; carried on the change feed as an order update, which is how the till shows the call.

      • received_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.

      • referencestring

        Short human-readable reference shown on the receipt and used by staff to find the order. Unique per location per business day.

        ≤ 64 chars

      • refundedMoney

        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.

      • rejection_notestring

        Read only

        The staff member's words, in their own voice, required when the reason is other.

        ≤ 512 chars

      • rejection_reasonstring · enum

        Read only

        Why an asked-for order was turned down, when it was. timed_out is the sweep that rejects an unanswered order past its location's deadline.

        sold_outclosedtoo_busyothertimed_out

      • service_chargeMoney

        Nullable

        The discretionary service charge added to this bill (order-ledger item 9), in the order's currency, or null when none. Never folded into goods; part of the total payments must sum to.

        • 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.

      • service_charge_taxMoney

        Nullable

        The tax on the service charge, its own line and never folded into goods tax_total, or null when the charge is not taxed.

        • 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.

      • service_charge_tax_class_idstring · uuid

        Nullable

        The tax class the service charge was taxed under (decision record 0054), snapshotted from the location's config at write, or null when the charge is not taxed.

      • service_charge_tax_rate_percentagePercentage

        Nullable

        The combined tax rate that applied to the service charge, or null when not taxed.

        pattern ^(100(\.0{1,4})?|(0|[1-9][0-9]?)(\.[0-9]{1,4})?)$

      • sourcestring

        Read only

        Which caller wrote the order — the device id for a pos or table order, storefront for one placed on a venue's ordering page, and the integration client id for an integration one. Set server-side from the caller, and an identifier's provenance, not a name.

      • source_namestring

        Read only

        The name the merchant gave the integration that wrote an integration order, as it was when the order was recorded; empty for any other channel.

      • statestring · enum

        Required

        open is a sale in progress, shared with the devices at its location. completed is finalised and immutable. canceled is an open order that ended without being paid. voided is a completed order reversed. Neither of the last two is deleted, so the audit trail survives.

        opencompletedcanceledvoided

      • table_idstring · uuid

        Nullable

        The table an open order is parked on, if any. Occupancy is derived from this.

      • tipsMoney

        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.

      • totalsOrderTotals

        Required

        The money on an order, broken out.

        These are computed and sent by the device, then independently recomputed by the server on receipt. A mismatch is rejected with 422 rather than quietly corrected: if the till printed a receipt showing one total and the server stored another, the merchant has an unreconcilable discrepancy and no way to know which is right. Failing loudly surfaces a till running stale tax configuration while it is still one order, not a month's worth.

        • cash_roundingMoney

          Nullable

          How much a cash settlement rounded the price, signed (docs/decisions/0015): a €10.02 bill rounded up to €10.05 has cash_rounding 3, and the cash payment's goods portion is the total plus this. The till computes it from the location's cash step; the server recomputes and rejects a mismatch. Zero on a sale no cash settles, and cards are never rounded.

          • 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.

        • discount_totalMoney

          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.

        • service_chargeMoney

          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.

        • service_charge_taxMoney

          Nullable

          The tax on the service charge (decision record 0054), its own line and never folded into goods tax_total. Computed server-side from the location's service-charge tax class and the sale's fulfilment and business date. Null when the charge is not taxed.

          • 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.

        • subtotalMoney

          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.

        • tax_totalMoney

          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.

        • totalMoney

          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.

      • updated_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.

      • 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

      • void_memberMemberSnapshot

        Nullable

        The staff member who voided this order, when it has been voided and the void was made by a person. Nil for a sale that was never voided, or one voided by something that is not a person.

        • idstring · uuid

          Required

          The roster's id, checked against the merchant's memberships.

        • namestring

          Required

          What the member was called.

          ≥ 1 chars

        • emailstring · email

          Required

          The member's address.

        • rolestring · enum

          Required

          The role the member held.

          ownermanagerstaff

      • void_notestring

        The staff member's note on the void, required when the reason is other.

      • void_reasonstring · enum

        Why this sale was voided. Set on a voided order.

        customer_canceledstaff_errorrefundother

      • voided_atstring · date-time

        Nullable

        When the sale was voided.

    • 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.

    • 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.