Skip to content

Add a reward

POST /program/rewards
Scopeprogram:write
Customer session tokenNot accepted: call from your server with an API key
Rate limit classwrite_shopify (limits)
Idempotency keyRequired (idempotency)
MCP toolcharm_create_reward

Adds a reward shoppers can spend points on, as the admin’s “Add reward” does. A product or collection the reward points at must exist in the shop (422 otherwise). Fields you omit take the catalog defaults; availability makes it seasonal. Call with dry_run: true first and show the merchant the diff and summary.

FieldTypeDescription
typestringThe reward type, one of GET /program/rule-types?kind=reward.
fieldsobjectThe reward’s configuration, keyed as GET /program/rule-types?kind=reward documents for the type. Omitted keys take the catalog defaults; unknown keys and out-of-range values are a 422 with the field path. (optional)
enabledbooleanDefaults to true. (optional)
titlestring or nullThe merchant’s own title shown to shoppers instead of the catalog’s, in the shop’s primary language. Null clears it. (optional, nullable)
title_translationsobjectThe title in other languages: { "sk": "Recenzia s fotkou" }. Keys are the shop’s published languages (GET /program/rule-types lists them under languages); null removes one, languages you omit keep theirs. A value for the primary language sets title. (optional)
dry_runbooleanPreview 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_revisionintegerThe 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)

Returns ProgramWriteResult.

FieldTypeDescription
dry_runbooleanTrue when nothing was written: the diff shows what the same call without dry_run would do.
revisionintegerThe program revision after the write (unchanged on a dry run). Send it back as if_revision on the next write.
operationstring: create, update, delete, none
diffarray of FieldChange
summarystring or nullThe admin’s one-line summary of the rule after the change. (nullable)
earning_ruleEarningRule or null(optional, nullable)
rewardReward or null(optional, nullable)
tierTier or null(optional, nullable)
programProgram or null(optional, nullable)
positionintegerTier writes: the tier’s place in the ladder, 0 = the entry tier. (optional)
tiers_enabledbooleanTier writes: whether VIP tiers are live. While they are off, adding or removing a tier moves nobody. (optional)
member_impactTierMemberImpact or null(optional, nullable)
referencesarray of objectRemoving 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"
}
]
}
Terminal window
curl -X POST "https://charm.appfleece.app/api/v1/program/rewards" \
-H "Authorization: Bearer chrm_live_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"type":"earn"}'

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.