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

Kitchen

The work the kitchen has been told to make. Read-only and derived from the ledger — there is no write endpoint, and nothing a channel sends creates a ticket.

Operations
5
Base
https://api.tenvelo.com
  1. GET/v1/kitchen-tickets

    List kitchen tickets

    No. listKitchenTicketsAuth kitchen:read

    Returns a page of the work the kitchen has been told to make at one location, oldest sent first, optionally of one order.

    Read-only and derived from the ledger: there is no write endpoint, and no channel ever creates a ticket. A ticket is one round of an order — the lines it sent together — so a counter sale is one ticket and each round a tab holds is its own ticket. Lines an order has not yet sent are not here.

    Parameters

    • location_idquery · string · uuid

      Required

      Only tickets at this location; a ticket is for one room.

    • order_idquery · string · uuid

      Only tickets for this order.

    • station_idquery · string · uuid

      Only tickets routed to this station, for a station's board.

    • cursorquery · string

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

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

    • limitquery · integer · int32

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

    Responses

    • 200

      A page of kitchen tickets.

      KitchenTicketPage · application/json
      • has_moreboolean

        Required

        Whether another page follows.

      • itemsarray of KitchenTicket

        Required

        • kindstring · enum

          What this ticket is. round is the ordinary ticket — make these lines. cancel is a cancellation: a line that was sent to the kitchen was taken back off the tab, so the kitchen is told to stop making it. The board draws a cancellation plainly so it is not read as a fresh order.

          roundcancel

          default round

        • bumped_atstring · date-time

          Nullable

          When this ticket was bumped — the kitchen said it is done. Bump state is kitchen data, not order data, and lives here rather than on the order. Null until bumped.

        • channelstring · enum

          Required

          Where the order came from, set by the server from the caller and never taken from a client: pos is a till, table a table-side device, online a venue's ordering page, integration a third-party integration. The kitchen shows an online or integration order so a pass can tell an outside guest from the room — "two identical coffees" is not enough when one is for a delivery the shop has already been paid for.

          postableonlineintegration

        • customer_namestring

          Nullable

          Whose sale it is — the name a tab was held under, or the guest on the order. Null for a walk-in.

          ≤ 255 chars

        • line_idstring · uuid

          Nullable

          The removed line a cancellation is for — the key it is acknowledged under, so acknowledging it never touches the round it cancels. Null for a round ticket.

        • linesarray of KitchenTicketLine

          Required

          What to make, in the order it was rung.

          • namestring

            Required

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

            1–255 chars

          • allergen_clashesarray of string

            The allergens this line's product carries that clash with what the linked guest declared on their profile. The clash, not the guest's health record, which a kitchen device is not allowed to read. Empty for a walk-in, a product with no allergens, or a guest with none declared.

          • allergens_uncheckedboolean

            True when this line could not be checked against the guest's declared allergies at all — an open item, which carries no product, or a product the catalogue no longer has. The guest declared something; this line was not looked at.

            It exists because an empty allergen_clashes otherwise means two different things, and one of them is dangerous: a cook reading no flag assumes the line was checked and is clean. The till draws the same distinction for the cashier (_allergyWarnings, ui/till_screen.dart); this carries it to the pass.

          • courseinteger · int32

            Nullable

            The course this item belongs to — starters, mains, desserts — so the kitchen times it and knows what is still held. Null for a single-course sale or a course-less line.

            ≥ 1

          • modifiersarray of object

            The options chosen on this line, as names only — the kitchen reads "Oat milk" and "Decaf", not a price delta. In the order the till sent them in.

            • namestring

              Required

              Option name as sold. Snapshot.

              1–255 chars

          • notestring

            Nullable

            Free text from the till — "no nuts", "well done".

            ≤ 512 chars

          • 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 item is for, carried from the order so the kitchen plates to the right place. Null for a counter sale or an unnumbered seat.

            ≥ 1

        • location_idstring · uuid

          Required

          The location the order is in; a ticket is for one room.

        • order_idstring · uuid

          Required

          The order this ticket belongs to.

        • pickup_atstring · date-time

          Nullable

          When a collection order is due, carried from the order's promise so the kitchen can pace the work and tell a waiting guest the truth. Null for an in-house sale or one nobody promised a time for. When the guest was told they could collect, set by the channel that took the order (online-ordering item 13). Null for a dine-in order and for a counter sale — the kitchen knows a requested time only when there is one. This ticket is for collection when it is set.

        • roundinteger · int32

          Required

          Which round of the order this is, one-based in the order they were sent. A tab held twice has two tickets, round 1 and 2; a completed counter sale is a single round.

          ≥ 1

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

        • station_idstring · uuid

          Nullable

          The station this ticket was routed to, when the location has stations configured — a round spanning stations becomes one ticket per station. Null for a kitchen with no stations, or for an un-routed line.

        • station_namestring

          Nullable

          The station's name, as the kitchen knows it.

          ≤ 255 chars

        • table_namestring

          Nullable

          The table the order is on, resolved from the table plan. Null for a counter sale.

          ≤ 255 chars

        • undoable_untilstring · date-time

          NullableRead only

          Until when the bump may still be taken back. Null for a ticket that is not bumped. A board offers the undo until then, and the server refuses it after.

        • undone_atstring · date-time

          Nullable

          When the bump was undone, within the window a bump may be un-bumped (a bumped ticket that was not finished is a lost meal). Presence means the ticket is no longer bumped. Null while bumped.

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

  2. POST/v1/kitchen-tickets/batch-bump

    Bump and un-bump kitchen tickets

    No. batchBumpKitchenTicketsAuth kitchen:write

    Marks kitchen tickets as done (bumped), or un-bumps them within a window. Bump state is kitchen data, not order data: it says the kitchen is done with a ticket and never touches the order.

    A batch of one is how a single ticket is bumped. Each item targets one ticket by (order_id, round), carries its own idempotency_key, and stands or falls on its own: an invalid or refused item fails in its result and every other item is still applied. Bumping an already-bumped ticket is unchanged — a retry converges rather than double-counting. Undo (undo=true) clears a bump, but only within the un-bump window; past it an undo is refused.

    Each bump and undo is recorded as a fact about the ticket, and the result says whether one was. What it does to the board reaches listKitchenTickets and the change feed a moment later, once the fact is applied; the tickets of an online order all bumped mark it ready for the guest in that same step.

    Request body application/json · KitchenTicketBumpRequest

    • itemsarray of KitchenTicketBumpItem

      Required

      ≥ 1 items · ≤ 500 items

      • idempotency_keystring

        Required

        The item's own key; a retried item returns the original outcome.

        1–128 chars

      • line_idstring · uuid

        Nullable

        The removed line a cancellation is acknowledged under, so acknowledging it never touches the round it cancels. Omit for a round ticket (kitchen-display item 9).

      • order_idstring · uuid

        Required

      • roundinteger · int32

        Required

        Which round of the order; a ticket is one round.

        ≥ 1

      • station_idstring · uuid

        Nullable

        The station the ticket was routed to, when the location has stations configured. Omit for a whole-round ticket (a kitchen with no stations, or an un-routed line). A bump is per ticket: bumping the grill's ticket does not bump the fryer's.

      • undoboolean

        true un-bumps the ticket — takes it off the done pile, within the window a bump may be undone. false (or omitted) bumps it. Idempotent either way.

        default false

    Responses

    • 200

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

      BatchResponse · application/json
      • resultsarray of BatchResult

        Required

        • idstring · uuid

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

        • idempotency_keystring

          Required

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

        • problemProblem

          Nullable

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

          • typestring · uri

            Required

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

            default about:blank

          • api_versionstring

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

          • detailstring

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

          • instancestring · uri-reference

            A URI reference identifying the specific occurrence.

          • invalid_paramsarray of InvalidParam

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

            • codestring

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

            • pointerstring

              Required

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

            • reasonstring

              Required

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

          • refusalsarray of Location refusal

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

            • kindstring

              Required

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

            • countinteger

              Required

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

              ≥ 1

          • statusinteger

            Required

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

            100–599

          • titlestring

            Required

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

          • trace_idstring

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

        • statusstring · enum

          Required

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

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

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

          createdupdatedunchangedfailed

        • versioninteger · int64

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

    • 400

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

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

    • 401

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

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

    • 403

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

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

    • 410

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

    • 415

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

    • 422

      The request parsed but failed validation.

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

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

    • 429

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

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

    • 500

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

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

    • 503

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

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

  3. POST/v1/kitchen-tickets/batch-print

    Claim kitchen-ticket prints

    No. batchPrintKitchenTicketsAuth kitchen:write

    Claims that kitchen tickets were printed, so a round reaches a kitchen printer exactly once however many tills carry the location's kitchen printer. A till with a kitchen printer claims the tickets a change brings that no device has printed: the first till to claim a ticket is the one that prints it, and a till that finds it already claimed must not print it again. A till that was offline when an online order arrived prints it on reconnect, because this claim is the marker that hands the print to exactly one device.

    A batch of one is how a single ticket is claimed. Each item targets one ticket by (order_id, round, station_id), carries its own idempotency_key, and stands or falls on its own: an invalid item fails in its result and every other item is still applied. Claiming an already-printed ticket is unchanged — a retry converges rather than double-printing.

    Request body application/json · KitchenTicketPrintRequest

    • itemsarray of KitchenTicketPrintItem

      Required

      ≥ 1 items · ≤ 500 items

      • idempotency_keystring

        Required

        The item's own key; a retried item returns the original outcome.

        1–128 chars

      • order_idstring · uuid

        Required

      • roundinteger · int32

        Required

        Which round of the order; a ticket is one round.

        ≥ 1

      • station_idstring · uuid

        Nullable

        The station the ticket was routed to, when the location has stations configured. Omit for a whole-round ticket (a kitchen with no stations, or an un-routed line). A print is per ticket: printing the grill's ticket does not print the fryer's.

    Responses

    • 200

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

      BatchResponse · application/json
      • resultsarray of BatchResult

        Required

        • idstring · uuid

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

        • idempotency_keystring

          Required

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

        • problemProblem

          Nullable

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

          • typestring · uri

            Required

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

            default about:blank

          • api_versionstring

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

          • detailstring

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

          • instancestring · uri-reference

            A URI reference identifying the specific occurrence.

          • invalid_paramsarray of InvalidParam

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

            • codestring

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

            • pointerstring

              Required

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

            • reasonstring

              Required

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

          • refusalsarray of Location refusal

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

            • kindstring

              Required

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

            • countinteger

              Required

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

              ≥ 1

          • statusinteger

            Required

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

            100–599

          • titlestring

            Required

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

          • trace_idstring

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

        • statusstring · enum

          Required

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

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

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

          createdupdatedunchangedfailed

        • versioninteger · int64

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

    • 400

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

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

    • 401

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

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

    • 403

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

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

    • 410

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

    • 415

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

    • 422

      The request parsed but failed validation.

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

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

    • 429

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

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

    • 500

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

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

    • 503

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

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

  4. GET/v1/ready-board

    Ready board

    No. getReadyBoardAuth kitchen:read

    Returns the orders a venue has told its guests are ready now, at one location, newest ready first within the last two hours — what a guest-facing screen by the pickup counter shows. Read-only and derived from the ledger: the order's ready_at is folded from its readiness facts and written by nothing else, so a screen and a guest's own notice can never disagree about ready. An order ready more than two hours ago is not listed, and one the venue stopped saying ready about is not listed either.

    Parameters

    • location_idquery · string · uuid

      Required

      Only orders at this location; a screen is for one room.

    Responses

    • 200

      The board.

      ReadyBoard · application/json
      • itemsarray of ReadyBoardItem

        Required

        • channelstring

          Required

          Where the order came from — online for one placed for collection.

        • labelstring

          Required

          The name to call out, or the order's own number when the guest named none.

        • order_idstring · uuid

          Required

        • ready_atstring · date-time

          Required

          When the venue last said ready, in the clock that said it.

    • 400

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

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

    • 401

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

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

    • 403

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

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

    • 410

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

    • 429

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

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

    • 500

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

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

    • 503

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

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

  5. GET/v1/reports/kitchen-prep

    Kitchen prep times for a day

    No. getKitchenPrepReportAuth kitchen:read

    One site's finished tickets of one business day, each with the moment it was first printed and the moment its last line was bumped — the print-to-done measure a venue reads to see the kitchen's pace. It is the fold of facts a kitchen screen recorded (a print and a bump), never a measured value a device sent, and a ticket still on the board at the day's end has no row.

    Parameters

    • location_idquery · string · uuid

      Required

      The site whose kitchen is reported.

    • business_datequery · string · date

      Required

      The day, as that site counts it.

    Responses

    • 200

      The day's finished tickets, earliest first.

      KitchenPrepReport · application/json
      • business_datestring · date

        Required

      • location_idstring · uuid

        Required

      • rowsarray of KitchenPrepRow

        Required

        • order_idstring · uuid

          Required

        • prep_minutesinteger · int32

          Required

          Whole minutes from printed_at to ready_at; lower is faster.

        • printed_atstring · date-time

          Required

        • ready_atstring · date-time

          Required

        • roundinteger · int32

          Required

        • station_idstring · uuid

          Required

        • station_namestring

          Required

          The station it was routed to, joined; a whole-round ticket has the empty name.

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