Skip to content
MatrixRewards
API docs menu

API reference, version 2026-10

Read a merchant's loyalty programme — stores, members, the ledger, reports, usage and the audit log — and change a member's points or cancel a reward code.

Every call goes to this base address:

https://app.matrixrewards.co.uk/api/2026-10

The whole API is one OpenAPI 3.1.0 file, version 2026-10, at https://app.matrixrewards.co.uk/api/openapi/2026-10.json. These pages are made from it. The newest version's file is also served at /api/openapi/latest.json.

Versions

The version is the first part of every address after /api. A call to a version this platform does not serve is answered with unsupported_version, which lists the versions that are. Every version served.

About this version

The account-scoped API behind Matrix Rewards. Every call is authenticated by an account API key (mrk_live_...), and the key alone decides whose data is served: no path, query or header names an account. The first writes are here — changing a member's points and cancelling a reward code — and so are webhooks: an https endpoint the merchant registers is sent every points movement, signed the Standard Webhooks way. The CLI follows. A points change and a code cancel each take an Idempotency-Key and can be previewed with ?dry_run=true, and every write is written to the account's audit log before it is answered; a key the merchant tagged for an AI assistant must preview a change and send back that preview's confirmation before anything changes.

Conventions: snake_case fields; lists are {data, has_more, next_cursor} with opaque keyset cursors; failures are RFC 9457 application/problem+json carrying a machine-readable code and the request_id also returned in the Request-Id header. The programme documents (config, effective, appearance) and the settlement report keep the engine's own field names, because they are the same documents the app writes and the write API will accept back.

Every read that carries shopper data is written to the account's audit log before it is returned, and the call fails with 503 access_not_recorded rather than return data it could not record.

No answer carries a whole reward code. An unused code is a live discount, and API answers end up in logs, scripts and assistant transcripts, so a code is named by its last four characters (code_ending, null for a code too short to show any of) and by its ref, which is what a cancel takes. The shopper holds the code itself.

Authentication

An account API key, created in the app. Scopes are held by the key, and a key can hold only the scopes listed in Scope: the ones some operation here checks, each named in that operation's security. write_members also covers read_members: a key that may change members' points may read them. A scope a key was given that is not on that list grants nothing, not even a read it might seem to cover, and is left out of what /me and /keys report. A key made for an AI assistant can never hold write_webhooks.

A key looks like mrk_live_<key id>.<secret><checksum>. How to make and send one.

Lists and cursors

  • limit: 1 to 100. Larger values are capped.
  • cursor: Opaque. Pass back next_cursor from the previous page, with the same filters, and on GET /members the same sort and order.

Dates and times

Each call that takes a window says how it reads its from and to:

  • GET /ledger and GET /audit-log: 2026-09-16 or 2026-09-16T10:00:00Z. A date means the whole of that day (a to of 2026-09-16 includes 23:59 on the 16th).
  • GET /reports/settlement:
    • from: The start of the window: an instant in UTC, written with a Z and to at most the millisecond, such as 2026-09-16T10:00:00Z. A date alone, or a finer fraction of a second, is refused with 400 invalid_request.
    • to: The end of the window, written the same way and later than from. The report stops just before it: anything recorded at exactly that instant is left out. A date alone, or a finer fraction of a second, is refused with 400 invalid_request.

Headers

The headers the answers carry. Each operation's page says which it sends, and which only when they apply.

HeaderWhat it is
Request-IdThis request's id, the same as a problem's request_id. Quote it if you contact support.
X-RateLimit-LimitThe bucket's size: how many calls of this kind, reads or writes, this key may make at once.
X-RateLimit-RemainingWhole calls of this kind left in the bucket after this one.
X-RateLimit-ResetUnix seconds when the bucket is full again.
WWW-AuthenticateNames the scheme a key is sent with: Bearer.
Retry-AfterOn a 429: Seconds to wait before sending the request again. On a 409: Only with idempotency_key_in_use: seconds to wait before sending the same request again for its answer.
Idempotency-ReplayedPresent, true, when this is the stored answer to an earlier request with the same Idempotency-Key.

Every operation

Account

What this key is and what it reaches.

Stores

The stores on the account and their programmes.

Members

Shoppers holding points, and the codes they hold.

Reports

The ledger, liability and settlement.

Billing

Usage against the plan, and the recorded plan.

  • GET /usage: Orders that earned points this billing month, against the plan's cap
  • GET /plan: The subscriptions recorded for this account

Log

What every key did, and what it was shown.

Webhooks

Where every points movement is sent, and a way to check a receiver. Managing webhooks needs write_webhooks, and a key for an AI assistant or a test key can never do it: an endpoint is a standing channel out of the account. Each event is POSTed as JSON to every enabled endpoint subscribed to its type, signed the Standard Webhooks way: webhook-id (the same on every retry, and the thing to de-duplicate on), webhook-timestamp (unix seconds of this attempt; reject one more than 5 minutes from your clock), and webhook-signature: v1,<base64 HMAC-SHA256> over <webhook-id>.<webhook-timestamp>.<body>, keyed by the base64 after whsec_ in the endpoint's secret, decoded to bytes. Verify the raw body, before parsing it. Answer 2xx within 15 seconds; anything else, a redirect, or no answer is a failure, and the event is sent again after 5 seconds, 5 minutes, 30 minutes, then 2, 5, 10, 14, 20 and 24 hours (each ±10%) — ten attempts over about 75 hours — with the same id and the same bytes. Events can arrive out of order: data.ledger_entry_id and data.created_at order them. An endpoint that has failed 10 deliveries in a row AND been failing for at least 24 hours is disabled: nothing more is sent to it, what was waiting is abandoned, webhook.disabled is written to the audit log, and GET /webhooks shows status: disabled. Delete it and create it again once the receiver works. An endpoint receives the events recorded after it was created.

The events a webhook endpoint is sent are on the Webhooks page.

Last updated: 14 October 2026