Loyalty programs

GET /loyalty-programs

List the organization's loyalty programs, alphabetically by default. Pass include=counts for what each program holds, which is what a deletion would take with it.

Requires the loyalty:read permission.

Query Parameters

limitnumber

The number of records to return.

cursorstring

A pagination cursor. Fetch the next page by passing the nextCursor value from the previous response.

sortname | -name | createdAt | -createdAt

The sort order. Prefix a field with - to sort descending. Defaults to name.

includestring

A comma-separated list of extra fields to compute and return. Each one costs an additional query, so ask only for what you need. Available: counts.

filterstring

A JSON-encoded filter document, for example {"createdAt":{"$gte":"2025-01-01T00:00:00Z"}}. Filterable fields: id, name, createdAt. See Filtering for the syntax.

Request
curl \
  "https://api.cascade.dev/loyalty-programs" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "string",
      "autoMembership": "off",
      "settings": {
        "loyaltyEmails": "...",
        "previousPointsNames": "..."
      },
      "sectionsEnabled": {
        "rules": "...",
        "offers": "...",
        "stampCards": "..."
      },
      "pointsNames": {
        "singular": "...",
        "plural": "..."
      },
      "pointsExpiresAfterDays": 0,
      "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
      "rewardsExpireAfterDays": 0,
      "expiringSoonDays": 0,
      "pausedAt": "2025-01-15T09:30:00Z",
      "publishedAt": "2025-01-15T09:30:00Z",
      "createdAt": "2025-01-15T09:30:00Z",
      "updatedAt": "2025-01-15T09:30:00Z",
      "counts": {
        "rules": "...",
        "tiers": "...",
        "members": "...",
        "stampCardTypes": "...",
        "rewards": "...",
        "sites": "..."
      }
    }
  ],
  "nextCursor": "string"
}

POST /loyalty-programs

Create a loyalty program. Name its points and it can hand them out, hold balances and offer things to spend them on; leave pointsNames out and it runs on coupons and stamp cards alone. The program is attached to every site the organization has, and to any site added later. It starts as a draft, so nothing is visible to shoppers until you call POST /loyalty-programs/{id}/publish. Triggers the loyaltyProgram.created webhook.

Requires the loyalty:write permission.

Body Parameters

dataobjectrequired

The program to create.

Request
curl \
  -X POST \
  "https://api.cascade.dev/loyalty-programs" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "data": {
    "name": "string",
    "autoMembership": "off",
    "sectionsEnabled": {
      "rules": true,
      "offers": true,
      "stampCards": true
    },
    "pointsNames": {
      "singular": "string",
      "plural": "string"
    },
    "pointsExpiresAfterDays": 0,
    "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
    "rewardsExpireAfterDays": 0,
    "expiringSoonDays": 0
  }
}'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "string",
  "autoMembership": "off",
  "settings": {
    "loyaltyEmails": {
      "welcome": {
        "enabled": true,
        "emailTemplateId": null
      },
      "pointsEarned": {
        "enabled": false,
        "emailTemplateId": null
      },
      "rewardsAvailable": {
        "enabled": true,
        "emailTemplateId": null
      },
      "redeemed": {
        "enabled": true,
        "emailTemplateId": null
      },
      "rewardExpiring": {
        "enabled": true,
        "emailTemplateId": null
      },
      "pointsExpiring": {
        "enabled": true,
        "emailTemplateId": null
      }
    },
    "previousPointsNames": null
  },
  "sectionsEnabled": {
    "rules": true,
    "offers": true,
    "stampCards": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0,
  "pausedAt": "2025-01-15T09:30:00Z",
  "publishedAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z"
}

GET /loyalty-programs/{id}

Get a single loyalty program by ID.

Requires the loyalty:read permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program.

Query Parameters

includestring

A comma-separated list of extra fields to compute and return. Each one costs an additional query, so ask only for what you need. Available: counts.

Request
curl \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "string",
  "autoMembership": "off",
  "settings": {
    "loyaltyEmails": {
      "welcome": {
        "enabled": true,
        "emailTemplateId": null
      },
      "pointsEarned": {
        "enabled": false,
        "emailTemplateId": null
      },
      "rewardsAvailable": {
        "enabled": true,
        "emailTemplateId": null
      },
      "redeemed": {
        "enabled": true,
        "emailTemplateId": null
      },
      "rewardExpiring": {
        "enabled": true,
        "emailTemplateId": null
      },
      "pointsExpiring": {
        "enabled": true,
        "emailTemplateId": null
      }
    },
    "previousPointsNames": null
  },
  "sectionsEnabled": {
    "rules": true,
    "offers": true,
    "stampCards": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0,
  "pausedAt": "2025-01-15T09:30:00Z",
  "publishedAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z",
  "counts": {
    "rules": 0,
    "tiers": 0,
    "members": 0,
    "stampCardTypes": 0,
    "rewards": 0,
    "sites": 0
  }
}

PUT /loyalty-programs/{id}

Update a loyalty program's name, enrollment behavior, points, visible sections, paused state or settings. Pausing stops the program earning and leaves every balance, reward and activity entry where it is. Setting pointsNames to null switches points off and keeps the names in settings.previousPointsNames, so they can be sent back to switch points on again. settings.loyaltyEmails holds the switch and template for each member email; send only the keys you want to change. Triggers the loyaltyProgram.updated webhook.

Requires the loyalty:write permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program to update.

Body Parameters

dataobjectrequired

The fields to update.

Request
curl \
  -X PUT \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "data": {
    "name": "string",
    "autoMembership": "off",
    "sectionsEnabled": {
      "rules": true,
      "offers": true,
      "stampCards": true
    },
    "pointsNames": {
      "singular": "string",
      "plural": "string"
    },
    "pointsExpiresAfterDays": 0,
    "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
    "rewardsExpireAfterDays": 0,
    "expiringSoonDays": 0,
    "paused": true,
    "settings": {
      "loyaltyEmails": {
        "welcome": "...",
        "pointsEarned": "...",
        "rewardsAvailable": "...",
        "redeemed": "...",
        "rewardExpiring": "...",
        "pointsExpiring": "..."
      }
    }
  }
}'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "string",
  "autoMembership": "off",
  "settings": {
    "loyaltyEmails": {
      "welcome": {
        "enabled": true,
        "emailTemplateId": null
      },
      "pointsEarned": {
        "enabled": false,
        "emailTemplateId": null
      },
      "rewardsAvailable": {
        "enabled": true,
        "emailTemplateId": null
      },
      "redeemed": {
        "enabled": true,
        "emailTemplateId": null
      },
      "rewardExpiring": {
        "enabled": true,
        "emailTemplateId": null
      },
      "pointsExpiring": {
        "enabled": true,
        "emailTemplateId": null
      }
    },
    "previousPointsNames": null
  },
  "sectionsEnabled": {
    "rules": true,
    "offers": true,
    "stampCards": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0,
  "pausedAt": "2025-01-15T09:30:00Z",
  "publishedAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z"
}

DELETE /loyalty-programs/{id}

Permanently delete a loyalty program. Its rules, tiers, stamp card types, memberships, balances and issued rewards go with it, and the sites it is attached to stop showing it. To stop a program earning without losing any of that, pause it instead. The reason is required and is kept in the audit trail along with who deleted it. Triggers the loyaltyProgram.deleted webhook.

Requires the loyalty:write permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program to delete.

Body Parameters

reasonclosing_program | duplicate | replaced | test_data | otherrequired

Why the program is being deleted. Kept in the organization's audit trail.

notestring

Anything worth remembering about the deletion. Required when the reason is other.

Request
curl \
  -X DELETE \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "closing_program",
  "note": "string"
}'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "string",
  "autoMembership": "off",
  "settings": {
    "loyaltyEmails": {
      "welcome": {
        "enabled": true,
        "emailTemplateId": null
      },
      "pointsEarned": {
        "enabled": false,
        "emailTemplateId": null
      },
      "rewardsAvailable": {
        "enabled": true,
        "emailTemplateId": null
      },
      "redeemed": {
        "enabled": true,
        "emailTemplateId": null
      },
      "rewardExpiring": {
        "enabled": true,
        "emailTemplateId": null
      },
      "pointsExpiring": {
        "enabled": true,
        "emailTemplateId": null
      }
    },
    "previousPointsNames": null
  },
  "sectionsEnabled": {
    "rules": true,
    "offers": true,
    "stampCards": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0,
  "pausedAt": "2025-01-15T09:30:00Z",
  "publishedAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z"
}

POST /loyalty-programs/{id}/publish

Publish a draft loyalty program, which is what makes it visible to shoppers. A new program starts as a draft: nothing enrolls, earns or shows up in the storefront widget until this is called. Publishing cannot be undone, so pause the program if you want to take it down later. Publishing a program with no rules, or one no site shows, is allowed and comes back with a warning saying it will not do anything visible yet. Calling it on a program that is already published changes nothing. Triggers the loyaltyProgram.updated webhook.

Requires the loyalty:write permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program to publish.

Request
curl \
  -X POST \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/publish" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "string",
    "autoMembership": "off",
    "settings": {
      "loyaltyEmails": {
        "welcome": {
          "enabled": true,
          "emailTemplateId": null
        },
        "pointsEarned": {
          "enabled": false,
          "emailTemplateId": null
        },
        "rewardsAvailable": {
          "enabled": true,
          "emailTemplateId": null
        },
        "redeemed": {
          "enabled": true,
          "emailTemplateId": null
        },
        "rewardExpiring": {
          "enabled": true,
          "emailTemplateId": null
        },
        "pointsExpiring": {
          "enabled": true,
          "emailTemplateId": null
        }
      },
      "previousPointsNames": null
    },
    "sectionsEnabled": {
      "rules": true,
      "offers": true,
      "stampCards": true
    },
    "pointsNames": {
      "singular": "string",
      "plural": "string"
    },
    "pointsExpiresAfterDays": 0,
    "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
    "rewardsExpireAfterDays": 0,
    "expiringSoonDays": 0,
    "pausedAt": "2025-01-15T09:30:00Z",
    "publishedAt": "2025-01-15T09:30:00Z",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "warnings": [
    {
      "code": "noRules",
      "message": "string"
    }
  ]
}

POST /loyalty-programs/{id}/preview

Mint a preview link for a loyalty program, which is how a draft is checked before it is published. The link opens the site's loyalty page carrying a short-lived signed token, and the storefront widget shows the program to whoever follows it, published or not. Anyone holding the link can read the program, so it is only ever as private as where you paste it. The preview is read only: nobody can join or claim through it. A program attached to no site still gets a token, but there is nowhere to open it.

Requires the loyalty:write permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program to preview.

Request
curl \
  -X POST \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/preview" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "token": "string",
  "expiresAt": "2025-01-15T09:30:00Z",
  "sites": [
    {
      "siteId": "550e8400-e29b-41d4-a716-446655440000",
      "siteName": "string",
      "siteSlug": "string",
      "url": "string"
    }
  ]
}

GET /loyalty-programs/{id}/sites

List the sites a loyalty program is attached to, along with the page on each one that shows it. A program is attached to every site when it is created, and a new site is attached to every program, so this is normally the organization's whole site list. The storefront widget resolves a shopper's program through it, and a site with nothing attached falls back to every program in the organization.

Requires the loyalty:read permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program.

Request
curl \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "siteId": "550e8400-e29b-41d4-a716-446655440000",
      "siteName": "string",
      "siteSlug": "string",
      "loyaltyProgramId": "550e8400-e29b-41d4-a716-446655440000",
      "programUrl": "string",
      "createdAt": "2025-01-15T09:30:00Z"
    }
  ]
}

POST /loyalty-programs/{id}/sites

Attach a loyalty program to one of the organization's sites. Every program is attached to every site by default, so this only does something for a site that was detached through the API. Attaching the same program twice fails with a conflict.

Requires the loyalty:write permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program.

Body Parameters

siteIdstring (uuid)required

The ID of the site to attach the program to.

Request
curl \
  -X POST \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "siteId": "550e8400-e29b-41d4-a716-446655440000"
}'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "siteId": "550e8400-e29b-41d4-a716-446655440000",
  "siteName": "string",
  "siteSlug": "string",
  "loyaltyProgramId": "550e8400-e29b-41d4-a716-446655440000",
  "programUrl": "string",
  "createdAt": "2025-01-15T09:30:00Z"
}

PATCH /loyalty-programs/{id}/sites/{siteId}

Set the page on a site that shows the loyalty program. The links in the member emails point at it. Leave it empty and they point at the site's base URL instead, which is where the launcher embed usually sits.

Requires the loyalty:write permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program.

siteIdstring (uuid)required

The ID of the site the program is attached to.

Body Parameters

programUrlstring (uri) | nullrequired

The page that shows the program, such as https://shop.example.com/pages/rewards. Null clears it.

Request
curl \
  -X PATCH \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "programUrl": "https://example.com"
}'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "siteId": "550e8400-e29b-41d4-a716-446655440000",
  "siteName": "string",
  "siteSlug": "string",
  "loyaltyProgramId": "550e8400-e29b-41d4-a716-446655440000",
  "programUrl": "string",
  "createdAt": "2025-01-15T09:30:00Z"
}

DELETE /loyalty-programs/{id}/sites/{siteId}

Detach a loyalty program from a site. Nothing moves: memberships, balances and rewards belong to the program rather than the site, so the site simply stops showing it. The loyalty page URL set on the link goes with it. The admin has no control for this: programs apply to every site unless you detach one here.

Requires the loyalty:write permission.

Path Parameters

idstring (uuid)required

The ID of the loyalty program.

siteIdstring (uuid)required

The ID of the site to detach it from.

Request
curl \
  -X DELETE \
  "https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "siteId": "550e8400-e29b-41d4-a716-446655440000",
  "siteName": "string",
  "siteSlug": "string",
  "loyaltyProgramId": "550e8400-e29b-41d4-a716-446655440000",
  "programUrl": "string",
  "createdAt": "2025-01-15T09:30:00Z"
}