Skip to content

Errors

Every failure answers with the same body on the REST API, and the same facts as text in MCP tool results:

{
"error": {
"code": "insufficient_points",
"message": "Customer has 120 points; the reward costs 500.",
"details": { "balance": 120, "required": 500 },
"request_id": "req_abc123",
"doc_url": "https://docs.charmloyalty.com/api/errors#insufficient-points"
}
}
  • Branch on code, never on message. Messages are for humans and may be reworded; codes are stable.
  • details says what to change when there is something to change (the field, the allowed values, the balance).
  • request_id is also in the X-Request-Id header. Quote it to support: it is how we find the request in our logs.
  • Only 4xx codes that describe the shopper’s situation (insufficient_points, some validation_errors) are worth showing to a shopper. Everything else is a signal for the integration.

401 unauthorized. No bearer credential arrived, or the Authorization header is not in the form Bearer <token>.

Fix: send Authorization: Bearer chrm_live_…. The word Bearer and a space are part of the value; pasting the bare key into a header field (a common slip in Gorgias, Zapier and Make) produces this error.

401 invalid_token. The key, OAuth token or session token is unknown, malformed or revoked. A customer session token also answers this when the key that minted it has been revoked.

Fix: check which key you are using in Settings → API; rotate or create a new one. For session tokens, mint a new token from your server.

401 token_expired. A customer session token is past its lifetime.

Fix: fetch a fresh token from your server and retry the request once. Do not retry more than once: a second token_expired means the minting side is broken.

403 insufficient_scope. The credential is valid but lacks the scope the endpoint needs (details.required_scope). For session tokens it also means the endpoint does not accept session tokens (details.session_token: true), the token lacks read or write, or the path names another customer.

Fix: create a key with the scope (scopes are exact: points:write does not imply points:read at request time), or call a server-only endpoint from your server with an API key.

403 access_required. API access is not switched on for this store yet.

Fix: ask us in the in-app support chat (Settings → API has a button) or at hello@appfleece.com. The API is available on every plan, free included.

403 api_paused. The merchant paused all API traffic in Settings → API.

Fix: switch the API back on in Settings → API. Keys and tokens are not revoked by a pause.

403 feature_disabled. The endpoint belongs to a feature the shop has not switched on: bonus campaigns, or the partner programme (every /program/affiliates endpoint). details.feature names it.

Fix: the merchant asks Charm support to enable the feature; the API cannot switch it on.

404 not_found. The path or the resource does not exist for this shop: an unknown customer, reward, redemption or webhook endpoint, or a path the API does not have.

Fix: check the id. Customer ids are Shopify customer GIDs (gid://shopify/Customer/123, percent-encoded in a path) or the numeric id. An unknown customer is never enrolled implicitly by a read.

405 method_not_allowed. The path exists but not with this HTTP method. The Allow header and details.allowed_methods list the methods it supports.

409 idempotency_conflict. The Idempotency-Key was already used with a different request, or the first request with that key is still running.

Fix: use a fresh key (a UUID) for every new operation, and the same key only to retry the same request.

409 duplicate_request. With Idempotency-Key: auto, an identical request ran less than a minute ago and was not repeated.

Fix: wait a minute, or change something about the request if you really mean a second operation.

409 revision_conflict. A program write carried if_revision, and the program’s revision moved on since — someone saved it in the admin, or another assistant changed it. Also answered when undoing a change whose target changed again since, or that was undone already. Nothing was written. details.current_revision carries the live revision.

Fix: read the program again (GET /program, or the rule’s current state), decide whether your change still makes sense, and retry with the current revision.

409 confirmation_required. Adding or removing a VIP tier, or changing a tier’s bar, while tiers are live would move members between tiers: someone is promoted or demoted, the members of a removed tier are placed in a remaining one, or promotions issue tier gift codes. Nothing was written. details.member_impact carries the same counts a dry run returns.

Fix: show the merchant member_impact (how many move, which gift codes are issued), and once they agree, repeat the call with confirm_member_impact: true and a new idempotency key (the first key now answers with this 409). A preview that had to stop at its member limit also asks for confirmation, since it cannot vouch for everyone.

422 validation_error. The request is well-formed but a value is wrong: missing or invalid field, bad cursor, limit out of range, a disabled program, an excluded customer, a program without a join gate. details names the field and, where useful, the allowed values.

Fix: correct the field named in details. The message says what the API expected.

422 insufficient_points. The customer’s balance does not cover the redemption or deduction.

Fix: show the shopper their balance and the reward’s cost. Nothing was charged.

429 rate_limited. A rate limit was hit. The Retry-After header says how many seconds to wait.

Fix: wait Retry-After seconds, then retry. Cache reads (a minute is plenty for balances) and invalidate with webhooks.

500 internal_error. Something failed on our side. The body never contains internal details.

Fix: retry later with the same idempotency key (a retry cannot repeat a write that partly happened). If it persists, send us the request_id.

503 shopify_unavailable. The operation needs Shopify (minting a discount code, writing a metafield) and Shopify is throttled or unreachable for this store, or the store’s Shopify budget is running low and we shed the request to protect order processing.

Fix: retry after Retry-After with the same idempotency key.