Add a VIP tier
POST /program/tiers| Scope | program:write |
| Customer session token | Not accepted: call from your server with an API key |
| Rate limit class | write_shopify (limits) |
| Idempotency key | Required (idempotency) |
| MCP tool | charm_create_tier |
Adds a tier to the ladder with a name, a bar (criteria), a points multiplier and benefits, at position (default: the top). The bar must climb: a tier may not ask for less than the one below it. Its icon follows the program’s icon style. While VIP tiers are off nobody moves. While they are live, members who now qualify move into it: the dry run’s member_impact shows how many move and which gift codes that issues, and the real call needs confirm_member_impact: true whenever anyone moves. Call with dry_run: true first.
Request body
Section titled “Request body”| Field | Type | Description |
|---|---|---|
name | string | In the shop’s primary language. |
name_translations | object | The name in other published languages; null removes one, omitted ones stay. A value for the primary language sets name. (optional) |
points_multiplier | number | 1 = base rate; 1.5 = 50% more points. (optional) |
criteria | array of object | The bar to reach the tier: { type: "points" | "spend" | "orders" | "referrals", threshold } or { type: "tag", value }, each type at most once. |
combine | string: any, all | How several criteria combine. Defaults to any. (optional) |
benefits | array of object | (optional) |
position | integer | Where in the ladder: 1 = right above the entry tier. Defaults to the top. The bar must still climb at that position. (optional) |
confirm_member_impact | boolean | Required, as true, when VIP tiers are live and the change moves members or issues tier gift codes. Run a dry run first and show the merchant member_impact. (optional) |
dry_run | boolean | Preview only: validate, resolve references and return the diff and summary, but write nothing. Costs no idempotency key and is limited as a read. Call with this first, show the merchant, then repeat without it. (optional) |
if_revision | integer | The program revision you last read (from GET /program or a previous write). When the program changed since, the write is refused with 409 revision_conflict instead of overwriting a concurrent admin edit. (optional) |
Response 201
Section titled “Response 201”Returns ProgramWriteResult.
| Field | Type | Description |
|---|---|---|
dry_run | boolean | True when nothing was written: the diff shows what the same call without dry_run would do. |
revision | integer | The program revision after the write (unchanged on a dry run). Send it back as if_revision on the next write. |
operation | string: create, update, delete, none | |
diff | array of FieldChange | |
summary | string or null | The admin’s one-line summary of the rule after the change. (nullable) |
earning_rule | EarningRule or null | (optional, nullable) |
reward | Reward or null | (optional, nullable) |
tier | Tier or null | (optional, nullable) |
program | Program or null | (optional, nullable) |
position | integer | Tier writes: the tier’s place in the ladder, 0 = the entry tier. (optional) |
tiers_enabled | boolean | Tier writes: whether VIP tiers are live. While they are off, adding or removing a tier moves nobody. (optional) |
member_impact | TierMemberImpact or null | (optional, nullable) |
references | array of object | Removing a tier: rules and live campaigns that name it. They keep working, but their per-tier part no longer applies to anyone. (optional) |
{ "dry_run": false, "revision": 1, "operation": "create", "diff": [ { "path": "path", "before": null, "after": null } ], "summary": "summary", "earning_rule": { "id": "id", "type": "earn", "enabled": true, "label": "Points per order", "points": 120, "points_per_currency": 120, "limit": 1, "title": "title", "title_translations": {}, "fields": {}, "summary": "summary" }, "reward": { "id": "id", "type": "earn", "enabled": true, "name": "Gold", "points_cost": 120, "discount_type": "percentage", "discount_value": 1, "code_valid_days": 1, "title": "title", "title_translations": {}, "fields": {}, "summary": "summary" }, "tier": { "id": "id", "name": "Gold", "handle": "handle", "name_translations": {}, "threshold": 1, "points_multiplier": 120, "combine": "any", "criteria": [ null ], "benefits": [ null ] }, "program": { "enabled": true, "program_name": "Rewards Club", "points_name": "points", "points_expiry": { "enabled": null, "months": null }, "reward_expiry": { "enabled": null, "code_valid_days": null }, "eligibility": { "customer_audience": null, "excluded_customer_tags": null, "excluded_product_tags": null }, "referral": { "enabled": true, "code_prefix": "REF", "discount_type": "percentage", "discount_value": 1, "minimum_purchase": 1 }, "memberships_enabled": false, "tiers_enabled": false, "earning_rules": [ null ], "redemption_rules": [ null ], "revision": 1, "updated_at": "2026-10-01T09:30:00.000Z" }, "position": 1, "tiers_enabled": false, "member_impact": { "members_evaluated": 1, "promoted": 1, "demoted": 1, "replaced": 1, "gift_codes": 1, "gifts": [ { "tier_id": null, "tier_name": null, "label": null, "codes": null } ], "per_tier": [ { "tier_id": null, "name": null, "members": null } ], "members_in_removed_tier": 1, "truncated": false }, "references": [ { "kind": "earning_rule", "id": "id", "label": "Points per order" } ]}Example
Section titled “Example”curl -X POST "https://charm.appfleece.app/api/v1/program/tiers" \ -H "Authorization: Bearer chrm_live_..." \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"name":"Gold","criteria":[{"type":"points","threshold":1,"value":"value"}]}'Errors
Section titled “Errors”unauthorized · invalid_token · insufficient_scope · access_required · validation_error · rate_limited · idempotency_conflict · shopify_unavailable
Every error body carries code, message, request_id and a doc_url pointing at the matching entry in the error catalog.