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

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

Payments · Reference

Card payments

Taking a card on a terminal, through whichever processor the merchant's payment account uses. The till asks, polls, and records the result on the order as a card payment.

Operations
15
Base
https://api.tenvelo.com
  1. GET/v1/business-identity

    Get the business identity

    No. getBusinessIdentityAuth business-identity:read

    Who the business is to a card processor: its registration number, its legal address and the people who own it. It names people and their dates of birth, so only the owner reads it.

    Responses

    • 200

      The business identity.

      BusinessIdentity · application/json
      • addressAddress

        RequiredNullable

        The business's legal address, or null while it is not given.

        • 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

      • beneficial_ownersarray of BeneficialOwner

        Required

        ≤ 20 items

        • namestring

          Required

          1–200 chars

        • addressAddress

          RequiredNullable

          Where the person lives, or null while it is not given.

          • 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

        • date_of_birthstring · date

          RequiredNullable

        • percent_ownershipPercentage

          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})?)$

      • registration_numberstring

        RequiredNullable

        The business's registration with its country's company registry, a company number in the UK.

        ≤ 64 chars

      • versionVersion

        RequiredRead only

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

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

        ≥ 1

    • 400

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

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

    • 401

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

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

    • 403

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

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

    • 410

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

    • 429

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

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

    • 500

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

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

  2. PATCH/v1/business-identity

    Change the business identity

    No. updateBusinessIdentityAuth business-identity:write

    Applies a JSON Merge Patch (RFC 7396) to the business identity, guarded by If-Match on the version read. beneficial_owners, when sent, is the whole list: an owner left out is no longer an owner. Only the owner may.

    Parameters

    • If-Matchheader · string

      Required

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

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

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

    Request body application/merge-patch+json · BusinessIdentityPatch

    • addressAddress

      Nullable

      A postal address.

      The address_line_* fields carry the most specific part of the address — street number, street name, building name. Less specific detail belongs in locality, administrative_district_level_* and country.

      The administrative_district_level_* fields are deliberately generic because their meaning is country-dependent: in the US level 1 is the state and level 2 the county; in the UK level 1 is the county.

      • 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

    • beneficial_ownersarray of BeneficialOwner

      ≤ 20 items

      • namestring

        Required

        1–200 chars

      • addressAddress

        RequiredNullable

        Where the person lives, or null while it is not given.

        • 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

      • date_of_birthstring · date

        RequiredNullable

      • percent_ownershipPercentage

        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})?)$

    • registration_numberstring

      Nullable

      ≤ 64 chars

    Responses

    • 200

      The business identity as saved.

      BusinessIdentity · application/json
      • addressAddress

        RequiredNullable

        The business's legal address, or null while it is not given.

        • 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

      • beneficial_ownersarray of BeneficialOwner

        Required

        ≤ 20 items

        • namestring

          Required

          1–200 chars

        • addressAddress

          RequiredNullable

          Where the person lives, or null while it is not given.

          • 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

        • date_of_birthstring · date

          RequiredNullable

        • percent_ownershipPercentage

          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})?)$

      • registration_numberstring

        RequiredNullable

        The business's registration with its country's company registry, a company number in the UK.

        ≤ 64 chars

      • versionVersion

        RequiredRead only

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

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

        ≥ 1

    • 400

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

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

    • 401

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

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

    • 403

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

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

    • 410

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

    • 412

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

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

    • 415

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

    • 422

      The request parsed but failed validation.

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

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

    • 429

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

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

    • 500

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

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

  3. POST/v1/card-not-present-payments

    Charge a card that is not present

    No. createCardNotPresentPaymentAuth terminal-checkouts:write

    Charges a card against an open order, taken from the guest's own phone — the online / pay-at-table flow. No terminal, no card read; the processor the payment account names is asked to authorise it.

    The id is minted by the client. Retrying with the same id returns the charge as it now stands and never takes the card twice.

    A payment larger than what the order still owes is refused with 422 (overpaid), and an order that is already paid is refused with 422. A payment account whose processor cannot take a card-not-present payment is refused with 422 (not_supported).

    On approved, the order carries a card payment with this charge's reference, and a tab is completed when what remains reaches zero.

    Request body application/json · CardNotPresentPaymentRequest

    • idstring · uuid

      Required

      Minted by the client as a UUIDv7; a retry names the same charge.

    • amountMoney

      Required

      What to charge, tip included if the guest added one on the page.

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

    • cardstring

      The processor's token for the card, never a PAN. Exactly one of card and card_details.

      1–256 chars

    • card_detailsCard details

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

      • cvcstring

        Required

        The card verification code on the back.

        3–4 chars

      • expiry_monthinteger

        Required

        The month the card expires, 1-12.

        1–12

      • expiry_yearinteger

        Required

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

        ≥ 0

      • holder_namestring

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

        ≤ 255 chars

      • numberstring

        Required

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

        12–19 chars

    • order_idstring · uuid

      Required

      The open order being paid for, at the caller's merchant.

    • payment_account_idstring · uuid

      The payment account (MID) the payment is taken on, which decides the processor. Omitted for a till keying a card, which does not know which account the merchant configured: the merchant's single account that takes a card-not-present payment is chosen, or the request is refused (not_supported when there is none, ambiguous when several could answer).

    • tipMoney

      Nullable

      A tip the guest added on top of the goods; part of amount.

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

    • tip_policystring · enum

      Whether the guest was offered a tip, as the venue set it.

      offallow

      default off

    Responses

    • 201

      The charge was recorded.

      CardNotPresentPayment · application/json
      • idstring · uuid

        Required

        Minted by the client; a retry names the same charge.

      • amountMoney

        Required

        What was asked for, before any partial approval.

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

      • approvedMoney

        NullableRead only

        What a partial approval actually took, when the processor approved less than amount.

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

      • cardstring

        Read only

        The processor's token for the card. The request's value, echoed so a retry matches.

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

      • fingerprintstring

        Read only

        The processor's card-alias token, never the PAN, so a returning card is recognised.

      • order_idstring · uuid

        Required

        The order the charge was taken against.

      • payment_account_idstring · uuid

        Required

        The payment account (MID) the charge was taken on.

      • reasonstring

        Read only

        The processor's explanation of a decline, for staff to read.

      • referencestring

        Read only

        The processor's reference for the charge, once approved. Recorded on the order's card payment.

        ≤ 128 chars

      • statusstring · enum

        RequiredRead only

        pending — accepted, not yet with the processor. approved — the card was charged. Final. declined — the card was refused. Final; reason says why. unknown — contact was lost after the request left us. The card may have been charged. Retry the same request; it is resolved, never charged twice.

        pendingapproveddeclinedunknown

      • tipMoney

        NullableRead only

        A tip a guest added, on top of amount. The order's payment records amount plus this.

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

      • tip_policystring · enum

        Whether the guest was offered a tip.

        offallow

        default off

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

    • 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/orders/{id}/card-holds

    List the card holds on a tab

    No. getOrderCardHoldsAuth terminal-checkouts:read

    Returns the card holds on an open order, most recent 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.

    Responses

    • 200

      The holds on the order.

      OrderCardHolds · application/json
      • itemsarray of CardHold

        Required

        • idstring · uuid

          Required

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

        • created_atstring · date-time

          Required

        • expires_atstring · date-time

          When the authorisation expires on the processor's terms, if known.

        • fingerprintstring

          Read only

          The processor's card-alias token for the authorised card — never the PAN — so a tab opened on a card can be named from a returning card (tabs item 14). The server records it from the processor's answer; a client never supplies it. Absent until the authorisation resolves. Nothing personal: an alias only.

        • order_idstring · uuid

          Required

        • payment_account_idstring · uuid

          Required

        • referencestring

          The processor's reference for the authorisation, once placed.

        • statusstring · enum

          Required

          What became of the hold. A hold is pending while the authorisation is being requested, unknown while the outcome is being resolved, and held once a processor confirms it. A refusal leaves it declined or failed.

          pendingunknownhelddeclinedfailedsettledvoidedexpired

        • terminal_idstring

          Required

          The processor's name for the terminal the card was put down on.

        • versioninteger · int64

          Required

          The resource version; raises on every change, for If-Match.

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

  5. POST/v1/orders/{id}/card-holds/batch-create

    Put a card down on a tab

    No. batchCreateCardHoldsAuth terminal-checkouts:write

    Authorises cards on an open order, without capturing them, as a guarantee for a bar tab. A hold is settled when the tab closes and released when it is abandoned.

    Each item's id is minted by the client. Retrying with the same idempotency_key and body answers the same hold rather than placing a second one. Each item stands or falls on its own, exactly as in batchCreateTerminalCheckouts.

    An item is refused with 409 when the order is not open, and with 422 when the account cannot take a card here.

    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.

    Request body application/json · CardHoldBatchCreateRequest

    • itemsarray of CardHoldBatchCreateItem

      Required

      ≥ 1 items · ≤ 10 items

      • card_holdCardHoldCreate

        Required

        Puts a card down on an open order as a guarantee. The id is minted by the client, so retrying with the same id never places a second hold.

        • idstring · uuid

          Required

          Minted by the client; a retry under the same id answers the same hold.

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

        • payment_account_idstring · uuid

          Required

        • terminal_idstring

          Required

      • idempotency_keyItemIdempotencyKey

        Required

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

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

        16–255 chars

    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.

  6. POST/v1/orders/{id}/card-holds/{hold_id}/capture

    Capture a card hold

    No. captureCardHoldAuth terminal-checkouts:write

    Captures a held authorisation, as when a bar tab closes and is paid, for the tab's final amount — the order's total, tip included — and writes the card payment to the order. Refused with 409 when the hold is not held — already captured, released, or expired.

    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.

    • hold_idpath · string · uuid

      Required

      UUIDv7 of the card hold.

    • 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 · CardHoldActionRequest

    • amountMoney

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

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

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

      • amountinteger · int64

        Required

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

      • currencyCurrency

        Required

        ISO 4217 currency code.

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

        One of 182 values; see the schema.

    Responses

    • 200

      The hold was captured.

      CardHold · application/json
      • idstring · uuid

        Required

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

      • created_atstring · date-time

        Required

      • expires_atstring · date-time

        When the authorisation expires on the processor's terms, if known.

      • fingerprintstring

        Read only

        The processor's card-alias token for the authorised card — never the PAN — so a tab opened on a card can be named from a returning card (tabs item 14). The server records it from the processor's answer; a client never supplies it. Absent until the authorisation resolves. Nothing personal: an alias only.

      • order_idstring · uuid

        Required

      • payment_account_idstring · uuid

        Required

      • referencestring

        The processor's reference for the authorisation, once placed.

      • statusstring · enum

        Required

        What became of the hold. A hold is pending while the authorisation is being requested, unknown while the outcome is being resolved, and held once a processor confirms it. A refusal leaves it declined or failed.

        pendingunknownhelddeclinedfailedsettledvoidedexpired

      • terminal_idstring

        Required

        The processor's name for the terminal the card was put down on.

      • versioninteger · int64

        Required

        The resource version; raises on every change, for If-Match.

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

    • 415

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

    • 422

      The request parsed but failed validation.

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

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

    • 429

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

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

    • 500

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

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

    • 503

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

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

  7. POST/v1/orders/{id}/card-holds/{hold_id}/void

    Release a card hold

    No. voidCardHoldAuth terminal-checkouts:write

    Releases a held authorisation that is not going to be captured, as when a bar tab is abandoned. Refused with 409 when the hold is not held.

    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.

    • hold_idpath · string · uuid

      Required

      UUIDv7 of the card hold.

    • 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 · CardHoldActionRequest

    • amountMoney

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

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

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

      • amountinteger · int64

        Required

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

      • currencyCurrency

        Required

        ISO 4217 currency code.

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

        One of 182 values; see the schema.

    Responses

    • 200

      The hold was released.

      CardHold · application/json
      • idstring · uuid

        Required

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

      • created_atstring · date-time

        Required

      • expires_atstring · date-time

        When the authorisation expires on the processor's terms, if known.

      • fingerprintstring

        Read only

        The processor's card-alias token for the authorised card — never the PAN — so a tab opened on a card can be named from a returning card (tabs item 14). The server records it from the processor's answer; a client never supplies it. Absent until the authorisation resolves. Nothing personal: an alias only.

      • order_idstring · uuid

        Required

      • payment_account_idstring · uuid

        Required

      • referencestring

        The processor's reference for the authorisation, once placed.

      • statusstring · enum

        Required

        What became of the hold. A hold is pending while the authorisation is being requested, unknown while the outcome is being resolved, and held once a processor confirms it. A refusal leaves it declined or failed.

        pendingunknownhelddeclinedfailedsettledvoidedexpired

      • terminal_idstring

        Required

        The processor's name for the terminal the card was put down on.

      • versioninteger · int64

        Required

        The resource version; raises on every change, for If-Match.

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

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

  8. GET/v1/payment-accounts

    List payment accounts

    No. listPaymentAccountsAuth terminal-checkouts:read

    Returns the merchant's payment accounts (MIDs), in the order they were created. This is how a till learns which account a card goes on: each names the processor and the currency the sale settles in. The checkout seam takes a card on one of these.

    Parameters

    • cursorquery · string

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

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

    • limitquery · integer · int32

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

    Responses

    • 200

      A page of payment accounts.

      PaymentAccountPage · application/json
      • has_moreboolean

        Required

        Whether another page follows.

      • itemsarray of PaymentAccount

        Required

        • idstring · uuid

          Required

        • capabilitiesarray of string · enum

          Required

          What this account can do. A till offers a feature — a bar-tab hold, a tip on the terminal — only where the capability is present, and the server refuses one where it is not, from the same set, so the two never disagree. Each capability is granted when the account is boarded or when the processor reports it, and withdrawn the same way (docs/decisions/0020 §3, 0079). card_not_present is not listed because no till acts on it — a guest's card is taken on the ordering site, which the server renders — and adding a value here would break a till that cannot decode it.

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

        • currencystring

          Required

          The currency the account settles in; a card is taken in this currency.

        • kyc_statusstring · enum

          Required

          Where the account is in KYC. A checkout refuses an account that is not verified.

          unverifiedpendingverifiedrejected

        • merchant_idstring · uuid

          RequiredRead only

        • midstring

          Required

          The processor's identifier for this merchant account.

        • processorstring

          Required

          The processor this account routes card payments to.

        • relationshipstring · enum

          Required

          How the merchant relates to the account: its own (merchant), a platform account it routes through (platform), or one it routes to another processor (external).

          platformmerchantexternal

        • statusstring · enum

          Required

          An inactive account cannot take a card.

          activeinactive

        • underwriting_outcomestring

          Nullable

          The underwriting result, when one has been reached.

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

      • next_cursorstring

        Nullable

        Opaque cursor for the next page, or null on the last page.

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

  9. POST/v1/payment-accounts

    Add a payment account

    No. createPaymentAccountAuth payment-accounts:write

    Adds a merchant ID to route card payments through. The processor decides when it takes cards; its KYC checklist says what it is waiting for. A merchant ID decides where card money goes, so only the owner or a manager adds one, and only from a sign-in with two steps (docs/decisions/0126 §1). One with fewer is answered 401 with WWW-Authenticate: Bearer error="insufficient_user_authentication", acr_values="…", and the app signs in again asking the issuer for those acr_values (RFC 9470).

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

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

    • midstring

      Required

      The processor's merchant ID.

      1–64 chars

    • processorstring

      Required

      1–64 chars

    • relationshipstring · enum

      Required

      merchant is the merchant's own account, platform one it routes through, and external one at another processor it is routed to.

      merchantplatformexternal

    Responses

    • 201

      The account.

      PaymentAccount · application/json
      • idstring · uuid

        Required

      • capabilitiesarray of string · enum

        Required

        What this account can do. A till offers a feature — a bar-tab hold, a tip on the terminal — only where the capability is present, and the server refuses one where it is not, from the same set, so the two never disagree. Each capability is granted when the account is boarded or when the processor reports it, and withdrawn the same way (docs/decisions/0020 §3, 0079). card_not_present is not listed because no till acts on it — a guest's card is taken on the ordering site, which the server renders — and adding a value here would break a till that cannot decode it.

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

      • currencystring

        Required

        The currency the account settles in; a card is taken in this currency.

      • kyc_statusstring · enum

        Required

        Where the account is in KYC. A checkout refuses an account that is not verified.

        unverifiedpendingverifiedrejected

      • merchant_idstring · uuid

        RequiredRead only

      • midstring

        Required

        The processor's identifier for this merchant account.

      • processorstring

        Required

        The processor this account routes card payments to.

      • relationshipstring · enum

        Required

        How the merchant relates to the account: its own (merchant), a platform account it routes through (platform), or one it routes to another processor (external).

        platformmerchantexternal

      • statusstring · enum

        Required

        An inactive account cannot take a card.

        activeinactive

      • underwriting_outcomestring

        Nullable

        The underwriting result, when one has been reached.

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

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

  10. GET/v1/payment-accounts/{id}/kyc-checklist

    Get a payment account's KYC checklist

    No. getKycChecklistAuth payment-accounts:read

    What the processor still needs before the account takes cards, and whose move each one is. The processor decides every status; nothing here sets one.

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

      KycChecklist · application/json
      • payment_account_idstring · uuid

        Required

      • requirementsarray of KycRequirement

        Required

        • typestring

          Required

          The processor's name for what it needs, such as a proof of address.

        • descriptionstring

          Required

        • ownerstring · enum

          Required

          Whose move a pending requirement is.

          merchantprocessor

        • statusstring · enum

          Required

          pendingverifiedfailed

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

  11. POST/v1/terminal-checkouts/batch-create

    Take a card on a terminal

    No. batchCreateTerminalCheckoutsAuth terminal-checkouts:write

    Asks a terminal to take a card, through whichever processor the payment account uses, and returns at once. Poll GET /v1/terminal-checkouts/{id} until its status is final.

    The checkout's id is minted by the client. Retrying with the same item — same key, same body — never takes a second payment: it answers unchanged with the checkout as it now stands.

    An item is refused with 422 when no card could be taken on that account here: the account does not exist, is inactive, takes another currency, or uses a processor this deployment cannot reach.

    An item is refused with 409 and the problem type terminal-checkout-in-progress while another checkout for the same order_id is not yet final: two requests for one order on the terminals at once could charge the card twice. Once that one is final, another may be started, for a declined card or a split bill.

    Request body application/json · TerminalCheckoutBatchCreateRequest

    • itemsarray of TerminalCheckoutBatchCreateItem

      Required

      ≥ 1 items · ≤ 10 items

      • idempotency_keyItemIdempotencyKey

        Required

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

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

        16–255 chars

      • terminal_checkoutTerminalCheckout

        Required

        A request to take a card on a terminal, and what came of it.

        The till asks for an amount on a terminal and polls this until status is final. It is not the payment itself: when it is approved, the till records a card payment on the order carrying this checkout's reference and tip, with amount including the tip.

        Shaped after Square's TerminalCheckout and named in nexo's terms underneath (docs/decisions/0020). The same checkout works whichever processor the payment account uses; nothing here says which one it is.

        An unknown status is not a failure. It means contact with the processor was lost after the request left us, so the card may or may not have been charged. Do not take the card again: keep polling, and the server resolves it by asking the processor.

        • idstring · uuid

          Required

          Minted by the client as a UUIDv7, so retrying the request names the same checkout rather than starting a second one.

        • amountMoney

          Required

          What to take, before any tip added on the terminal.

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

        • order_idstring · uuid

          The order being paid for. Not checked against the orders the server has: a counter sale usually reaches the server after its card was taken. An order has at most one checkout that is not yet final.

        • payment_account_idstring · uuid

          Required

          The payment account (MID) the card is taken on, which decides the processor.

        • terminal_idstring

          Required

          The processor's identifier for the terminal the card is taken on.

          1–64 chars

        • tip_policystring · enum

          Whether the terminal prompts the guest for a tip, as the location set it. off takes the amount with no prompt; allow lets the guest add a tip on the terminal, which comes back as tip. A location's setting, sent here so the terminal offers it.

          offallow

          default off

    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.

  12. GET/v1/terminal-checkouts/{id}

    Get a terminal checkout

    No. getTerminalCheckoutAuth terminal-checkouts:read

    Returns a checkout as it now stands. A till waiting on a card polls this, about once a second, until status is final.

    Read directly rather than through the change feed, so a busy estate never delays the answer a guest at the terminal is waiting for.

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

      TerminalCheckout · application/json
      • idstring · uuid

        Required

        Minted by the client as a UUIDv7, so retrying the request names the same checkout rather than starting a second one.

      • amountMoney

        Required

        What to take, before any tip added on the terminal.

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

      • approvedMoney

        NullableRead only

        What a partial approval actually took, when the processor approved less than amount (a prepaid card with too little on it). Null when the full amount was approved. The order's card payment records this as its amount, and the till takes the rest of the sale by another tender.

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

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

      • device_idstring · uuid

        Read only

        The till that asked, when a till did.

      • fingerprintstring

        Read only

        The processor's card-alias token for the card, never the PAN — set once the card is approved. A till recognises a returning guest by it (customers item 12), and the server links their order by it when it resolves; nothing here is the card number.

      • order_idstring · uuid

        The order being paid for. Not checked against the orders the server has: a counter sale usually reaches the server after its card was taken. An order has at most one checkout that is not yet final.

      • payment_account_idstring · uuid

        Required

        The payment account (MID) the card is taken on, which decides the processor.

      • reasonstring

        Read only

        The processor's explanation of a decline or failure, for staff to read.

      • referencestring

        Read only

        The processor's reference for the payment, once approved. Record it on the order's card payment: it is what a payout is matched against.

        ≤ 128 chars

      • statusstring · enum

        RequiredRead only

        pending — accepted, not yet with the processor.

        in_progress — with the processor; the terminal may be waiting for the card.

        unknown — contact was lost after the request left us. The card may have been charged. Keep polling and do not take the card again; the server is asking the processor.

        approved — the card was charged. Final.

        declined — the card was refused. Final; reason says why.

        canceled — withdrawn before the card was charged. Final.

        failed — the processor refused the request itself, such as for a terminal it does not know. Final; reason says why.

        More values may be added. Treat one you do not recognise as not yet final.

        pendingin_progressunknownapproveddeclinedcanceledfailed

      • terminal_idstring

        Required

        The processor's identifier for the terminal the card is taken on.

        1–64 chars

      • tipMoney

        NullableRead only

        A tip the guest added on the terminal, on top of amount. The order's payment records amount plus this, with this as its tip.

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

      • tip_policystring · enum

        Whether the terminal prompts the guest for a tip, as the location set it. off takes the amount with no prompt; allow lets the guest add a tip on the terminal, which comes back as tip. A location's setting, sent here so the terminal offers it.

        offallow

        default off

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

  13. POST/v1/terminal-checkouts/{id}/cancel

    Cancel a terminal checkout

    No. cancelTerminalCheckoutAuth terminal-checkouts:write

    Withdraws a checkout. One not yet sent to the processor is canceled at once. One the terminal is already waiting on is asked to stop, and keeps its status until the processor answers: a card read a moment before the cancel arrived is still charged, and the checkout then ends approved. Keep polling either way.

    Canceling a canceled checkout returns it unchanged. One already approved, declined or failed cannot be canceled, and is refused with 409; an approved payment is undone by a refund.

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

    Responses

    • 200

      The checkout after the cancel was accepted.

      TerminalCheckout · application/json
      • idstring · uuid

        Required

        Minted by the client as a UUIDv7, so retrying the request names the same checkout rather than starting a second one.

      • amountMoney

        Required

        What to take, before any tip added on the terminal.

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

      • approvedMoney

        NullableRead only

        What a partial approval actually took, when the processor approved less than amount (a prepaid card with too little on it). Null when the full amount was approved. The order's card payment records this as its amount, and the till takes the rest of the sale by another tender.

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

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

      • device_idstring · uuid

        Read only

        The till that asked, when a till did.

      • fingerprintstring

        Read only

        The processor's card-alias token for the card, never the PAN — set once the card is approved. A till recognises a returning guest by it (customers item 12), and the server links their order by it when it resolves; nothing here is the card number.

      • order_idstring · uuid

        The order being paid for. Not checked against the orders the server has: a counter sale usually reaches the server after its card was taken. An order has at most one checkout that is not yet final.

      • payment_account_idstring · uuid

        Required

        The payment account (MID) the card is taken on, which decides the processor.

      • reasonstring

        Read only

        The processor's explanation of a decline or failure, for staff to read.

      • referencestring

        Read only

        The processor's reference for the payment, once approved. Record it on the order's card payment: it is what a payout is matched against.

        ≤ 128 chars

      • statusstring · enum

        RequiredRead only

        pending — accepted, not yet with the processor.

        in_progress — with the processor; the terminal may be waiting for the card.

        unknown — contact was lost after the request left us. The card may have been charged. Keep polling and do not take the card again; the server is asking the processor.

        approved — the card was charged. Final.

        declined — the card was refused. Final; reason says why.

        canceled — withdrawn before the card was charged. Final.

        failed — the processor refused the request itself, such as for a terminal it does not know. Final; reason says why.

        More values may be added. Treat one you do not recognise as not yet final.

        pendingin_progressunknownapproveddeclinedcanceledfailed

      • terminal_idstring

        Required

        The processor's identifier for the terminal the card is taken on.

        1–64 chars

      • tipMoney

        NullableRead only

        A tip the guest added on the terminal, on top of amount. The order's payment records amount plus this, with this as its tip.

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

      • tip_policystring · enum

        Whether the terminal prompts the guest for a tip, as the location set it. off takes the amount with no prompt; allow lets the guest add a tip on the terminal, which comes back as tip. A location's setting, sent here so the terminal offers it.

        offallow

        default off

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

    • 422

      The request parsed but failed validation.

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

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

    • 429

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

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

    • 500

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

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

    • 503

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

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

  14. GET/v1/terminals

    List terminals

    No. listTerminalsAuth terminal-checkouts:read

    Returns the merchant's card terminals, in name order. This is how a till learns which terminal is at which counter; the checkout seam names a terminal by its id, but a till has no reason to know a processor's POIID.

    Parameters

    • device_idquery · string · uuid

      Return only the terminal paired to this till, if any. A till passes its own id so the checkout offers the one terminal it owns rather than every card surface the merchant runs.

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

      TerminalPage · application/json
      • has_moreboolean

        Required

        Whether another page follows.

      • itemsarray of Terminal

        Required

        • idstring · uuid

          Required

        • namestring

          Required

          1–128 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.

        • device_idstring · uuid

          Nullable

          The till paired to this terminal, if any. A paired till offers only terminals it owns at the checkout, rather than every terminal a processor knows.

        • location_idstring · uuid

          Nullable

          The location this terminal serves, if it is fixed to one.

        • merchant_idstring · uuid

          RequiredRead only

        • processorstring

          Required

          The processor this terminal is on, matching the payment account's processor value.

        • statusstring · enum

          Required

          An inactive terminal is not offered at the checkout.

          activeinactive

        • terminal_idstring

          Required

          The processor's own id for this terminal (its POIID), which the checkout seam sends.

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

    • 422

      The request parsed but failed validation.

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

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

    • 429

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

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

    • 500

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

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

    • 503

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

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

  15. POST/v1/terminals/batch-upsert

    Create or replace terminals

    No. batchUpsertTerminalsAuth terminals:write

    Registers card terminals at client-minted ids, or replaces existing ones at the version they were read at: renaming, moving, pairing with a till, or turning off. A batch of one is how a single terminal is registered.

    A terminal is on a processor the merchant has a payment account with, and a processor's terminal id is registered once. Each item carries its own idempotency_key and stands or falls on its own.

    Request body application/json · TerminalBatchUpsertRequest

    • itemsarray of TerminalBatchUpsertItem

      Required

      ≥ 1 items · ≤ 500 items

      • idempotency_keyItemIdempotencyKey

        Required

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

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

        16–255 chars

      • terminalTerminal

        Required

        A card terminal the merchant runs: one surface to take a card, on whichever processor the processor field names (the same name a payment account records; docs/decisions/0020). The checkout seam names a terminal by its id, but a till has no reason to know a processor's POIID — it knows which terminal is at which counter. This is that mapping.

        • idstring · uuid

          Required

        • namestring

          Required

          1–128 chars

        • device_idstring · uuid

          Nullable

          The till paired to this terminal, if any. A paired till offers only terminals it owns at the checkout, rather than every terminal a processor knows.

        • location_idstring · uuid

          Nullable

          The location this terminal serves, if it is fixed to one.

        • processorstring

          Required

          The processor this terminal is on, matching the payment account's processor value.

        • statusstring · enum

          Required

          An inactive terminal is not offered at the checkout.

          activeinactive

        • terminal_idstring

          Required

          The processor's own id for this terminal (its POIID), which the checkout seam sends.

      • versioninteger · int64

        The version this write was made from. Required to replace a terminal that exists, and refused as 412 when somebody else has written it since.

        ≥ 1

    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.