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

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

Merchant · Reference

Billing

What a merchant has bought and is licensed for.

Operations
6
Base
https://api.tenvelo.com
  1. GET/v1/billing

    Get the current merchant's billing

    No. getBillingAuth billing:read

    Returns what the merchant has bought — every subscription — the plans they may buy, and the tier their subscriptions grant.

    The tier a client enforces on is derived server-side, so a lagging or partitioned client meets a value it predates rather than one it worked out wrong.

    Responses

    • 200

      The billing status.

      BillingStatus · application/json
      • billable_locationsinteger

        Required

        The locations a per-location plan charges this business for, which each plan's estate_price counts.

        ≥ 0

      • offersarray of BillingOffer

        Required

        What each plan would cost this business a month, one per plan.

        • estate_changeMoney

          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.

        • estate_priceMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

        • plan_idstring

          Required

      • plansarray of Plan

        Required

        The plans a merchant may buy — the seeded catalogue Tenvelo sells.

        • idstring

          Required

          A stable id for the plan, e.g. tier-plus.

        • namestring

          Required

          A human-readable plan name.

        • priceMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

        • suiteBillingSuite

          Required

          A product line a merchant may buy — the POS till, ordering, customers, operations.

        • tierBillingTier

          Required

          The packaging a merchant is on. Free is the till and back office with cash-up and Z reports; Plus adds tables, kitchen, bookings, customer records, rotas and several sites; Pro adds online and QR ordering, loyalty, gift cards, stock, and payroll and accounts exports.

        • unitBillingUnit

          Required

          How a plan's price scales.

          flat — one price for the merchant, regardless of size.

          location — per shop, stall or van; the price scales with the estate.

          device — per till.

          flatlocationdevice

      • subscriptionsarray of Subscription

        Required

        What the merchant has taken, one per site packaging.

        • idstring · uuid

          Required

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

        • billed_to_cardboolean

          Required

          Whether the card provider charges for it, against the card the owner put on file.

        • charged_quantityinteger

          RequiredNullable

          How many units the card provider is charging for, when that differs from quantity. Null when they agree.

          ≥ 0

        • created_atstring · date-time

          When the subscription was first recorded.

        • current_chargeBilling charge

          RequiredNullable

          What a live subscription charges for its current period so far; null otherwise.

          • ends_atstring · date-time

            Required

          • linesarray of Billing charge line

            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.

            • daysinteger

              Required

              The days the site was open in the period.

              ≥ 0

            • location_idstring · uuid

              RequiredNullable

              The site charged for, or null for a flat plan, which no site scales.

            • location_namestring

              RequiredNullable

              The site's name, kept for one since closed.

          • starts_atstring · date-time

            Required

          • totalMoney

            Required

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

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

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

            • amountinteger · int64

              Required

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

            • currencyCurrency

              Required

              ISO 4217 currency code.

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

              One of 182 values; see the schema.

        • current_priceMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

        • grace_ends_atstring · date-time

          RequiredNullable

          When a past-due subscription stops working, if it is past due.

        • location_idsarray of string · uuid

          Nullable

          The sites a per-location plan covers; the suite grants at exactly those locations. Null for flat and per-device plans, which are merchant-wide.

        • planPlan

          Required

          What a merchant can buy: a suite at a tier, priced per unit (per location, per device or flat). Plans are the catalogue Tenvelo sells; the merchant chooses from it.

          • idstring

            Required

            A stable id for the plan, e.g. tier-plus.

          • namestring

            Required

            A human-readable plan name.

          • priceMoney

            Required

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

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

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

            • amountinteger · int64

              Required

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

            • currencyCurrency

              Required

              ISO 4217 currency code.

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

              One of 182 values; see the schema.

          • suiteBillingSuite

            Required

            A product line a merchant may buy — the POS till, ordering, customers, operations.

          • tierBillingTier

            Required

            The packaging a merchant is on. Free is the till and back office with cash-up and Z reports; Plus adds tables, kitchen, bookings, customer records, rotas and several sites; Pro adds online and QR ordering, loyalty, gift cards, stock, and payroll and accounts exports.

          • unitBillingUnit

            Required

            How a plan's price scales.

            flat — one price for the merchant, regardless of size.

            location — per shop, stall or van; the price scales with the estate.

            device — per till.

            flatlocationdevice

        • quantityinteger

          Required

          How many units the plan covers — locations for a per-location plan, devices for a per-device plan, 1 for a flat plan.

          ≥ 0

        • started_atstring · date-time

          Required

          When the subscription began.

        • stateBillingState

          Required

          The state a subscription is in.

          trialing — the merchant is trying it; the entitlements it grants may end.

          active — the merchant is paying for it; its entitlements are granted.

          past_due — payment has lapsed; dunning runs before any refusal.

          stopped — a past-due grace window closed without payment; the suite stopped granting, and the merchant may still put it right.

          canceled — the merchant ended it; its entitlements are no longer granted.

          trialingactivepast_duestoppedcanceled

        • trial_ends_atstring · date-time

          Nullable

          When a trial ends, if the subscription is trialing.

        • uncovered_location_idsarray of string · uuid

          Required

          The open sites a per-location plan limited to some sites does not cover.

        • updated_atstring · date-time

          When the subscription was last changed.

        • versioninteger · int64

          Required

          The version the caller last saw, for optimistic concurrency.

      • tierBillingTier

        Required

        The packaging a merchant is on. Free is the till and back office with cash-up and Z reports; Plus adds tables, kitchen, bookings, customer records, rotas and several sites; Pro adds online and QR ordering, loyalty, gift cards, stock, and payroll and accounts exports.

      • tier_ladderarray of BillingTier

        Required

        The tiers in the order they stack, weakest first. A client derives what a tier grants from the label's rung on this list rather than comparing labels it released knowing — a tier added above is one server deployment, not one client release.

      • trial_daysinteger

        Required

        How long a trial of a plan lasts.

        ≥ 1

    • 400

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

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

    • 401

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

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

    • 403

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

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

    • 410

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

    • 429

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

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

    • 500

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

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

    • 503

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

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

  2. POST/v1/billing/subscriptions

    Take out a subscription

    No. createSubscriptionAuth billing:write

    Subscribes the merchant to a plan. A per-location plan charges for the sites in location_ids, or every site, including ones opened later, when it names none. A plan the card provider prices is billed through the provider, and the owner then puts a card on file with a card link. Only the owner may take one out, and a merchant has at most one live subscription.

    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 · Subscription create

    • location_idsarray of string · uuid

      Required

      The sites a per-location plan covers. Empty covers every site, including ones opened later. Any other plan names none.

    • plan_idstring

      Required

      The plan's id, from the billing read.

    Responses

    • 201

      The subscription.

      Subscription · application/json
      • idstring · uuid

        Required

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

      • billed_to_cardboolean

        Required

        Whether the card provider charges for it, against the card the owner put on file.

      • charged_quantityinteger

        RequiredNullable

        How many units the card provider is charging for, when that differs from quantity. Null when they agree.

        ≥ 0

      • created_atstring · date-time

        When the subscription was first recorded.

      • current_chargeBilling charge

        RequiredNullable

        What a live subscription charges for its current period so far; null otherwise.

        • ends_atstring · date-time

          Required

        • linesarray of Billing charge line

          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.

          • daysinteger

            Required

            The days the site was open in the period.

            ≥ 0

          • location_idstring · uuid

            RequiredNullable

            The site charged for, or null for a flat plan, which no site scales.

          • location_namestring

            RequiredNullable

            The site's name, kept for one since closed.

        • starts_atstring · date-time

          Required

        • totalMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

      • current_priceMoney

        Required

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

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

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

        • amountinteger · int64

          Required

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

        • currencyCurrency

          Required

          ISO 4217 currency code.

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

          One of 182 values; see the schema.

      • grace_ends_atstring · date-time

        RequiredNullable

        When a past-due subscription stops working, if it is past due.

      • location_idsarray of string · uuid

        Nullable

        The sites a per-location plan covers; the suite grants at exactly those locations. Null for flat and per-device plans, which are merchant-wide.

      • planPlan

        Required

        What a merchant can buy: a suite at a tier, priced per unit (per location, per device or flat). Plans are the catalogue Tenvelo sells; the merchant chooses from it.

        • idstring

          Required

          A stable id for the plan, e.g. tier-plus.

        • namestring

          Required

          A human-readable plan name.

        • priceMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

        • suiteBillingSuite

          Required

          A product line a merchant may buy — the POS till, ordering, customers, operations.

        • tierBillingTier

          Required

          The packaging a merchant is on. Free is the till and back office with cash-up and Z reports; Plus adds tables, kitchen, bookings, customer records, rotas and several sites; Pro adds online and QR ordering, loyalty, gift cards, stock, and payroll and accounts exports.

        • unitBillingUnit

          Required

          How a plan's price scales.

          flat — one price for the merchant, regardless of size.

          location — per shop, stall or van; the price scales with the estate.

          device — per till.

          flatlocationdevice

      • quantityinteger

        Required

        How many units the plan covers — locations for a per-location plan, devices for a per-device plan, 1 for a flat plan.

        ≥ 0

      • started_atstring · date-time

        Required

        When the subscription began.

      • stateBillingState

        Required

        The state a subscription is in.

        trialing — the merchant is trying it; the entitlements it grants may end.

        active — the merchant is paying for it; its entitlements are granted.

        past_due — payment has lapsed; dunning runs before any refusal.

        stopped — a past-due grace window closed without payment; the suite stopped granting, and the merchant may still put it right.

        canceled — the merchant ended it; its entitlements are no longer granted.

        trialingactivepast_duestoppedcanceled

      • trial_ends_atstring · date-time

        Nullable

        When a trial ends, if the subscription is trialing.

      • uncovered_location_idsarray of string · uuid

        Required

        The open sites a per-location plan limited to some sites does not cover.

      • updated_atstring · date-time

        When the subscription was last changed.

      • versioninteger · int64

        Required

        The version the caller last saw, for optimistic concurrency.

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

  3. POST/v1/billing/subscriptions/{id}/cancel

    Cancel a subscription

    No. cancelSubscriptionAuth billing:write

    Ends a live subscription. It stays on the books, so the history is complete. Only the owner may cancel 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.

    • Idempotency-Keyheader · string

      Required

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

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

      Rules:

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

      A UUIDv7 or ULID is a good choice.

    Responses

    • 200

      The canceled subscription.

      Subscription · application/json
      • idstring · uuid

        Required

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

      • billed_to_cardboolean

        Required

        Whether the card provider charges for it, against the card the owner put on file.

      • charged_quantityinteger

        RequiredNullable

        How many units the card provider is charging for, when that differs from quantity. Null when they agree.

        ≥ 0

      • created_atstring · date-time

        When the subscription was first recorded.

      • current_chargeBilling charge

        RequiredNullable

        What a live subscription charges for its current period so far; null otherwise.

        • ends_atstring · date-time

          Required

        • linesarray of Billing charge line

          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.

          • daysinteger

            Required

            The days the site was open in the period.

            ≥ 0

          • location_idstring · uuid

            RequiredNullable

            The site charged for, or null for a flat plan, which no site scales.

          • location_namestring

            RequiredNullable

            The site's name, kept for one since closed.

        • starts_atstring · date-time

          Required

        • totalMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

      • current_priceMoney

        Required

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

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

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

        • amountinteger · int64

          Required

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

        • currencyCurrency

          Required

          ISO 4217 currency code.

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

          One of 182 values; see the schema.

      • grace_ends_atstring · date-time

        RequiredNullable

        When a past-due subscription stops working, if it is past due.

      • location_idsarray of string · uuid

        Nullable

        The sites a per-location plan covers; the suite grants at exactly those locations. Null for flat and per-device plans, which are merchant-wide.

      • planPlan

        Required

        What a merchant can buy: a suite at a tier, priced per unit (per location, per device or flat). Plans are the catalogue Tenvelo sells; the merchant chooses from it.

        • idstring

          Required

          A stable id for the plan, e.g. tier-plus.

        • namestring

          Required

          A human-readable plan name.

        • priceMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

        • suiteBillingSuite

          Required

          A product line a merchant may buy — the POS till, ordering, customers, operations.

        • tierBillingTier

          Required

          The packaging a merchant is on. Free is the till and back office with cash-up and Z reports; Plus adds tables, kitchen, bookings, customer records, rotas and several sites; Pro adds online and QR ordering, loyalty, gift cards, stock, and payroll and accounts exports.

        • unitBillingUnit

          Required

          How a plan's price scales.

          flat — one price for the merchant, regardless of size.

          location — per shop, stall or van; the price scales with the estate.

          device — per till.

          flatlocationdevice

      • quantityinteger

        Required

        How many units the plan covers — locations for a per-location plan, devices for a per-device plan, 1 for a flat plan.

        ≥ 0

      • started_atstring · date-time

        Required

        When the subscription began.

      • stateBillingState

        Required

        The state a subscription is in.

        trialing — the merchant is trying it; the entitlements it grants may end.

        active — the merchant is paying for it; its entitlements are granted.

        past_due — payment has lapsed; dunning runs before any refusal.

        stopped — a past-due grace window closed without payment; the suite stopped granting, and the merchant may still put it right.

        canceled — the merchant ended it; its entitlements are no longer granted.

        trialingactivepast_duestoppedcanceled

      • trial_ends_atstring · date-time

        Nullable

        When a trial ends, if the subscription is trialing.

      • uncovered_location_idsarray of string · uuid

        Required

        The open sites a per-location plan limited to some sites does not cover.

      • updated_atstring · date-time

        When the subscription was last changed.

      • versioninteger · int64

        Required

        The version the caller last saw, for optimistic concurrency.

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

    • 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/billing/subscriptions/{id}/invoices

    List a subscription's invoices

    No. listSubscriptionInvoicesAuth billing:read

    A subscription billed through the card provider's invoices, as the provider has them: what was charged, and where the canonical copy lives. Only the owner may read them.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

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

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

      Subscription invoice page · application/json
      • has_moreboolean

        Required

        Whether another page may follow.

      • itemsarray of Subscription invoice

        Required

        • idstring

          Required

          The provider's id for the invoice.

        • 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

        • hosted_urlstring · uri

          RequiredNullable

          The provider's page for the invoice, when it has one.

        • statestring

          Required

          Where the invoice stands, in the provider's word (draft, open, paid, void, uncollectible).

      • next_cursorstring

        Nullable

        Pass as cursor for the next page, when has_more is true.

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

  5. POST/v1/billing/trials

    Start a trial

    No. createTrialAuth billing:write

    Opens a trialing subscription on a plan, so the owner can try a tier before buying it. A per-location trial covers every site. Refused while any subscription is live, and for a tier the merchant has trialled before. Only the owner may start one.

    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 · Trial create

    • plan_idstring

      Required

      The plan's id, from the billing read.

    Responses

    • 201

      The trialing subscription.

      Subscription · application/json
      • idstring · uuid

        Required

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

      • billed_to_cardboolean

        Required

        Whether the card provider charges for it, against the card the owner put on file.

      • charged_quantityinteger

        RequiredNullable

        How many units the card provider is charging for, when that differs from quantity. Null when they agree.

        ≥ 0

      • created_atstring · date-time

        When the subscription was first recorded.

      • current_chargeBilling charge

        RequiredNullable

        What a live subscription charges for its current period so far; null otherwise.

        • ends_atstring · date-time

          Required

        • linesarray of Billing charge line

          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.

          • daysinteger

            Required

            The days the site was open in the period.

            ≥ 0

          • location_idstring · uuid

            RequiredNullable

            The site charged for, or null for a flat plan, which no site scales.

          • location_namestring

            RequiredNullable

            The site's name, kept for one since closed.

        • starts_atstring · date-time

          Required

        • totalMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

      • current_priceMoney

        Required

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

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

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

        • amountinteger · int64

          Required

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

        • currencyCurrency

          Required

          ISO 4217 currency code.

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

          One of 182 values; see the schema.

      • grace_ends_atstring · date-time

        RequiredNullable

        When a past-due subscription stops working, if it is past due.

      • location_idsarray of string · uuid

        Nullable

        The sites a per-location plan covers; the suite grants at exactly those locations. Null for flat and per-device plans, which are merchant-wide.

      • planPlan

        Required

        What a merchant can buy: a suite at a tier, priced per unit (per location, per device or flat). Plans are the catalogue Tenvelo sells; the merchant chooses from it.

        • idstring

          Required

          A stable id for the plan, e.g. tier-plus.

        • namestring

          Required

          A human-readable plan name.

        • priceMoney

          Required

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

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

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

          • amountinteger · int64

            Required

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

          • currencyCurrency

            Required

            ISO 4217 currency code.

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

            One of 182 values; see the schema.

        • suiteBillingSuite

          Required

          A product line a merchant may buy — the POS till, ordering, customers, operations.

        • tierBillingTier

          Required

          The packaging a merchant is on. Free is the till and back office with cash-up and Z reports; Plus adds tables, kitchen, bookings, customer records, rotas and several sites; Pro adds online and QR ordering, loyalty, gift cards, stock, and payroll and accounts exports.

        • unitBillingUnit

          Required

          How a plan's price scales.

          flat — one price for the merchant, regardless of size.

          location — per shop, stall or van; the price scales with the estate.

          device — per till.

          flatlocationdevice

      • quantityinteger

        Required

        How many units the plan covers — locations for a per-location plan, devices for a per-device plan, 1 for a flat plan.

        ≥ 0

      • started_atstring · date-time

        Required

        When the subscription began.

      • stateBillingState

        Required

        The state a subscription is in.

        trialing — the merchant is trying it; the entitlements it grants may end.

        active — the merchant is paying for it; its entitlements are granted.

        past_due — payment has lapsed; dunning runs before any refusal.

        stopped — a past-due grace window closed without payment; the suite stopped granting, and the merchant may still put it right.

        canceled — the merchant ended it; its entitlements are no longer granted.

        trialingactivepast_duestoppedcanceled

      • trial_ends_atstring · date-time

        Nullable

        When a trial ends, if the subscription is trialing.

      • uncovered_location_idsarray of string · uuid

        Required

        The open sites a per-location plan limited to some sites does not cover.

      • updated_atstring · date-time

        When the subscription was last changed.

      • versioninteger · int64

        Required

        The version the caller last saw, for optimistic concurrency.

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