Safe retries and previews
Send the same change twice and it is made once. Ask for a preview and nothing changes.
Both apply to the two calls that change a member: POST /members/{member_key}/adjustments and POST /members/{member_key}/codes/void.
Idempotency-Key
Every points change and every cancel carries an Idempotency-Key header: a name you choose for that one change, of up to 100 letters, digits, dots, underscores, colons and hyphens. The reference describes it like this:
In short:
- Use a new key for each new change.
- To retry a change, send exactly the same request with the same key.
- Never use a new key to retry a change you are unsure about. A new key is a new change: if the first one was made, you have now made it twice.
The three answers a key can have, besides the first one:
- the same request again: the first answer, with
Idempotency-Replayed: true; - a different request with a key already used: idempotency_key_reused;
- the same request while the first is still running: idempotency_key_in_use.
Creating a webhook endpoint takes no Idempotency-Key. POST /webhooks says why, and what to do after an answer that went missing.
Preview a change
Add ?dry_run=true to either call. The reference says of dry_run:
A points change's preview says in outcome what the real call would do: would_apply or would_replay. A cancel's preview says would_void, already_voided, nothing_to_void or would_replay. And of the preview's confirmation:
Test keys
A test key walks the whole flow for real: its preview is a real preview and it can send the confirmation back, but the change itself is refused with test_mode_key. Nothing changes.
Keys for AI assistants
A key you marked as being for an AI assistant cannot make a change in one call. It has to send the Matrix-Confirmation header, which the reference describes like this:
So a change takes four steps:
- Choose a new
Idempotency-Keyand keep it. - Send the change with
?dry_run=true. - Show the person the preview, and wait for a clear yes.
- Send the identical request, without
dry_run, with the preview'sconfirmation.tokenin theMatrix-Confirmationheader, within 10 minutes.
Both steps are in your activity log. What the rule proves is that a change was previewed first. It cannot prove a person saw the preview: that is the assistant's job. And it does not protect you from a stolen key, because a thief can preview and confirm too. Against that, a key has its permissions, its limits, the log, and revoking.
A worked example
Set the member's key first, in place of the words in angle brackets, then preview the change. Choose a new Idempotency-Key for each change you make; the one here is an example.
export MEMBER='<member_key from GET /members>'
curl -X POST "https://app.matrixrewards.co.uk/api/2026-10/members/$MEMBER/adjustments?dry_run=true" \
-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 preview:
{
"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"
}
Then make the change: the same request, without dry_run, with the preview's confirmation.token in Matrix-Confirmation. Only a key for an assistant has to send it, but any key may, and it is then checked.
export CONFIRMATION='<confirmation.token from the preview>'
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 "Matrix-Confirmation: $CONFIRMATION" \
-H "Content-Type: application/json" \
-d '{"delta_points": 50, "reason": "Sorry about the late parcel"}'
The answer:
{
"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"
}
Send that second request again and you get the same answer back, with Idempotency-Replayed: true, and the points move once.