Skip to content

Idempotency and approvals.

Repeat an Agent API write safely with an Idempotency-Key header, and handle the 202 answer when a change waits for the owner's approval.

Oct 23, 2026
On this page

This page is for developers who call the Agent API over HTTP. It covers two safety features for writes: idempotency keys, which make a retry safe, and approvals, which hold a risky change until the owner agrees. A 202 means: give review_url to the owner, then call execute with the approval's id and nonce. MCP adds approval requirements for some operations that run immediately over HTTP. See Which writes wait below for the distinction. See Limits, errors and retries and Approvals for that view.

Idempotency keys

Send an Idempotency-Key header with a write. If the call times out, repeat it with the same key. Reward Loyalty then returns the first answer instead of making the change twice.

curl -X POST https://your-domain.com/api/agent/v1/partner/clubs \
  -H "Authorization: Bearer rl_agent_..." \
  -H "Idempotency-Key: 7d3a9c2e-1f4b-4e8a-9a51-0c6b2f8d3e10" \
  -H "Content-Type: application/json" \
  -d '{"name": "Main Street"}'

The header is optional over HTTP. Over MCP the same value is the required idempotency_key argument.

Rule Behavior
Format 1 to 80 characters from A-Z, a-z, 0-9, hyphen and underscore. Anything else returns 422 INVALID_IDEMPOTENCY_KEY.
Same key, same request Returns the stored answer with the original status and the header Idempotent-Replayed: true. Nothing runs again. A stored 202 returns the same approval.
Same key, different request Returns 409 IDEMPOTENCY_CONFLICT. Use a new key for a new request.
Same key, first request still running Returns 409 IDEMPOTENCY_IN_PROGRESS with Retry-After: 1. Retry with the same key. A request that stops without finishing releases its key. Only when its process was killed outright does a later call wait 15 minutes to take the key over.
Failed request Only successful answers are stored. After a failure the key is free, so you can retry with it.
Where a key applies One credential and one operation (method and route). The same key sent for another record of that operation returns 409 IDEMPOTENCY_CONFLICT.
Recheck A replay is refused with 403 AUTH_INSUFFICIENT_SCOPE or 403 FEATURE_DISABLED when the credential has since lost the scope or the plan feature. Nothing runs.
Retention Reward Loyalty keeps stored answers for 7 days, encrypted. A scheduled task removes older ones.

What counts as "the same request" is the method, the path, the path parameters, the query and the body, compared by content. The order of JSON keys doesn't matter.

Two kinds of write keep their own retry rule:

  • Stored-money operations (selling a stored-money pass, spending from it, reversing a spend) take a UUID operation key in the same header.
  • A purchase sent with a source_reference uses that reference. See the production checklist.

Reads never need a key. Reward Loyalty ignores the header on them.

Approvals

Some writes don't run when you call them. They answer 202 Accepted with an approval and wait until the owner approves the change on a review page in Reward Loyalty.

Which writes wait

  • Every operation marked confirm in the OpenAPI spec (x-agent-safety) always waits, over HTTP and over MCP. Deleting a business, deactivating a business (admin_deactivate_partner), moving a business to another plan and sending an email campaign are examples. The tool tables show them as Waits for approval.
  • Deletes and stored-money moves marked destructive wait over MCP only. Over HTTP they run at once.
  • A few operations wait only for certain input. The spec marks them with x-agent-confirm-when:
Operation When it waits
admin_update_settings, admin_reset_setting The change touches a sensitive setting.
update_lifecycle_automations The change switches on an emailing automation.
update_club, update_voucher The change reactivates a club or voucher that an automation depends on.
update_tier The change moves members while tier emails or tier vouchers would reach them.
create_voucher, update_voucher The voucher would be emailed to tier members at once.
update_achievement_program The change starts or resumes the achievements program.

Handle a 202 from any write.

The 202 answer

{
  "error": false,
  "approval": {
    "id": "01a1b2c3-d4e5-7f60-8a9b-0c1d2e3f4a5b",
    "status": "pending",
    "operation": "delete_club",
    "summary": "Delete a club",
    "preview": { "...": "..." },
    "expires_at": "2026-10-21T09:14:03+00:00",
    "seconds_left": 600,
    "review_url": "https://your-domain.com/en-us/partner/approvals/01a1b2c3-...",
    "nonce": "…",
    "next_step": "Give the user the review_url. After they approve, call the execute approval tool with this id and nonce."
  }
}

Nothing has changed yet. Give review_url to the owner. The nonce is a secret that only the credential that prepared the approval receives. Keep it for the next step. The approval lasts 10 minutes from this moment, and the owner's decision doesn't extend it.

The preview names the people a change concerns only when the credential may read members. Otherwise Reward Loyalty drops the names and the figures that go with them.

Read an approval

GET /api/agent/v1/partner/approvals
GET /api/agent/v1/partner/approvals/{id}
GET /api/agent/v1/admin/approvals
GET /api/agent/v1/admin/approvals/{id}

An approval belongs to the credential that prepared it. Another credential, including another key of the same owner, gets 404. The list takes a status filter. Reading needs any scope that allows changes.

Run an approved change

POST /api/agent/v1/partner/approvals/{id}/execute
POST /api/agent/v1/admin/approvals/{id}/execute

Send the nonce:

{ "nonce": "…" }

Reward Loyalty checks again that the connection and its owner are active, the scope and plan still allow the change, and the record is as the owner reviewed it. It then runs the original request once. A successful answer carries the approval and the original result:

{
  "error": false,
  "approval": { "id": "01a1b2c3-...", "status": "executed" },
  "result": { "error": false, "data": { "...": "..." } }
}

If the owner hasn't decided, the call returns 409 APPROVAL_PENDING with retry_strategy: backoff. Poll the approval, or wait for the owner. A rejected approval returns 409 APPROVAL_REJECTED. After the window it returns 410 APPROVAL_EXPIRED. If the record changed, it returns 409 APPROVAL_STALE. Prepare the change again after an expired or stale approval.

A wrong nonce, or an approval that belongs to another credential, returns 404 NOT_FOUND.

If the approved run is refused with a 4xx, the answer carries the operation's own error and the approval. Nothing changed, so the approval stays usable until it expires and you can fix the call. Some failures may have done part of the work: a run that fails with a 5xx, a change that reached an outside system such as a store and failed there, and a run that stopped before it recorded an outcome. Their approval closes with APPROVAL_STALE, and running the change again needs a new approval from the owner.

Send an idempotency key with the execute call to get the stored result back when you repeat it. Without one, a repeat returns 409 APPROVAL_USED.

Who decides

Only the owner of the key decides, on the review page. The decision needs a signed-in owner of the right role. A key can't approve its own request. Approvals describes the page.