GET/v1/discounts
List discounts
Returns the merchant's discounts, active and inactive, so a caller can show a paused promotion alongside the ones being offered.
Parameters
-
cursorquery · stringOpaque 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 · int32Maximum items to return. The server may return fewer, so always drive the loop from
has_morerather than from the size of the page you got back.
Responses
-
200
A page of discounts.
DiscountPage · application/json
has_morebooleanRequired
Whether another page follows.
-
itemsarray of DiscountRequired
idstring · uuidRequired
kindstring · enumRequired
The arithmetic.
percentagetakespercentageoff the gross;fixedtakes theamountoff;spendis a spend-and-save reward (retail promotions) — when the order's subtotal reachesthreshold, thepercentagepercentage or theamountcomes off the whole order, applied by the till with no staff action. Exactly one of the two value fields is meaningful, and which one is decided here rather than inferred from whichever is set. Aspenddiscount must be order scope.percentagefixedspendnamestringRequired
What staff see in the discount picker, and what the receipt prints.
1–128 chars
-
amountMoneyThe amount off for a
fixeddiscount, in the merchant's currency. Required whenkindisfixedand ignored otherwise; the currency is the merchant's and is set by the server, so a fixed discount cannot be created in a currency the merchant does not trade in.amountinteger · int64Required
The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.
currencyCurrencyRequired
ISO 4217 currency code.
Determines the minor unit that
Money.amountis expressed in. Zero-decimal currencies such asJPYhave a minor unit equal to the major unit.One of 182 values; see the schema.
created_atTimestampRequiredRead 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.merchant_idstring · uuidRequiredRead only
percentagePercentageA share of a whole, from
"0"to"100", as a percentage in a decimal string with at most four decimal places, as Square'spercentageand Stripe Tax'spercentage_decimalare: 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})?)$
-
schedulearray of DaypartWindowThe windows the discount is offered in. Empty or absent is an always-on discount, offered whenever staff pick it. A scheduled discount is the only kind that applies itself, with no staff action, which is why a manual staff rate must be left unscheduled.
≤ 8 items
daysarray of integerThe weekdays this window opens on, 1 (Monday) through 7 (Sunday). An empty or absent list is every day.
≤ 7 items
end_minuteintegerRequired
1–1440
start_minuteintegerRequired
0–1439
scopestring · enumRequired
Whether the discount comes off one line (
line_item) or the whole order (order). A till offers a line discount on the line it is on and an order discount at the total. Required rather than defaulted: the two are offered in different places on the till, so a discount that did not say which would be offered in the wrong one.line_itemorderstatusstring · enumRequired
inactivepauses a discount without losing it — a seasonal promotion is deactivated rather than deleted, so it can be brought back without being recreated. Devices are sent both, and offer only the active ones.activeinactive-
thresholdMoneyThe subtotal a
spendreward starts at, in the merchant's currency. Required whenkindisspendand ignored otherwise; a basket below it earns nothing.amountinteger · int64Required
The amount, as a whole number of the currency's minor unit. May be negative to express a refund, discount or credit.
currencyCurrencyRequired
ISO 4217 currency code.
Determines the minor unit that
Money.amountis expressed in. Zero-decimal currencies such asJPYhave a minor unit equal to the major unit.One of 182 values; see the schema.
updated_atTimestampRequiredRead 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.versionVersionRequiredRead 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
ETagresponse header as well as in the body. Send it back inIf-Matchon an update and the write is rejected with412if 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_cursorstringNullable
Opaque cursor for the next page, or null on the last page. Pass it back verbatim as the
cursorquery 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-Versionlater than the server speaks or not a date (api-version-unknown, with the server's own inapi_version).Distinct from
422: a400never reached validation, soinvalid_paramsis absent. Retrying without changing the request will fail again, except that a request refused for itsAPI-Versiongoes 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
401after 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
403on a resource the caller may not see, and as404where even confirming existence would leak information. Retrying will not help. -
410
The request names an
API-Versionthis server no longer serves (docs/decisions/0011). Nothing in it was read.api_versionnames 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_paramslists 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-Keyis replayed with a different body, and when device-computed order totals disagree with the server's recomputation. -
429
Rate limit exceeded. Honour
Retry-Afterrather 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; noRateLimitfield is sent, so this response and itsRetry-Afterare the whole of the contract. -
500
Something failed on our side.
trace_ididentifies 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-KeyafterRetry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.