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 onmessage. Messages are for humans and may be reworded; codes are stable. detailssays what to change when there is something to change (the field, the allowed values, the balance).request_idis also in theX-Request-Idheader. Quote it to support: it is how we find the request in our logs.- Only
4xxcodes that describe the shopper’s situation (insufficient_points, somevalidation_errors) are worth showing to a shopper. Everything else is a signal for the integration.
Unauthorized
Section titled “Unauthorized”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.
Invalid token
Section titled “Invalid token”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.
Token expired
Section titled “Token expired”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.
Insufficient scope
Section titled “Insufficient scope”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.
Access required
Section titled “Access required”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.
API paused
Section titled “API paused”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.
Feature disabled
Section titled “Feature disabled”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.
Not found
Section titled “Not found”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.
Method not allowed
Section titled “Method not allowed”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.
Idempotency conflict
Section titled “Idempotency conflict”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.
Duplicate request
Section titled “Duplicate 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.
Revision conflict
Section titled “Revision conflict”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.
Confirmation required
Section titled “Confirmation required”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.
Validation error
Section titled “Validation error”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.
Insufficient points
Section titled “Insufficient points”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.
Rate limited
Section titled “Rate limited”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.
Internal error
Section titled “Internal error”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.
Shopify unavailable
Section titled “Shopify unavailable”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.