Create a bonus campaign
POST /campaigns| Scope | program:write |
| Customer session token | Not accepted: call from your server with an API key |
| Rate limit class | write (limits) |
| Idempotency key | Required (idempotency) |
| MCP tool | charm_create_campaign |
Creates a campaign such as “double points this weekend on the Summer collection”. It starts as a draft unless status: "scheduled" (a scheduled one turns active on its own when its window opens). Emails stay off unless the request switches a phase on. Call with dry_run: true first, show the merchant the campaign, the diff and would_email, then repeat without it.
Request body
Section titled “Request body”| Field | Type | Description |
|---|---|---|
name | string | Shown to shoppers on the storefront banner and in campaign emails. No links. |
template | string: custom, black_friday, mothers_day, member_anniversary, win_back, valentines, holiday_season, flash_weekend, new_collection, vip_exclusive, reward_sale, exclusive_reward | Start from a preset (multiplier, stacking, audience). Its dates and emails are not used: send starts_at/ends_at, and emails stay off unless switched on. (optional) |
type | string: earning_boost, reward_spotlight | earning_boost (default) multiplies points earned; reward_spotlight puts rewards on sale or unlocks them for the window. (optional) |
multiplier | number | Points multiplier while it runs, e.g. 2 for double points. Above 1 to go live; at most 10. (optional) |
stacking_mode | string: compound, max, replace | With the VIP tier multiplier: multiply both (compound, default), take the higher (max), or use the campaign’s (replace, never below the tier’s). (optional) |
starts_at | string or null | ISO 8601 with a time zone, e.g. 2026-10-03T00:00:00+02:00. (optional, nullable) |
ends_at | string or null | (optional, nullable) |
rule_types | array of string: order_placed, account_created, newsletter_signup, sms_signup, birthday, referral, product_review, follow_instagram, follow_tiktok, follow_x, like_facebook, share_facebook, share_x, visit_url | The ways to earn it boosts (default order_placed). Each must be a way to earn the shop has switched on. (optional) |
product_scope | object | Which purchases count (order rules only): { mode: all | include | exclude, product_ids, collection_ids, tags }. Ids are numeric or GIDs and must exist. (optional) |
audience | object | Who it applies to: { mode: all | tier | tag, tier_ids, tags }. (optional) |
limits | object | Caps, null for none: { max_orders, max_bonus_points }. (optional) |
badge_text | string or null | Short badge on product points, e.g. 2×. (optional, nullable) |
banner_copy | object | Storefront banner wording per published language: { "cs": { "title": "…", "subtitle": "…" } }; null removes a language. (optional) |
spotlight_rewards | array of object | For reward_spotlight: [{ reward_id, sale_points_cost }]; a sale price below the reward’s cost, or null to only unlock a disabled reward. (optional) |
emails | object | Switch the campaign emails on or off: { teaser, launch, last_chance } booleans. Off unless switched on. An enabled phase mails the whole audience when it is due — check would_email in a dry run and confirm with the merchant first. (optional) |
status | string: draft, scheduled | draft (default) or scheduled. A scheduled campaign turns active on its own when its window opens. (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 CampaignWriteResult.
| Field | Type | Description |
|---|---|---|
dry_run | boolean | True when nothing was written. |
revision | integer | The campaign’s revision after the write (unchanged on a dry run). |
operation | string: create, update, delete, none | |
diff | array of FieldChange | |
campaign | Campaign or null | (nullable) |
would_email | object or null | (nullable) |
{ "dry_run": false, "revision": 1, "operation": "create", "diff": [ { "path": "path", "before": null, "after": null } ], "campaign": { "id": "id", "name": "Gold", "type": "earning_boost", "template": "template", "status": "draft", "multiplier": 1, "stacking_mode": "stacking mode", "starts_at": "2026-10-01T09:30:00.000Z", "ends_at": "2026-10-01T09:30:00.000Z", "rule_types": [ "rule types" ], "product_scope": { "mode": "all", "product_ids": [ null ], "collection_ids": [ null ], "tags": [ null ] }, "audience": { "mode": "all", "tier_ids": [ null ], "tags": [ null ] }, "limits": { "max_orders": 1, "max_bonus_points": 120 }, "consumed": { "orders": 1, "bonus_points": 120 }, "badge_text": "badge text", "banner_copy": {}, "spotlight_rewards": [ { "reward_id": null, "sale_points_cost": null } ], "emails": { "teaser": null, "launch": null, "last_chance": null }, "deletable": false, "revision": 1, "created_at": "2026-10-01T09:30:00.000Z", "updated_at": "2026-10-01T09:30:00.000Z" }, "would_email": { "phases": [ "phases" ], "recipients": 1 }}Example
Section titled “Example”curl -X POST "https://charm.appfleece.app/api/v1/campaigns" \ -H "Authorization: Bearer chrm_live_..." \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"name":"Gold"}'Errors
Section titled “Errors”unauthorized · invalid_token · insufficient_scope · access_required · validation_error · rate_limited · idempotency_conflict
Every error body carries code, message, request_id and a doc_url pointing at the matching entry in the error catalog.