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 backnext_cursorfrom the previous page, with the same filters, and onGET /membersthe samesortandorder.
Dates and times
Each call that takes a window says how it reads its from and to:
GET /ledgerandGET /audit-log: 2026-09-16 or 2026-09-16T10:00:00Z. A date means the whole of that day (atoof 2026-09-16 includes 23:59 on the 16th).GET /reports/settlement:from: The start of the window: an instant in UTC, written with aZand 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 400invalid_request.to: The end of the window, written the same way and later thanfrom. 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 400invalid_request.
Headers
The headers the answers carry. Each operation's page says which it sends, and which only when they apply.
| Header | What it is |
|---|---|
Request-Id | This request's id, the same as a problem's request_id. Quote it if you contact support. |
X-RateLimit-Limit | The bucket's size: how many calls of this kind, reads or writes, this key may make at once. |
X-RateLimit-Remaining | Whole calls of this kind left in the bucket after this one. |
X-RateLimit-Reset | Unix seconds when the bucket is full again. |
WWW-Authenticate | Names the scheme a key is sent with: Bearer. |
Retry-After | On 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-Replayed | Present, 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.
GET /me: The account this key belongs to, its stores, and the key's own scopesGET /keys: The API keys on this accountPOST /keys/self/revoke: Stop the key making this request, immediately
Stores
The stores on the account and their programmes.
GET /stores: Every store on this accountGET /stores/{store_key}: One storeGET /stores/{store_key}/programme: A store's programme: what is stored, the settings in force, what shoppers are actually paid, and the expiry rulePOST /stores/{store_key}/programme: Change the earn rate, the bonuses, what a point is worth, referrals, spending any amount, the time zone, the ways to earn, or when points expireGET /stores/{store_key}/programme/rewards: The rewards a shopper can spend points on at this storePOST /stores/{store_key}/programme/rewards: Add a reward shoppers can spend points onPOST /stores/{store_key}/programme/rewards/{reward_id}: Change a reward's cost, value, minimum order or labelPOST /stores/{store_key}/programme/rewards/{reward_id}/remove: Remove a rewardGET /stores/{store_key}/programme/offers: A store's offersPOST /stores/{store_key}/programme/offers: Add an offer: a campaign, a product offer or a channel offerPOST /stores/{store_key}/programme/offers/{offer_id}: Change an offerPOST /stores/{store_key}/programme/offers/{offer_id}/remove: Remove an offerGET /stores/{store_key}/programme/rules: The earn rules in force at this storeGET /stores/{store_key}/design: A store's design: the colours, words and look of the rewards panel, the product-page line and the account pagePOST /stores/{store_key}/design: Change the design: colours, words, the panel and product-line look, the account page layout, the terms linkGET /stores/{store_key}/brand: A store's name and branding as the Matrix Rewards website shows them, and what Shopify hasGET /stores/{store_key}/images: The pictures uploaded to a store, newest firstPOST /stores/{store_key}/images: Upload a picture for the store's branding (a logo, a square logo, a cover)
Members
Shoppers holding points, and the codes they hold.
GET /members: Shoppers on this accountGET /members/{member_key}: One shopper's recordGET /members/{member_key}/codes: The reward codes this shopper holdsPOST /members/{member_key}/adjustments: Add points to a member's balance, or take them awayPOST /members/{member_key}/codes/void: Cancel a reward code and give the member back its points
Reports
The ledger, liability and settlement.
GET /ledger: Every points movement on this account, newest firstGET /reports/liability: What this account owes its shoppersGET /reports/settlement: Points earned at one store and spent at another, inside a window
Billing
Usage against the plan, and the recorded plan.
GET /usage: Orders that earned points this billing month, against the plan's capGET /plan: The subscriptions recorded for this account
Log
What every key did, and what it was shown.
GET /audit-log: What every key changed, 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.
GET /webhooks: The webhook endpoints on this account, and why any stoppedPOST /webhooks: Send points movements to an https endpointDELETE /webhooks/{endpoint_id}: Stop sending to an endpointPOST /webhooks/{endpoint_id}/test: Send one signed test event now
The events a webhook endpoint is sent are on the Webhooks page.