Reports
The ledger, liability and settlement.
Part of the API reference, version 2026-10. Every address below starts with the base address:
https://app.matrixrewards.co.uk/api/2026-10
Every points movement on this account, newest first
GET /ledger — the full address is https://app.matrixrewards.co.uk/api/2026-10/ledger.
Needs: the read_reports permission (in the app: “The ledger, what you owe, and settlement between stores”).
The ledger is append-only: a correction is a later row, never an edit to an earlier one.
Parameters
| Name | In | Required | Type | What it is |
|---|---|---|---|---|
store | query | no | string | — |
member_key | query | no | string | — |
from | query | no | string | 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). |
to | query | no | string | 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). |
limit | query | no | integer, from 1 to 100, default 25 | 1 to 100. Larger values are capped. |
cursor | query | no | string | Opaque. Pass back next_cursor from the previous page, with the same filters, and on GET /members the same sort and order. |
The answer
A page of ledger entries.
| Field | Type | Always there | What it is |
|---|---|---|---|
data | list of object | yes | — |
data[].id | integer | yes | — |
data[].at | string | yes | — |
data[].store_key | string | yes | — |
data[].store_name | string, or null | yes | Null when the row's store is not one of this account's. |
data[].member_key | string | yes | — |
data[].delta_points | integer | yes | Signed: earning is positive, spending and clawbacks negative. |
data[].reason | string | yes | — |
data[].rule_id | string | yes | — |
data[].source_type | string | yes | — |
data[].source_ref | string, or null | yes | The platform's own key for what caused the row: an order, a refund, a points change. Null when that key is a reward code — a cancelled code's points coming back (redeem_void) or being taken again because it was spent after all (void_clawback); code_ending and code_ref name the code instead. |
data[].order_ref | string, or null | yes | — |
data[].code_ending | string, or null | yes | The last four characters of the reward code this row moved points for; null on a row that is not about a code, and for a code too short to show any of (see CodePage). The whole code is never returned by this API. |
data[].code_ref | string, matching ^[0-9a-f]{20}$, or null | yes | That code's ref, as listMemberCodes gives it and cancelMemberCode takes it. Null on a row with no code, or when the platform holds no record of the code to give a reference for. |
has_more | boolean | yes | — |
next_cursor | string, or null | yes | — |
Headers on the answer: Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. What each header means.
Example
curl "https://app.matrixrewards.co.uk/api/2026-10/ledger" \
-H "Authorization: Bearer $MATRIX_API_KEY"
The answer:
{
"data": [
{
"id": 3,
"at": "2026-09-16T10:00:00.000Z",
"store_key": "example-store",
"store_name": "Example Store",
"member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
"delta_points": 500,
"reason": "Reward removed — points returned",
"rule_id": "redeem_void",
"source_type": "redeem_void",
"source_ref": null,
"order_ref": null,
"code_ending": "4TPB",
"code_ref": "5f0c2d8e91a47b36c0de"
},
{
"id": 2,
"at": "2026-09-16T10:00:00.000Z",
"store_key": "example-store",
"store_name": "Example Store",
"member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
"delta_points": 50,
"reason": "Sorry about the late parcel",
"rule_id": "admin_adjustment",
"source_type": "admin_adjustment",
"source_ref": "admin|api.Kx7Qp2Lm9Vt4Rz8N|goodwill-2026-09-16-0001|mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18|50",
"order_ref": null,
"code_ending": null,
"code_ref": null
},
{
"id": 1,
"at": "2026-08-04T11:02:00Z",
"store_key": "example-store",
"store_name": "Example Store",
"member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
"delta_points": 640,
"reason": "Points earned on your order",
"rule_id": "base_earn",
"source_type": "order",
"source_ref": "o-1001",
"order_ref": null,
"code_ending": null,
"code_ref": null
}
],
"has_more": false,
"next_cursor": null
}
Other answers
| Status | What it means | Error pages |
|---|---|---|
| 400 | invalid_request: a parameter could not be read; param names it. invalid_cursor: the cursor is not one this list issued. | invalid_request, invalid_cursor |
| 401 | unauthenticated: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer. | unauthenticated |
| 403 | forbidden_scope: the key does not carry the scope this call needs; required_scope says which. | forbidden_scope |
| 429 | rate_limited: too many requests for this key; bucket says which allowance. Retry-After says how long to wait. | rate_limited |
| 503 | access_not_recorded: the read could not be written to the account's audit log, so no data was returned. | access_not_recorded |
What this account owes its shoppers
GET /reports/liability — the full address is https://app.matrixrewards.co.uk/api/2026-10/reports/liability.
Needs: the read_reports permission (in the app: “The ledger, what you owe, and settlement between stores”).
Summed from the ledger, not from the balance cache. The money value is null with a reason rather than a guess when the stores redeem at different rates or none is configured.
The answer
The liability report.
| Field | Type | Always there | What it is |
|---|---|---|---|
as_at | string | yes | — |
redeemable_points | integer | yes | Held by members in credit — what could be spent tomorrow. |
net_points | integer | yes | The algebraic sum, including negative balances. |
members_in_credit | integer | yes | — |
total_members | integer | yes | — |
points_expiring_within_days | integer, or null | yes | Points due to expire within expiring_within_days that can still be taken: the shopper holds them, was shown every warning whose window has already passed, and can still be shown the rest because a store they shop at shows the expiry countdown. It is the most that can go, not a forecast: a shopper who misses a later warning keeps their points. Null means we could not tell, which is not the same as none. |
expiring_within_days | integer | yes | — |
outstanding_codes | list of object | yes | — |
outstanding_codes[].store_key | string | yes | — |
outstanding_codes[].count | integer | yes | — |
outstanding_codes[].cost_points | integer | yes | — |
valuation | object, or null | yes | — |
valuation.currency | string | yes | — |
valuation.minor_units | integer | yes | — |
valuation.major_units | string | yes | — |
valuation_unavailable | one of mixed_rates, no_rate_configured, or null | yes | — |
redeem_rate_by_store | list of object | yes | — |
redeem_rate_by_store[].store_key | string | yes | — |
redeem_rate_by_store[].currency | string, or null | yes | — |
redeem_rate_by_store[].redeem_points_per_major_unit | integer, or null | yes | — |
Headers on the answer: Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. What each header means.
Example
curl "https://app.matrixrewards.co.uk/api/2026-10/reports/liability" \
-H "Authorization: Bearer $MATRIX_API_KEY"
The answer:
{
"as_at": "2026-09-16T10:00:00Z",
"redeemable_points": 1190,
"net_points": 1190,
"members_in_credit": 1,
"total_members": 1,
"points_expiring_within_days": 0,
"expiring_within_days": 90,
"outstanding_codes": [],
"valuation": {
"currency": "GBP",
"minor_units": 1190,
"major_units": "11.90"
},
"valuation_unavailable": null,
"redeem_rate_by_store": [
{
"store_key": "example-outlet",
"currency": "GBP",
"redeem_points_per_major_unit": 100
},
{
"store_key": "example-store",
"currency": "GBP",
"redeem_points_per_major_unit": 100
}
]
}
Other answers
| Status | What it means | Error pages |
|---|---|---|
| 401 | unauthenticated: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer. | unauthenticated |
| 403 | forbidden_scope: the key does not carry the scope this call needs; required_scope says which. | forbidden_scope |
| 429 | rate_limited: too many requests for this key; bucket says which allowance. Retry-After says how long to wait. | rate_limited |
Points earned at one store and spent at another, inside a window
GET /reports/settlement — the full address is https://app.matrixrewards.co.uk/api/2026-10/reports/settlement.
Needs: the read_reports permission (in the app: “The ledger, what you owe, and settlement between stores”).
The window is two absolute UTC instants. The report keeps the engine's own field names.
Parameters
| Name | In | Required | Type | What it is |
|---|---|---|---|---|
from | query | yes | string | 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 | query | yes | string | 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. |
The answer
The settlement report.
| Field | Type | Always there | What it is |
|---|---|---|---|
report | object, open | yes | The settlement report in the engine's own field names. |
An open object has no fixed set of fields: it is sent as the platform holds it, in its own field names.
Headers on the answer: Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. What each header means.
Example
curl "https://app.matrixrewards.co.uk/api/2026-10/reports/settlement?from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer $MATRIX_API_KEY"
The answer:
{
"report": {
"accountId": "acc_example",
"window": {
"from": "2026-09-01T00:00:00Z",
"to": "2026-09-16T00:00:00Z"
},
"lines": [],
"totals": {
"redeemedPoints": 0,
"consumedPoints": 0,
"attributedPoints": 0,
"unattributedPoints": 0,
"valueByCurrency": {}
},
"attributionComplete": true,
"anomalies": [],
"unpricedSpendSites": [],
"unknownSpendSites": [],
"clampedFutureRows": 0,
"ledgerDigest": "17b799bbaf2815941767da47eebbc881fac4e92211c327b92723cad37f5003b7"
}
}
Other answers
| Status | What it means | Error pages |
|---|---|---|
| 400 | invalid_request: a parameter or the body could not be read; param names it. | invalid_request |
| 401 | unauthenticated: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer. | unauthenticated |
| 403 | forbidden_scope: the key does not carry the scope this call needs; required_scope says which. | forbidden_scope |
| 429 | rate_limited: too many requests for this key; bucket says which allowance. Retry-After says how long to wait. | rate_limited |
| 503 | access_not_recorded: the read could not be written to the account's audit log, so no data was returned. | access_not_recorded |