Skip to content

API changelog

The Charm API is versioned by date. The current version is 2026-10-01.

  • Additive changes ship continuously in the current version: new endpoints, new optional request fields, new response fields, new webhook topics and new error details. Build your client to ignore fields it does not know.

  • Breaking changes ship only in a new dated version, which you opt into. Nothing changes for your integration until you do.

  • A key answers in the version it was created with. Upgrade it in Settings → API (open the key, then Upgrade), or send the Charm-Version header to choose per request:

    Terminal window
    curl https://charm.appfleece.app/api/v1/shop \
    -H "Authorization: Bearer chrm_live_..." \
    -H "Charm-Version: 2026-10-01"
  • Webhook endpoints deliver in the version they were created with, shown as api_version on the endpoint and sent as the Charm-Version header on every delivery.

  • Every response carries Charm-Version, the version it was rendered in. An unknown version answers 422 validation_error with the live versions in details.allowed.

  • At most two versions are live. When a new one ships, the previous one keeps working and its retirement is announced here well in advance.

  • MCP tools always speak the current version; their schemas describe it.

Keys created before dated versions existed answer in 2026-10-01, the first version.

  • Changing a tier’s bar asks first: while VIP tiers are live, PATCH /program/tiers/{tier_id} with new criteria returns member_impact on the dry run and needs confirm_member_impact: true, like adding or removing a tier. A preview that stopped at its member limit asks for confirmation too.
  • Stricter program input: a social-follow or visit-a-link rule needs its url; a product or collection discount perk needs its target, and members-only access at least one page, product or collection; every product, collection and page a tier’s or membership’s perk names must exist in the shop. Partner UTM values are refused with a 422 instead of being rewritten (letters, digits, _, -, {}; up to 60).
  • fields.earnDenominator on the purchase rule round-trips: send back what GET returned.
  • Change log: target.kind also lists referral, membership and affiliate_group; the partner programme’s invoicing details are left out of a change’s diff; a campaign status change is not undoable (a campaign only moves forward).
  • MCP: tools/list leaves out output schemas, the points estimates are marked read-only, deletes destructive; a tools/call sent as a notification is not run, and every tool call appears in the developer console.
  • 409 confirmation_required now says to send a new idempotency key with the confirmed call.
  • Tier changes reach every member reliably: after a tier is added, removed or changed, members are re-evaluated in batches that pick up where they stopped, even across a restart. A member of a removed tier who qualifies above the tier below it is promoted as usual, gifts included.
  • program:write: an assistant or integration can now change the program itself, rule by rule. GET /program/rule-types describes every earning rule, reward and tier benefit type with the fields it takes; POST/PATCH/DELETE on /program/earn-rules and /program/rewards, PATCH /program/tiers/{tier_id} and PATCH /program/settings (names and expiry policies only). Validation comes from the same catalog the admin uses: a field outside its range is a 422 with the field path.
  • Dry runs and revisions on program writes: dry_run: true returns the exact diff and the admin’s summary line and writes nothing; if_revision refuses a write when the program changed since you read it (new error 409 revision_conflict). GET /program carries revision.
  • Change log and undo: GET /program/changes lists every rule, reward, tier and settings change made in the admin or through the API, with the actor and the field-level diff; POST /program/changes/{change_id}/undo puts one back.
  • Richer rule objects: EarningRule and Reward carry fields (the full configuration, as the write endpoints accept it), summary and title; Tier carries handle and benefits. Additive.
  • The program, tier and referral switches cannot be flipped through the API; those stay in the admin. Undo follows the same rule: a change that flipped a switch, or touched a setting outside PATCH /program/settings, is listed with undoable: false.
  • Reward.type now carries the reward type (it was always null); it equals discount_type.
  • Bonus campaigns: GET, POST, PATCH and DELETE /campaigns, with dry_run (including would_email, the members an enabled email phase would reach) and if_revision on the campaign. Campaigns created through the API send no email until a phase is switched on. Campaign changes appear in GET /program/changes and can be undone. New error 403 feature_disabled for shops without campaigns.
  • Titles in every language: title_translations on earning rules and rewards, name_translations on tiers, label_translations on tier benefits; GET /program/rule-types returns the shop’s languages.
  • Change history in the admin: Programs → Change history lists every program and campaign change, from the admin or the API, with undo.
  • Add and remove VIP tiers: POST /program/tiers (at position, default the top; the bar must climb) and DELETE /program/tiers/{tier_id} (not the entry tier; references lists rules and campaigns that name it). While tiers are live the dry run returns member_impact, and a write that moves members needs confirm_member_impact: true (new error 409 confirmation_required). Members of a removed tier are re-placed without a promotion email or tier gifts, in the admin too.
  • The referral program: GET /program/referrals and PATCH /program/referrals: the referral rule’s fields (both sides’ rewards and conditions), friend_discount (the friend’s first-order code; its minimum follows fields.referralMinimumOrderAmount) and code_prefix, with dry_run, if_revision, the change log (kind referral) and undo. The referral rule is no longer changed through PATCH /program/earn-rules/{rule_id}; that answers 422 pointing here. The switch stays in the admin.
  • Eligibility and e-mail templates: GET and PATCH /program/eligibility (audience, excluded tags, products, variants and collections checked in Shopify, gift cards, discounted items, subscription renewals, membership purchases, opt_in); GET /program/emails and PATCH /program/emails/{type} (one template on or off, with reach). Both write through the program settings save: dry_run, if_revision, change log and undo. The global e-mail switch stays in the admin.
  • Paid memberships: GET, POST, PATCH and DELETE /program/memberships. Created off sale, on an existing product or a new draft (create_product); the price is read from the product; billing and length are fixed once a membership has members; delete only off sale and never sold. Change log (kind membership) and undo, on the same save as the admin.
  • Partner programme: GET and PATCH /program/affiliates/settings (commission, buyer discount, commission base, holdback, attribution window, code prefix, terms, UTM; through the program settings save with change log and undo) and partner groups (GET, POST, PATCH, DELETE /program/affiliates/groups). 403 feature_disabled where the programme is not available. The switch, applications, approvals and payouts stay in the admin.
  • Connect an assistant with a one-time code: the admin’s Set up in chat card (onboarding and the home page) shows the MCP address and a one-time connection code; Charm’s OAuth approval page accepts the code instead of an API key. The connection runs on a key Charm manages (Chat assistant (Charm)), limited to shop:read, program:read, program:write and analytics:read, and works without the per-store API enablement. Changes it makes are logged under the assistant’s name.
  • Dated versions: the Charm-Version header, a version pinned on every key and webhook endpoint, and api_version on webhook endpoints. The first version is 2026-10-01; there are no breaking changes to act on.
  • Developer console in Settings → API: every request of the last 7 days with its status, error and request_id; webhook endpoints with their recent deliveries, Send again, test events and turning a switched-off endpoint back on; a Test key button. Test events carry "test": true in the payload and an event id starting with evt_test_.
  • Session tokens without a backend: POST /session-tokens/exchange trades a Shopify Customer Account API access token for a session token, and theme stores fetch one from /apps/charm/session-token. Headless storefronts.
  • Shopper self-serve actions with a session token: birthday, children, pets, claiming a way to earn, tier gifts, referral shares; variant_id on redemptions puts a gift product in the cart.
  • Referrals: POST /referrals/claim returns the friend’s discount and attributes the referral.
  • One call for a whole loyalty page: include=rewards,history,program,referral on GET /customers/{customer_id}.
  • AI agents: the UCP loyalty extension (AI agents and UCP) and cart points estimates with POST /estimates.
  • Contract: typed response schemas for every endpoint, every list as { data, next_cursor, has_more }, every write reporting changed, and a doc_url on every error pointing into the error catalog.
  • Webhook payloads: customer_id is now the customer GID, the id every endpoint accepts, with the numeric id in customer_legacy_id. (No endpoint was subscribed at the time.)
  • Customer session tokens for browsers and apps (overview).
  • Product points estimates, the explicit join for join-gated programs (POST /customers/{customer_id}/enroll) and next_tier on customers.
  • Outbound webhooks, OAuth for AI assistant connectors and GET /analytics/summary.