Skip to content
MatrixRewards
API docs menu

Members

Shoppers holding points, and the codes they hold.

Part of the API reference, version 2026-10. Every address below starts with the base address:

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

Shoppers on this account

GET /members — the full address is https://app.matrixrewards.co.uk/api/2026-10/members.

Needs: the read_members permission (in the app: “Members, their points, their email addresses and the last four characters of their reward codes”). A key with write_members can call it too.

Recorded in the audit log as a disclosure, counted in shoppers.

Parameters

NameInRequiredTypeWhat it is
qquerynostring, up to 120 charactersEmail, member key or store customer reference. At most 120 characters.
storequerynostringOnly members with a customer record at this store.
sortquerynoone of member_key, balance, enrolled_at, last_activity_at—
orderquerynoone of asc, desc—
limitquerynointeger, from 1 to 100, default 251 to 100. Larger values are capped.
cursorquerynostringOpaque. 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 members.

FieldTypeAlways thereWhat it is
datalist of object (MemberSummary)yes—
data[].member_keystringyes—
data[].emailstringyes—
data[].balance_pointsintegeryesThe maintained balance, which the engine recomputes from the ledger under a lock on every write. GET /members/{member_key} sums the ledger rows there and then; the two agree unless something is wrong, and that is the only difference between them.
data[].enrolled_atstringyes—
data[].last_activity_atstringyes—
has_morebooleanyes—
next_cursorstring, or nullyes—

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/members" \
  -H "Authorization: Bearer $MATRIX_API_KEY"

The answer:

{
  "data": [
    {
      "member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
      "email": "alex@example.com",
      "balance_points": 640,
      "enrolled_at": "2026-08-04T11:02:00Z",
      "last_activity_at": "2026-08-04T11:02:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Other answers

StatusWhat it meansError pages
400invalid_request: a parameter could not be read; param names it. invalid_cursor: the cursor is not one this list issued.invalid_request, invalid_cursor
401unauthenticated: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer.unauthenticated
403forbidden_scope: the key does not carry the scope this call needs; required_scope says which.forbidden_scope
429rate_limited: too many requests for this key; bucket says which allowance. Retry-After says how long to wait.rate_limited
503access_not_recorded: the read could not be written to the account's audit log, so no data was returned.access_not_recorded

One shopper's record

GET /members/{member_key} — the full address is https://app.matrixrewards.co.uk/api/2026-10/members/{member_key}.

Needs: the read_members permission (in the app: “Members, their points, their email addresses and the last four characters of their reward codes”). A key with write_members can call it too.

Email is the only contact detail returned. The birthday a merchant's own staff screen can show is never returned here.

Parameters

NameInRequiredTypeWhat it is
member_keypathyesstring—

The answer

The member.

FieldTypeAlways thereWhat it is
member_keystringyes—
emailstringyesThe only contact detail this API returns.
balance_pointsintegeryesSummed from the ledger, not read from the balance cache.
enrolled_atstringyes—
last_activity_atstringyes—
points_expire_atstring, or nullyesNull when expiry is off, which is the default.
storeslist of objectyes—
stores[].store_keystringyes—
stores[].customer_refstringyesThe merchant's own customer identifier at that store.
stores[].linked_atstringyes—
points_by_storelist of objectyes—
points_by_store[].store_keystringyes—
points_by_store[].store_namestring, or nullyes—
points_by_store[].earned_pointsintegeryesPoints the shopper got at this store: orders, bonuses and credits from staff.
points_by_store[].spent_pointsintegeryesPoints exchanged for a reward at this store. Only redemptions: refunds, expiry and staff removals are in other_points.
points_by_store[].other_pointsintegeryesSigned. Every other movement at this store: points taken back on a refund, expired, removed by staff, returned when a reward was cancelled, or carried over when another of the shopper's store records was deleted. earned_points - spent_points + other_points is this store's net.
outstanding_codesintegeryes—
history_urlstringyes—

Headers on the answer: Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. What each header means.

Example

export MEMBER='<member_key from GET /members>'
curl "https://app.matrixrewards.co.uk/api/2026-10/members/$MEMBER" \
  -H "Authorization: Bearer $MATRIX_API_KEY"

The answer:

{
  "member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
  "email": "alex@example.com",
  "balance_points": 640,
  "enrolled_at": "2026-08-04T11:02:00Z",
  "last_activity_at": "2026-08-04T11:02:00Z",
  "points_expire_at": null,
  "stores": [
    {
      "store_key": "example-store",
      "customer_ref": "gid://shopify/Customer/7001",
      "linked_at": "2026-08-04T11:02:00Z"
    }
  ],
  "points_by_store": [
    {
      "store_key": "example-store",
      "store_name": "Example Store",
      "earned_points": 640,
      "spent_points": 0,
      "other_points": 0
    }
  ],
  "outstanding_codes": 1,
  "history_url": "/api/2026-10/ledger?member_key=mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18"
}

Other answers

StatusWhat it meansError pages
401unauthenticated: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer.unauthenticated
403forbidden_scope: the key does not carry the scope this call needs; required_scope says which.forbidden_scope
404not_found: no such resource on this account — indistinguishable from one that does not exist at all.not_found
429rate_limited: too many requests for this key; bucket says which allowance. Retry-After says how long to wait.rate_limited
503access_not_recorded: the read could not be written to the account's audit log, so no data was returned.access_not_recorded

The reward codes this shopper holds

GET /members/{member_key}/codes — the full address is https://app.matrixrewards.co.uk/api/2026-10/members/{member_key}/codes.

Needs: the read_members permission (in the app: “Members, their points, their email addresses and the last four characters of their reward codes”). A key with write_members can call it too.

Paged: a shopper who has redeemed for two years has a history, so this list is keyset-paged like the others. Each code is named by its last four characters and its ref, never whole — and so is the cursor, which is opaque but not secret.

Parameters

NameInRequiredTypeWhat it is
member_keypathyesstring—
statusquerynostring'minted' is issued and unspent.
limitquerynointeger, from 1 to 100, default 251 to 100. Larger values are capped.
cursorquerynostringOpaque. Pass back next_cursor from the previous page, with the same filters, and on GET /members the same sort and order.

The answer

The codes.

FieldTypeAlways thereWhat it is
datalist of objectyes—
data[].code_endingstring, or nullyesThe last four characters of the code: enough to match what the shopper reads out. Null when the code's final run of letters and digits is shorter than twelve, as a code your own connector recorded in a short or prefixed format can be, because its last four would be all of its random part; name it by ref. The whole code is never returned by this API — an unused code is a live discount, and API answers end up in logs and assistant transcripts.
data[].refstring, matching ^[0-9a-f]{20}$yesThe code's reference: what cancelMemberCode takes. It is derived from the redemption, never from the code, so it says nothing about the code.
data[].store_keystringyes—
data[].reward_idstringyesThe reward the code was bought with, as listRewards gives its id; @any-amount for a code the shopper bought by choosing how many points to spend (spend_any_amount). A merchant's reward id never starts with @.
data[].cost_pointsintegeryes—
data[].statusstringyes—
data[].issued_atstringyes—
has_morebooleanyes—
next_cursorstring, or nullyes—

Headers on the answer: Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. What each header means.

Example

export MEMBER='<member_key from GET /members>'
curl "https://app.matrixrewards.co.uk/api/2026-10/members/$MEMBER/codes" \
  -H "Authorization: Bearer $MATRIX_API_KEY"

The answer:

{
  "data": [
    {
      "code_ending": "4TPB",
      "ref": "5f0c2d8e91a47b36c0de",
      "store_key": "example-store",
      "reward_id": "reward_5",
      "cost_points": 500,
      "status": "minted",
      "issued_at": "2026-09-10T16:40:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Other answers

StatusWhat it meansError pages
400invalid_cursor: the cursor is not one this list issued.invalid_cursor
401unauthenticated: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer.unauthenticated
403forbidden_scope: the key does not carry the scope this call needs; required_scope says which.forbidden_scope
404not_found: no such resource on this account — indistinguishable from one that does not exist at all.not_found
429rate_limited: too many requests for this key; bucket says which allowance. Retry-After says how long to wait.rate_limited
503access_not_recorded: the read could not be written to the account's audit log, so no data was returned.access_not_recorded

Add points to a member's balance, or take them away

POST /members/{member_key}/adjustments — the full address is https://app.matrixrewards.co.uk/api/2026-10/members/{member_key}/adjustments.

Needs: the write_members permission (in the app: “Change members’ points and cancel reward codes. This also reads members, their email addresses and the last four characters of their reward codes. Every change is logged”).

Booked to the member's earliest-linked store on this account, with the reason on the shopper's statement. At most 100,000 points either way per change, and per account at most 100,000 points added and 100,000 taken away by points changes through the API in any rolling 24 hours, across all its keys (a cancelled code's points are not counted). A test key can preview and is refused the change (403 test_mode_key). Written to the audit log in the same transaction as the change: if the entry cannot be written, the change is not made.

Parameters

NameInRequiredTypeWhat it is
member_keypathyesstring—
Idempotency-Keyheaderyesstring, matching ^[A-Za-z0-9._:-]{1,100}$Required on both writes, a points change and a cancel. Pick a new one for each new change and send the same one to retry it. For 24 hours from its first use, a key has three answers: the same request again gets the first answer back, byte for byte, with Idempotency-Replayed: true; a different request under the key is 422 idempotency_key_reused; and while the first request is still running, 409 idempotency_key_in_use with Retry-After. A 5xx does not use the key up: retry with the same one. For a points change the key is also the adjustment's id in the ledger, so the same change is never booked twice, whichever of the account's keys sends it, and even after the 24 hours.
dry_runquerynobooleantrue previews the change: every check the real call makes runs, the answer has the status the real call would have, and nothing changes — except one dry_run entry in the audit log, because a preview shows the member's balance. When the change would happen, the preview carries a confirmation for exactly this request. It is the only query parameter a write takes: any other, a misspelling such as dryRun included, is refused with 400 invalid_request naming it in param, and nothing changes.
Matrix-Confirmationheadernostring, up to 1,024 charactersThe confirmation.token a dry run of this exact request returned. Required for keys tagged for an assistant, except on a replay: a request whose first answer is still kept, or a points change that is already made; checked whenever it is sent. It covers one request — the same key, member, body and Idempotency-Key — for 10 minutes. It proves the change was previewed first; it cannot prove a person saw the preview.

Request body

Sent as application/json.

FieldTypeRequiredWhat it is
delta_pointsinteger, from -100,000 to 100,000, not 0yesPoints to add (positive) or take away (negative). Not zero; at most 100,000 either way.
reasonstring, up to 200 characters, at least 1 characteryesWhy. Shown to the shopper on their statement, and kept in the audit log.

The answer

The change, or with dry_run=true the preview of it.

One shape: AdjustmentResult.

FieldTypeAlways thereWhat it is
objectalways adjustmentyes—
member_keystringyes—
delta_pointsintegeryes—
balance_pointsintegeryesThe member's balance afterwards.
store_keystringyesThe store the change is booked to: the member's earliest-linked store on this account.
replayedbooleanyesTrue when the ledger already held this change (the same Idempotency-Key, member and amount) and nothing new was booked.
idempotency_keystringyes—
audit_idstringyesThe audit-log entry this answer rests on.

One shape: AdjustmentPreview.

FieldTypeAlways thereWhat it is
objectalways adjustment_previewyes—
dry_runalways trueyes—
outcomeone of would_apply, would_replayyes—
member_keystringyes—
delta_pointsintegeryes—
balance_points_beforeintegeryes—
balance_points_afterintegeryesWhat the real call would report. Equal to before when it would replay.
store_keystringyes—
limitobject, or nullyesThe account's rolling 24-hour limit in this change's direction, before it. Null when the change would be a replay (a replay spends nothing).
limit.directionone of credit, debityes—
limit.limit_pointsintegeryes—
limit.used_pointsintegeryes—
limit.remaining_pointsintegeryes—
confirmationobject (Confirmation), or nullyesSend token back in Matrix-Confirmation with the same request (without dry_run). Null when nothing would change, or when the change would be a replay: a replay needs no confirmation, so send the request without one.
confirmation.tokenstringyes—
confirmation.expires_atstringyes—
audit_idstringyesThe preview's own audit-log entry.

Headers on the answer: Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; and, when it applies, Idempotency-Replayed. What each header means.

Example

export MEMBER='<member_key from GET /members>'
curl -X POST "https://app.matrixrewards.co.uk/api/2026-10/members/$MEMBER/adjustments" \
  -H "Authorization: Bearer $MATRIX_API_KEY" \
  -H "Idempotency-Key: goodwill-2026-09-16-0001" \
  -H "Content-Type: application/json" \
  -d '{"delta_points": 50, "reason": "Sorry about the late parcel"}'

The change, made:

{
  "object": "adjustment",
  "member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
  "delta_points": 50,
  "balance_points": 690,
  "store_key": "example-store",
  "replayed": false,
  "idempotency_key": "goodwill-2026-09-16-0001",
  "audit_id": "0d1e2f30-4a5b-4c6d-8e7f-00000000000d"
}

The same request with ?dry_run=true: nothing changes:

{
  "object": "adjustment_preview",
  "dry_run": true,
  "outcome": "would_apply",
  "member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
  "delta_points": 50,
  "balance_points_before": 640,
  "balance_points_after": 690,
  "store_key": "example-store",
  "limit": {
    "direction": "credit",
    "limit_points": 100000,
    "used_points": 0,
    "remaining_points": 100000
  },
  "confirmation": {
    "token": "mct_EXAMPLE.EXAMPLE",
    "expires_at": "2026-09-16T10:10:00.000Z"
  },
  "audit_id": "0d1e2f30-4a5b-4c6d-8e7f-00000000000b"
}

Other answers

StatusWhat it meansError pages
400invalid_request: a parameter or the body could not be read; param names it.invalid_request
401unauthenticated: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer.unauthenticated
403forbidden_scope: the key does not hold write_members (required_scope says so). test_mode_key: a test key can preview a change and never make one.forbidden_scope, test_mode_key
404not_found: no such member on this account, or (cancelling a code) the member holds no code with that ref.not_found
409idempotency_key_in_use: a request with this key is still running (Retry-After). credit_limit_reached: the account has moved 100,000 points through the API in this direction in the last 24 hours (direction, limit_points, used_points). member_has_no_store: the member is linked to no store to book the change to.idempotency_key_in_use, credit_limit_reached, member_has_no_store
413payload_too_large: the body is over 4,096 bytes.payload_too_large
422idempotency_key_reused: this Idempotency-Key was used for a different request in the last 24 hours.idempotency_key_reused
428confirmation_required: this key is tagged for an assistant; preview with dry_run=true and send the returned confirmation. confirmation_invalid: the confirmation is not for this request, has expired, or is not one we issued (reason).confirmation_required, confirmation_invalid
429rate_limited: too many requests for this key; bucket says which allowance. Retry-After says how long to wait.rate_limited
503change_not_recorded (or access_not_recorded for a preview or a replay): the audit log could not be written, so nothing was changed or returned. Retry with the same Idempotency-Key.change_not_recorded, access_not_recorded

Cancel a reward code and give the member back its points

POST /members/{member_key}/codes/void — the full address is https://app.matrixrewards.co.uk/api/2026-10/members/{member_key}/codes/void.

Needs: the write_members permission (in the app: “Change members’ points and cancel reward codes. This also reads members, their email addresses and the last four characters of their reward codes. Every change is logged”).

Switches the code off at the store it belongs to and credits its cost back — never one without the other. Name the code by its ref from listMemberCodes; the code itself is never sent or returned. The attempt is written to the audit log before Shopify is asked. A preview (dry_run=true) never contacts Shopify, so a store problem shows only on the real call. A cancel is not counted against the daily points limits on adjustMemberPoints: it gives back points the shopper already spent on the code.

Parameters

NameInRequiredTypeWhat it is
member_keypathyesstring—
Idempotency-Keyheaderyesstring, matching ^[A-Za-z0-9._:-]{1,100}$Required on both writes, a points change and a cancel. Pick a new one for each new change and send the same one to retry it. For 24 hours from its first use, a key has three answers: the same request again gets the first answer back, byte for byte, with Idempotency-Replayed: true; a different request under the key is 422 idempotency_key_reused; and while the first request is still running, 409 idempotency_key_in_use with Retry-After. A 5xx does not use the key up: retry with the same one. For a points change the key is also the adjustment's id in the ledger, so the same change is never booked twice, whichever of the account's keys sends it, and even after the 24 hours.
dry_runquerynobooleantrue previews the change: every check the real call makes runs, the answer has the status the real call would have, and nothing changes — except one dry_run entry in the audit log, because a preview shows the member's balance. When the change would happen, the preview carries a confirmation for exactly this request. It is the only query parameter a write takes: any other, a misspelling such as dryRun included, is refused with 400 invalid_request naming it in param, and nothing changes.
Matrix-Confirmationheadernostring, up to 1,024 charactersThe confirmation.token a dry run of this exact request returned. Required for keys tagged for an assistant, except on a replay: a request whose first answer is still kept, or a points change that is already made; checked whenever it is sent. It covers one request — the same key, member, body and Idempotency-Key — for 10 minutes. It proves the change was previewed first; it cannot prove a person saw the preview.

Request body

Sent as application/json.

FieldTypeRequiredWhat it is
refstring, matching ^[0-9a-f]{20}$yesThe code's ref, from listMemberCodes. The code itself is never sent: a write request's body passes through logs and assistant transcripts, and a live code is spendable.

The answer

The change, or with dry_run=true the preview of it.

One shape: VoidResult.

FieldTypeAlways thereWhat it is
objectalways code_voidyes—
resultone of voided, already_voided, nothing_to_voidyesvoided: switched off at Shopify and the points given back. already_voided: done before, not again. nothing_to_void: the code never held points to give back.
member_keystringyes—
code_endingstring, or nullyesThe last four characters of the code, or null for a code too short to show any of (see CodePage). The code itself is never returned.
store_keystring, or nullyes—
balance_pointsintegeryes—
audit_idstringyesThe audit-log entry written before Shopify was asked.

One shape: VoidPreview.

FieldTypeAlways thereWhat it is
objectalways code_void_previewyes—
dry_runalways trueyes—
outcomeone of would_void, already_voided, nothing_to_void, would_replayyes—
member_keystringyes—
code_endingstring, or nullyes—
store_keystring, or nullyes—
cost_pointsinteger, or nullyesWhat the code cost. Null when the answer would be a replay of a stored one.
balance_points_beforeintegeryes—
balance_points_afterintegeryes—
confirmationobject (Confirmation), or nullyesSend token back in Matrix-Confirmation with the same request (without dry_run). Null when nothing would change, or when the change would be a replay: a replay needs no confirmation, so send the request without one.
confirmation.tokenstringyes—
confirmation.expires_atstringyes—
audit_idstringyes—

Headers on the answer: Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; and, when it applies, Idempotency-Replayed. What each header means.

Example

export MEMBER='<member_key from GET /members>'
curl -X POST "https://app.matrixrewards.co.uk/api/2026-10/members/$MEMBER/codes/void" \
  -H "Authorization: Bearer $MATRIX_API_KEY" \
  -H "Idempotency-Key: cancel-2026-09-16-0001" \
  -H "Content-Type: application/json" \
  -d '{"ref": "5f0c2d8e91a47b36c0de"}'

The change, made:

{
  "object": "code_void",
  "result": "voided",
  "member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
  "code_ending": "4TPB",
  "store_key": "example-store",
  "balance_points": 1190,
  "audit_id": "0d1e2f30-4a5b-4c6d-8e7f-000000000011"
}

The same request with ?dry_run=true: nothing changes:

{
  "object": "code_void_preview",
  "dry_run": true,
  "outcome": "would_void",
  "member_key": "mx_3f6c2a9e-8b41-4d7a-9e05-c1b7d24f6a18",
  "code_ending": "4TPB",
  "store_key": "example-store",
  "cost_points": 500,
  "balance_points_before": 690,
  "balance_points_after": 1190,
  "confirmation": {
    "token": "mct_EXAMPLE.EXAMPLE",
    "expires_at": "2026-09-16T10:10:00.000Z"
  },
  "audit_id": "0d1e2f30-4a5b-4c6d-8e7f-00000000000f"
}

Other answers

StatusWhat it meansError pages
400invalid_request: a parameter or the body could not be read; param names it.invalid_request
401unauthenticated: no usable API key was presented. Malformed, unknown, revoked and wrong-secret are one answer.unauthenticated
403forbidden_scope: the key does not hold write_members (required_scope says so). test_mode_key: a test key can preview a change and never make one.forbidden_scope, test_mode_key
404not_found: no such member on this account, or (cancelling a code) the member holds no code with that ref.not_found
409idempotency_key_in_use: a request with this key is still running (Retry-After). code_already_used: the code was spent at checkout.idempotency_key_in_use, code_already_used
413payload_too_large: the body is over 4,096 bytes.payload_too_large
422idempotency_key_reused: this Idempotency-Key was used for a different request in the last 24 hours.idempotency_key_reused
428confirmation_required: this key is tagged for an assistant; preview with dry_run=true and send the returned confirmation. confirmation_invalid: the confirmation is not for this request, has expired, or is not one we issued (reason).confirmation_required, confirmation_invalid
429rate_limited: too many requests for this key; bucket says which allowance. Retry-After says how long to wait.rate_limited
502upstream_failed: Shopify did not confirm the code was switched off. Nothing was credited and the code is still live; retry with the same Idempotency-Key.upstream_failed
503change_not_recorded / access_not_recorded: as for a points change. store_not_connected: the code's store is not connected, so it cannot be switched off; nothing was changed.change_not_recorded, access_not_recorded, store_not_connected

Last updated: 14 October 2026