# Reports

The ledger, liability and settlement.

Part of [the API reference, version 2026-10](https://matrixrewards.co.uk/docs/api/2026-10.md). Every address below starts with the base address:

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

## Every points movement on this account, newest first {#listLedger}

`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](https://matrixrewards.co.uk/docs/api/2026-10.md#headers).

### Example

```bash
curl "https://app.matrixrewards.co.uk/api/2026-10/ledger" \
  -H "Authorization: Bearer $MATRIX_API_KEY"
```

The answer:

```json
{
  "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](https://matrixrewards.co.uk/docs/errors/invalid_request.md), [invalid_cursor](https://matrixrewards.co.uk/docs/errors/invalid_cursor.md) |
| 401 | `unauthenticated`: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer. | [unauthenticated](https://matrixrewards.co.uk/docs/errors/unauthenticated.md) |
| 403 | `forbidden_scope`: the key does not carry the scope this call needs; `required_scope` says which. | [forbidden_scope](https://matrixrewards.co.uk/docs/errors/forbidden_scope.md) |
| 429 | `rate_limited`: too many requests for this key; `bucket` says which allowance. `Retry-After` says how long to wait. | [rate_limited](https://matrixrewards.co.uk/docs/errors/rate_limited.md) |
| 503 | `access_not_recorded`: the read could not be written to the account's audit log, so no data was returned. | [access_not_recorded](https://matrixrewards.co.uk/docs/errors/access_not_recorded.md) |

## What this account owes its shoppers {#getLiabilityReport}

`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](https://matrixrewards.co.uk/docs/api/2026-10.md#headers).

### Example

```bash
curl "https://app.matrixrewards.co.uk/api/2026-10/reports/liability" \
  -H "Authorization: Bearer $MATRIX_API_KEY"
```

The answer:

```json
{
  "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](https://matrixrewards.co.uk/docs/errors/unauthenticated.md) |
| 403 | `forbidden_scope`: the key does not carry the scope this call needs; `required_scope` says which. | [forbidden_scope](https://matrixrewards.co.uk/docs/errors/forbidden_scope.md) |
| 429 | `rate_limited`: too many requests for this key; `bucket` says which allowance. `Retry-After` says how long to wait. | [rate_limited](https://matrixrewards.co.uk/docs/errors/rate_limited.md) |

## Points earned at one store and spent at another, inside a window {#getSettlementReport}

`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](https://matrixrewards.co.uk/docs/api/2026-10.md#headers).

### Example

```bash
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:

```json
{
  "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](https://matrixrewards.co.uk/docs/errors/invalid_request.md) |
| 401 | `unauthenticated`: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer. | [unauthenticated](https://matrixrewards.co.uk/docs/errors/unauthenticated.md) |
| 403 | `forbidden_scope`: the key does not carry the scope this call needs; `required_scope` says which. | [forbidden_scope](https://matrixrewards.co.uk/docs/errors/forbidden_scope.md) |
| 429 | `rate_limited`: too many requests for this key; `bucket` says which allowance. `Retry-After` says how long to wait. | [rate_limited](https://matrixrewards.co.uk/docs/errors/rate_limited.md) |
| 503 | `access_not_recorded`: the read could not be written to the account's audit log, so no data was returned. | [access_not_recorded](https://matrixrewards.co.uk/docs/errors/access_not_recorded.md) |

---

Page: https://matrixrewards.co.uk/docs/api/2026-10/reports
Contract: 2026-10 sha256:35af153960028890fea5738799f5a71e535e1eea4dd3f6b8d5f50b37f02363da
