#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
limitnumberThe number of records to return.
cursorstringA pagination cursor. Fetch the next page by passing the nextCursor value from the previous response.
sortname | -name | createdAt | -createdAtThe sort order. Prefix a field with - to sort descending. Defaults to name.
includestringA 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.
filterstringA JSON-encoded filter document, for example {"createdAt":{"$gte":"2025-01-01T00:00:00Z"}}. Filterable fields: id, name, createdAt. See Filtering for the syntax.
curl \
"https://api.cascade.dev/loyalty-programs" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getLoyaltyPrograms } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getLoyaltyPrograms()require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/loyalty-programs",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the loyalty program."
},
"name": {
"type": "string",
"description": "The program's display name, shown to members."
},
"autoMembership": {
"type": "string",
"enum": ["off", "onFirstActivity"],
"description": "When customers are enrolled in the program automatically. `off` means they join only when enrolled explicitly; `onFirstActivity` enrolls them the first time they do something the program rewards."
},
"settings": {
"type": "object",
"properties": {
"loyaltyEmails": {
"default": {
"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
}
},
"description": "Which member emails this program sends, and the template each one renders.",
"type": "object",
"properties": {
"welcome": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when they join the program.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsEarned": {
"default": {
"enabled": false,
"emailTemplateId": null
},
"description": "The email a customer gets when they earn points. One email covers everything a single order or visit earned, however many rules paid out. Off by default: it is the noisiest of these emails.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardsAvailable": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when a reward is issued to them.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"redeemed": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email carrying a customer's reward code when they spend points on a reward.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before a reward they hold expires. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before their points expire. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
}
},
"required": [
"welcome",
"pointsEarned",
"rewardsAvailable",
"redeemed",
"rewardExpiring",
"pointsExpiring"
],
"additionalProperties": false
},
"previousPointsNames": {
"default": null,
"description": "The names the points had before they were switched off, kept so switching them back on restores them. Null when points have never been switched off.",
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
]
}
},
"required": ["loyaltyEmails", "previousPointsNames"],
"additionalProperties": false,
"description": "Program-level configuration: which member emails go out and with which template, and the points names kept from before points were switched off."
},
"sectionsEnabled": {
"type": "object",
"properties": {
"rules": {
"type": "boolean",
"description": "Whether the ways-to-earn section is shown to members."
},
"offers": {
"type": "boolean",
"description": "Whether the offers section is shown to members."
},
"stampCards": {
"type": "boolean",
"description": "Whether the stamp cards section is shown to members."
}
},
"required": ["rules", "offers", "stampCards"],
"additionalProperties": false,
"description": "Which optional sections members see in the loyalty widget."
},
"pointsNames": {
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
],
"description": "What the program calls its points, singular and plural. Null means the program has no points, so nothing earns, spends or displays them."
},
"pointsExpiresAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Each earn expires this many days after it happened. Null means earns never age out. When `pointsBalancesExpireAt` is also set, the earlier of the two applies."
},
"pointsBalancesExpireAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "Every points balance in the program expires at this moment, the event-tickets case. Null means balances have no fixed end date. When `pointsExpiresAfterDays` is also set, the earlier of the two applies."
},
"rewardsExpireAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Rewards issued in this program lapse this many days after a member gets them. Null means they stay redeemable until they are used. A rule that sets its own expiry wins over this."
},
"expiringSoonDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "How many days of warning members get before a reward or a points balance lapses. Null means no warning goes out."
},
"pausedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was paused. While it is set the program earns nothing, and members keep their balances, rewards and history."
},
"publishedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was published. Null means it is still a draft: no shopper can see it, join it or earn in it, and only a preview link reaches it. Publishing cannot be undone, so pause a program you want to take down."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was last changed."
},
"counts": {
"type": "object",
"properties": {
"rules": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Earning and redemption rules in the program."
},
"tiers": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Tiers, archived ones included."
},
"members": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Members enrolled in the program."
},
"stampCardTypes": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Stamp card types defined on the program."
},
"rewards": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Rewards the program has issued, in any state."
},
"sites": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Sites the program is currently attached to."
}
},
"required": ["rules", "tiers", "members", "stampCardTypes", "rewards", "sites"],
"additionalProperties": false,
"description": "What belongs to the program, and so what a deletion takes with it. Each count is a separate query."
}
},
"required": [
"id",
"name",
"autoMembership",
"settings",
"sectionsEnabled",
"pointsNames",
"pointsExpiresAfterDays",
"pointsBalancesExpireAt",
"rewardsExpireAfterDays",
"expiringSoonDays",
"pausedAt",
"publishedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
"description": "The page of records, in the requested sort order."
},
"nextCursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Pass this back as `cursor` to fetch the next page. Null once the last page has been returned."
}
},
"required": ["data", "nextCursor"],
"additionalProperties": false
}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
dataobjectrequiredThe program to create.
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
}
}'import { client } from "@cascade-commerce/api/admin/client"
import { postLoyaltyPrograms } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postLoyaltyPrograms({
body: {
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,
},
},
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"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
}
}
JSON
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
body = """{
"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
}
}"""
req = Request(
"https://api.cascade.dev/loyalty-programs",
data=body.encode(),
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the loyalty program."
},
"name": {
"type": "string",
"description": "The program's display name, shown to members."
},
"autoMembership": {
"type": "string",
"enum": ["off", "onFirstActivity"],
"description": "When customers are enrolled in the program automatically. `off` means they join only when enrolled explicitly; `onFirstActivity` enrolls them the first time they do something the program rewards."
},
"settings": {
"type": "object",
"properties": {
"loyaltyEmails": {
"default": {
"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
}
},
"description": "Which member emails this program sends, and the template each one renders.",
"type": "object",
"properties": {
"welcome": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when they join the program.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsEarned": {
"default": {
"enabled": false,
"emailTemplateId": null
},
"description": "The email a customer gets when they earn points. One email covers everything a single order or visit earned, however many rules paid out. Off by default: it is the noisiest of these emails.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardsAvailable": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when a reward is issued to them.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"redeemed": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email carrying a customer's reward code when they spend points on a reward.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before a reward they hold expires. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before their points expire. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
}
},
"required": [
"welcome",
"pointsEarned",
"rewardsAvailable",
"redeemed",
"rewardExpiring",
"pointsExpiring"
],
"additionalProperties": false
},
"previousPointsNames": {
"default": null,
"description": "The names the points had before they were switched off, kept so switching them back on restores them. Null when points have never been switched off.",
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
]
}
},
"required": ["loyaltyEmails", "previousPointsNames"],
"additionalProperties": false,
"description": "Program-level configuration: which member emails go out and with which template, and the points names kept from before points were switched off."
},
"sectionsEnabled": {
"type": "object",
"properties": {
"rules": {
"type": "boolean",
"description": "Whether the ways-to-earn section is shown to members."
},
"offers": {
"type": "boolean",
"description": "Whether the offers section is shown to members."
},
"stampCards": {
"type": "boolean",
"description": "Whether the stamp cards section is shown to members."
}
},
"required": ["rules", "offers", "stampCards"],
"additionalProperties": false,
"description": "Which optional sections members see in the loyalty widget."
},
"pointsNames": {
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
],
"description": "What the program calls its points, singular and plural. Null means the program has no points, so nothing earns, spends or displays them."
},
"pointsExpiresAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Each earn expires this many days after it happened. Null means earns never age out. When `pointsBalancesExpireAt` is also set, the earlier of the two applies."
},
"pointsBalancesExpireAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "Every points balance in the program expires at this moment, the event-tickets case. Null means balances have no fixed end date. When `pointsExpiresAfterDays` is also set, the earlier of the two applies."
},
"rewardsExpireAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Rewards issued in this program lapse this many days after a member gets them. Null means they stay redeemable until they are used. A rule that sets its own expiry wins over this."
},
"expiringSoonDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "How many days of warning members get before a reward or a points balance lapses. Null means no warning goes out."
},
"pausedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was paused. While it is set the program earns nothing, and members keep their balances, rewards and history."
},
"publishedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was published. Null means it is still a draft: no shopper can see it, join it or earn in it, and only a preview link reaches it. Publishing cannot be undone, so pause a program you want to take down."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was last changed."
}
},
"required": [
"id",
"name",
"autoMembership",
"settings",
"sectionsEnabled",
"pointsNames",
"pointsExpiresAfterDays",
"pointsBalancesExpireAt",
"rewardsExpireAfterDays",
"expiringSoonDays",
"pausedAt",
"publishedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The new program."
}GET /loyalty-programs/{id}
Get a single loyalty program by ID.
Requires the loyalty:read permission.
Path Parameters
idstring (uuid)requiredThe ID of the loyalty program.
Query Parameters
includestringA 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.
curl \
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getLoyaltyProgramsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getLoyaltyProgramsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"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
}
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the loyalty program."
},
"name": {
"type": "string",
"description": "The program's display name, shown to members."
},
"autoMembership": {
"type": "string",
"enum": ["off", "onFirstActivity"],
"description": "When customers are enrolled in the program automatically. `off` means they join only when enrolled explicitly; `onFirstActivity` enrolls them the first time they do something the program rewards."
},
"settings": {
"type": "object",
"properties": {
"loyaltyEmails": {
"default": {
"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
}
},
"description": "Which member emails this program sends, and the template each one renders.",
"type": "object",
"properties": {
"welcome": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when they join the program.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsEarned": {
"default": {
"enabled": false,
"emailTemplateId": null
},
"description": "The email a customer gets when they earn points. One email covers everything a single order or visit earned, however many rules paid out. Off by default: it is the noisiest of these emails.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardsAvailable": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when a reward is issued to them.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"redeemed": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email carrying a customer's reward code when they spend points on a reward.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before a reward they hold expires. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before their points expire. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
}
},
"required": [
"welcome",
"pointsEarned",
"rewardsAvailable",
"redeemed",
"rewardExpiring",
"pointsExpiring"
],
"additionalProperties": false
},
"previousPointsNames": {
"default": null,
"description": "The names the points had before they were switched off, kept so switching them back on restores them. Null when points have never been switched off.",
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
]
}
},
"required": ["loyaltyEmails", "previousPointsNames"],
"additionalProperties": false,
"description": "Program-level configuration: which member emails go out and with which template, and the points names kept from before points were switched off."
},
"sectionsEnabled": {
"type": "object",
"properties": {
"rules": {
"type": "boolean",
"description": "Whether the ways-to-earn section is shown to members."
},
"offers": {
"type": "boolean",
"description": "Whether the offers section is shown to members."
},
"stampCards": {
"type": "boolean",
"description": "Whether the stamp cards section is shown to members."
}
},
"required": ["rules", "offers", "stampCards"],
"additionalProperties": false,
"description": "Which optional sections members see in the loyalty widget."
},
"pointsNames": {
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
],
"description": "What the program calls its points, singular and plural. Null means the program has no points, so nothing earns, spends or displays them."
},
"pointsExpiresAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Each earn expires this many days after it happened. Null means earns never age out. When `pointsBalancesExpireAt` is also set, the earlier of the two applies."
},
"pointsBalancesExpireAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "Every points balance in the program expires at this moment, the event-tickets case. Null means balances have no fixed end date. When `pointsExpiresAfterDays` is also set, the earlier of the two applies."
},
"rewardsExpireAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Rewards issued in this program lapse this many days after a member gets them. Null means they stay redeemable until they are used. A rule that sets its own expiry wins over this."
},
"expiringSoonDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "How many days of warning members get before a reward or a points balance lapses. Null means no warning goes out."
},
"pausedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was paused. While it is set the program earns nothing, and members keep their balances, rewards and history."
},
"publishedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was published. Null means it is still a draft: no shopper can see it, join it or earn in it, and only a preview link reaches it. Publishing cannot be undone, so pause a program you want to take down."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was last changed."
},
"counts": {
"type": "object",
"properties": {
"rules": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Earning and redemption rules in the program."
},
"tiers": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Tiers, archived ones included."
},
"members": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Members enrolled in the program."
},
"stampCardTypes": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Stamp card types defined on the program."
},
"rewards": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Rewards the program has issued, in any state."
},
"sites": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Sites the program is currently attached to."
}
},
"required": ["rules", "tiers", "members", "stampCardTypes", "rewards", "sites"],
"additionalProperties": false,
"description": "What belongs to the program, and so what a deletion takes with it. Each count is a separate query."
}
},
"required": [
"id",
"name",
"autoMembership",
"settings",
"sectionsEnabled",
"pointsNames",
"pointsExpiresAfterDays",
"pointsBalancesExpireAt",
"rewardsExpireAfterDays",
"expiringSoonDays",
"pausedAt",
"publishedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The requested loyalty program."
}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)requiredThe ID of the loyalty program to update.
Body Parameters
dataobjectrequiredThe fields to update.
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": "..."
}
}
}
}'import { client } from "@cascade-commerce/api/admin/client"
import { putLoyaltyProgramsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await putLoyaltyProgramsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
body: {
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: "...",
},
},
},
},
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"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": "..."
}
}
}
}
JSON
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
body = """{
"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": "..."
}
}
}
}"""
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000",
data=body.encode(),
method="PUT",
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the loyalty program."
},
"name": {
"type": "string",
"description": "The program's display name, shown to members."
},
"autoMembership": {
"type": "string",
"enum": ["off", "onFirstActivity"],
"description": "When customers are enrolled in the program automatically. `off` means they join only when enrolled explicitly; `onFirstActivity` enrolls them the first time they do something the program rewards."
},
"settings": {
"type": "object",
"properties": {
"loyaltyEmails": {
"default": {
"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
}
},
"description": "Which member emails this program sends, and the template each one renders.",
"type": "object",
"properties": {
"welcome": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when they join the program.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsEarned": {
"default": {
"enabled": false,
"emailTemplateId": null
},
"description": "The email a customer gets when they earn points. One email covers everything a single order or visit earned, however many rules paid out. Off by default: it is the noisiest of these emails.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardsAvailable": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when a reward is issued to them.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"redeemed": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email carrying a customer's reward code when they spend points on a reward.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before a reward they hold expires. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before their points expire. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
}
},
"required": [
"welcome",
"pointsEarned",
"rewardsAvailable",
"redeemed",
"rewardExpiring",
"pointsExpiring"
],
"additionalProperties": false
},
"previousPointsNames": {
"default": null,
"description": "The names the points had before they were switched off, kept so switching them back on restores them. Null when points have never been switched off.",
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
]
}
},
"required": ["loyaltyEmails", "previousPointsNames"],
"additionalProperties": false,
"description": "Program-level configuration: which member emails go out and with which template, and the points names kept from before points were switched off."
},
"sectionsEnabled": {
"type": "object",
"properties": {
"rules": {
"type": "boolean",
"description": "Whether the ways-to-earn section is shown to members."
},
"offers": {
"type": "boolean",
"description": "Whether the offers section is shown to members."
},
"stampCards": {
"type": "boolean",
"description": "Whether the stamp cards section is shown to members."
}
},
"required": ["rules", "offers", "stampCards"],
"additionalProperties": false,
"description": "Which optional sections members see in the loyalty widget."
},
"pointsNames": {
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
],
"description": "What the program calls its points, singular and plural. Null means the program has no points, so nothing earns, spends or displays them."
},
"pointsExpiresAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Each earn expires this many days after it happened. Null means earns never age out. When `pointsBalancesExpireAt` is also set, the earlier of the two applies."
},
"pointsBalancesExpireAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "Every points balance in the program expires at this moment, the event-tickets case. Null means balances have no fixed end date. When `pointsExpiresAfterDays` is also set, the earlier of the two applies."
},
"rewardsExpireAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Rewards issued in this program lapse this many days after a member gets them. Null means they stay redeemable until they are used. A rule that sets its own expiry wins over this."
},
"expiringSoonDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "How many days of warning members get before a reward or a points balance lapses. Null means no warning goes out."
},
"pausedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was paused. While it is set the program earns nothing, and members keep their balances, rewards and history."
},
"publishedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was published. Null means it is still a draft: no shopper can see it, join it or earn in it, and only a preview link reaches it. Publishing cannot be undone, so pause a program you want to take down."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was last changed."
}
},
"required": [
"id",
"name",
"autoMembership",
"settings",
"sectionsEnabled",
"pointsNames",
"pointsExpiresAfterDays",
"pointsBalancesExpireAt",
"rewardsExpireAfterDays",
"expiringSoonDays",
"pausedAt",
"publishedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The updated loyalty program."
}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)requiredThe ID of the loyalty program to delete.
Body Parameters
reasonclosing_program | duplicate | replaced | test_data | otherrequiredWhy the program is being deleted. Kept in the organization's audit trail.
notestringAnything worth remembering about the deletion. Required when the reason is other.
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"
}'import { client } from "@cascade-commerce/api/admin/client"
import { deleteLoyaltyProgramsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteLoyaltyProgramsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
body: {
reason: "closing_program",
note: "string",
},
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"reason": "closing_program",
"note": "string"
}
JSON
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
body = """{
"reason": "closing_program",
"note": "string"
}"""
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000",
data=body.encode(),
method="DELETE",
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the loyalty program."
},
"name": {
"type": "string",
"description": "The program's display name, shown to members."
},
"autoMembership": {
"type": "string",
"enum": ["off", "onFirstActivity"],
"description": "When customers are enrolled in the program automatically. `off` means they join only when enrolled explicitly; `onFirstActivity` enrolls them the first time they do something the program rewards."
},
"settings": {
"type": "object",
"properties": {
"loyaltyEmails": {
"default": {
"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
}
},
"description": "Which member emails this program sends, and the template each one renders.",
"type": "object",
"properties": {
"welcome": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when they join the program.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsEarned": {
"default": {
"enabled": false,
"emailTemplateId": null
},
"description": "The email a customer gets when they earn points. One email covers everything a single order or visit earned, however many rules paid out. Off by default: it is the noisiest of these emails.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardsAvailable": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when a reward is issued to them.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"redeemed": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email carrying a customer's reward code when they spend points on a reward.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before a reward they hold expires. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before their points expire. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
}
},
"required": [
"welcome",
"pointsEarned",
"rewardsAvailable",
"redeemed",
"rewardExpiring",
"pointsExpiring"
],
"additionalProperties": false
},
"previousPointsNames": {
"default": null,
"description": "The names the points had before they were switched off, kept so switching them back on restores them. Null when points have never been switched off.",
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
]
}
},
"required": ["loyaltyEmails", "previousPointsNames"],
"additionalProperties": false,
"description": "Program-level configuration: which member emails go out and with which template, and the points names kept from before points were switched off."
},
"sectionsEnabled": {
"type": "object",
"properties": {
"rules": {
"type": "boolean",
"description": "Whether the ways-to-earn section is shown to members."
},
"offers": {
"type": "boolean",
"description": "Whether the offers section is shown to members."
},
"stampCards": {
"type": "boolean",
"description": "Whether the stamp cards section is shown to members."
}
},
"required": ["rules", "offers", "stampCards"],
"additionalProperties": false,
"description": "Which optional sections members see in the loyalty widget."
},
"pointsNames": {
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
],
"description": "What the program calls its points, singular and plural. Null means the program has no points, so nothing earns, spends or displays them."
},
"pointsExpiresAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Each earn expires this many days after it happened. Null means earns never age out. When `pointsBalancesExpireAt` is also set, the earlier of the two applies."
},
"pointsBalancesExpireAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "Every points balance in the program expires at this moment, the event-tickets case. Null means balances have no fixed end date. When `pointsExpiresAfterDays` is also set, the earlier of the two applies."
},
"rewardsExpireAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Rewards issued in this program lapse this many days after a member gets them. Null means they stay redeemable until they are used. A rule that sets its own expiry wins over this."
},
"expiringSoonDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "How many days of warning members get before a reward or a points balance lapses. Null means no warning goes out."
},
"pausedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was paused. While it is set the program earns nothing, and members keep their balances, rewards and history."
},
"publishedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was published. Null means it is still a draft: no shopper can see it, join it or earn in it, and only a preview link reaches it. Publishing cannot be undone, so pause a program you want to take down."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was last changed."
}
},
"required": [
"id",
"name",
"autoMembership",
"settings",
"sectionsEnabled",
"pointsNames",
"pointsExpiresAfterDays",
"pointsBalancesExpireAt",
"rewardsExpireAfterDays",
"expiringSoonDays",
"pausedAt",
"publishedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The deleted program."
}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)requiredThe ID of the loyalty program to publish.
curl \
-X POST \
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/publish" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { postLoyaltyProgramsByIdPublish } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postLoyaltyProgramsByIdPublish({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/publish")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/publish",
method="POST",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}
]
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the loyalty program."
},
"name": {
"type": "string",
"description": "The program's display name, shown to members."
},
"autoMembership": {
"type": "string",
"enum": ["off", "onFirstActivity"],
"description": "When customers are enrolled in the program automatically. `off` means they join only when enrolled explicitly; `onFirstActivity` enrolls them the first time they do something the program rewards."
},
"settings": {
"type": "object",
"properties": {
"loyaltyEmails": {
"default": {
"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
}
},
"description": "Which member emails this program sends, and the template each one renders.",
"type": "object",
"properties": {
"welcome": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when they join the program.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsEarned": {
"default": {
"enabled": false,
"emailTemplateId": null
},
"description": "The email a customer gets when they earn points. One email covers everything a single order or visit earned, however many rules paid out. Off by default: it is the noisiest of these emails.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardsAvailable": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email a customer gets when a reward is issued to them.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"redeemed": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The email carrying a customer's reward code when they spend points on a reward.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"rewardExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before a reward they hold expires. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
},
"pointsExpiring": {
"default": {
"enabled": true,
"emailTemplateId": null
},
"description": "The warning a customer gets before their points expire. It only ever sends when the program sets a warning window.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this email goes out at all."
},
"emailTemplateId": {
"anyOf": [
{
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
{
"type": "null"
}
],
"description": "The email template to render, which has to be one of this email's type. Null uses the organization's oldest template of that type, or the packaged default when there is none."
}
},
"required": ["enabled", "emailTemplateId"],
"additionalProperties": false
}
},
"required": [
"welcome",
"pointsEarned",
"rewardsAvailable",
"redeemed",
"rewardExpiring",
"pointsExpiring"
],
"additionalProperties": false
},
"previousPointsNames": {
"default": null,
"description": "The names the points had before they were switched off, kept so switching them back on restores them. Null when points have never been switched off.",
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
]
}
},
"required": ["loyaltyEmails", "previousPointsNames"],
"additionalProperties": false,
"description": "Program-level configuration: which member emails go out and with which template, and the points names kept from before points were switched off."
},
"sectionsEnabled": {
"type": "object",
"properties": {
"rules": {
"type": "boolean",
"description": "Whether the ways-to-earn section is shown to members."
},
"offers": {
"type": "boolean",
"description": "Whether the offers section is shown to members."
},
"stampCards": {
"type": "boolean",
"description": "Whether the stamp cards section is shown to members."
}
},
"required": ["rules", "offers", "stampCards"],
"additionalProperties": false,
"description": "Which optional sections members see in the loyalty widget."
},
"pointsNames": {
"anyOf": [
{
"type": "object",
"properties": {
"singular": {
"type": "string",
"minLength": 1,
"description": "The name of one point, such as `point`."
},
"plural": {
"type": "string",
"minLength": 1,
"description": "The name of several points, such as `points`."
}
},
"required": ["singular", "plural"],
"additionalProperties": false,
"description": "Unit names a balance is composed from, for example `12 points`."
},
{
"type": "null"
}
],
"description": "What the program calls its points, singular and plural. Null means the program has no points, so nothing earns, spends or displays them."
},
"pointsExpiresAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Each earn expires this many days after it happened. Null means earns never age out. When `pointsBalancesExpireAt` is also set, the earlier of the two applies."
},
"pointsBalancesExpireAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "Every points balance in the program expires at this moment, the event-tickets case. Null means balances have no fixed end date. When `pointsExpiresAfterDays` is also set, the earlier of the two applies."
},
"rewardsExpireAfterDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Rewards issued in this program lapse this many days after a member gets them. Null means they stay redeemable until they are used. A rule that sets its own expiry wins over this."
},
"expiringSoonDays": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "How many days of warning members get before a reward or a points balance lapses. Null means no warning goes out."
},
"pausedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was paused. While it is set the program earns nothing, and members keep their balances, rewards and history."
},
"publishedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the program was published. Null means it is still a draft: no shopper can see it, join it or earn in it, and only a preview link reaches it. Publishing cannot be undone, so pause a program you want to take down."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was created."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was last changed."
}
},
"required": [
"id",
"name",
"autoMembership",
"settings",
"sectionsEnabled",
"pointsNames",
"pointsExpiresAfterDays",
"pointsBalancesExpireAt",
"rewardsExpireAfterDays",
"expiringSoonDays",
"pausedAt",
"publishedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The published program."
},
"warnings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": ["noRules", "noSites"],
"description": "What is missing: `noRules` for a program with nothing to earn, `noSites` for one no storefront shows."
},
"message": {
"type": "string",
"description": "The warning in a sentence, ready to show a merchant."
}
},
"required": ["code", "message"],
"additionalProperties": false
},
"description": "Reasons the program will not do anything visible yet. None of them block it."
}
},
"required": ["data", "warnings"],
"additionalProperties": false
}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)requiredThe ID of the loyalty program to preview.
curl \
-X POST \
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/preview" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { postLoyaltyProgramsByIdPreview } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postLoyaltyProgramsByIdPreview({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/preview")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/preview",
method="POST",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"token": "string",
"expiresAt": "2025-01-15T09:30:00Z",
"sites": [
{
"siteId": "550e8400-e29b-41d4-a716-446655440000",
"siteName": "string",
"siteSlug": "string",
"url": "string"
}
]
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"token": {
"type": "string",
"description": "The signed preview token. Anyone holding it can read this one program through the widget API, published or not, until it expires."
},
"expiresAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the token stops working."
},
"sites": {
"type": "array",
"items": {
"type": "object",
"properties": {
"siteId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The site the link opens."
},
"siteName": {
"type": "string",
"description": "The site's display name."
},
"siteSlug": {
"type": "string",
"description": "The site's slug."
},
"url": {
"type": "string",
"description": "The site's loyalty page carrying the preview token and the `#cascade-rewards` fragment."
}
},
"required": ["siteId", "siteName", "siteSlug", "url"],
"additionalProperties": false
},
"description": "One link per site the program is attached to. Empty when no site shows the program, in which case there is nowhere to open the preview."
}
},
"required": ["token", "expiresAt", "sites"],
"additionalProperties": false,
"description": "The token and where to open it."
}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)requiredThe ID of the loyalty program.
curl \
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getLoyaltyProgramsByIdSites } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getLoyaltyProgramsByIdSites({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}
]
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the link between the site and the program."
},
"siteId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The site the program is attached to."
},
"siteName": {
"type": "string",
"description": "The site's display name."
},
"siteSlug": {
"type": "string",
"description": "The site's slug, which the widget identifies it by."
},
"loyaltyProgramId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The loyalty program the site is attached to."
},
"programUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The page on the site that shows the loyalty program, used by the links in the member emails. Null means the site's base URL, where the launcher embed is expected to be."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was attached to the site."
}
},
"required": [
"id",
"siteId",
"siteName",
"siteSlug",
"loyaltyProgramId",
"programUrl",
"createdAt"
],
"additionalProperties": false
},
"description": "The sites, alphabetically."
}
},
"required": ["data"],
"additionalProperties": false
}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)requiredThe ID of the loyalty program.
Body Parameters
siteIdstring (uuid)requiredThe ID of the site to attach the program to.
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"
}'import { client } from "@cascade-commerce/api/admin/client"
import { postLoyaltyProgramsByIdSites } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postLoyaltyProgramsByIdSites({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
body: {
siteId: "550e8400-e29b-41d4-a716-446655440000",
},
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"siteId": "550e8400-e29b-41d4-a716-446655440000"
}
JSON
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
body = """{
"siteId": "550e8400-e29b-41d4-a716-446655440000"
}"""
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites",
data=body.encode(),
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the link between the site and the program."
},
"siteId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The site the program is attached to."
},
"siteName": {
"type": "string",
"description": "The site's display name."
},
"siteSlug": {
"type": "string",
"description": "The site's slug, which the widget identifies it by."
},
"loyaltyProgramId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The loyalty program the site is attached to."
},
"programUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The page on the site that shows the loyalty program, used by the links in the member emails. Null means the site's base URL, where the launcher embed is expected to be."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was attached to the site."
}
},
"required": [
"id",
"siteId",
"siteName",
"siteSlug",
"loyaltyProgramId",
"programUrl",
"createdAt"
],
"additionalProperties": false,
"description": "The new link."
}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)requiredThe ID of the loyalty program.
siteIdstring (uuid)requiredThe ID of the site the program is attached to.
Body Parameters
programUrlstring (uri) | nullrequiredThe page that shows the program, such as https://shop.example.com/pages/rewards. Null clears it.
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"
}'import { client } from "@cascade-commerce/api/admin/client"
import { patchLoyaltyProgramsByIdSitesBySiteId } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await patchLoyaltyProgramsByIdSitesBySiteId({
path: {
id: "550e8400-e29b-41d4-a716-446655440000",
siteId: "550e8400-e29b-41d4-a716-446655440000",
},
body: {
programUrl: "https://example.com",
},
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"programUrl": "https://example.com"
}
JSON
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
body = """{
"programUrl": "https://example.com"
}"""
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites/550e8400-e29b-41d4-a716-446655440000",
data=body.encode(),
method="PATCH",
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the link between the site and the program."
},
"siteId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The site the program is attached to."
},
"siteName": {
"type": "string",
"description": "The site's display name."
},
"siteSlug": {
"type": "string",
"description": "The site's slug, which the widget identifies it by."
},
"loyaltyProgramId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The loyalty program the site is attached to."
},
"programUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The page on the site that shows the loyalty program, used by the links in the member emails. Null means the site's base URL, where the launcher embed is expected to be."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was attached to the site."
}
},
"required": [
"id",
"siteId",
"siteName",
"siteSlug",
"loyaltyProgramId",
"programUrl",
"createdAt"
],
"additionalProperties": false,
"description": "The updated link."
}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)requiredThe ID of the loyalty program.
siteIdstring (uuid)requiredThe ID of the site to detach it from.
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"import { client } from "@cascade-commerce/api/admin/client"
import { deleteLoyaltyProgramsByIdSitesBySiteId } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteLoyaltyProgramsByIdSitesBySiteId({
path: {
id: "550e8400-e29b-41d4-a716-446655440000",
siteId: "550e8400-e29b-41d4-a716-446655440000",
},
})require "net/http"
uri = URI("https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/loyalty-programs/550e8400-e29b-41d4-a716-446655440000/sites/550e8400-e29b-41d4-a716-446655440000",
method="DELETE",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"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"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the link between the site and the program."
},
"siteId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The site the program is attached to."
},
"siteName": {
"type": "string",
"description": "The site's display name."
},
"siteSlug": {
"type": "string",
"description": "The site's slug, which the widget identifies it by."
},
"loyaltyProgramId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The loyalty program the site is attached to."
},
"programUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The page on the site that shows the loyalty program, used by the links in the member emails. Null means the site's base URL, where the launcher embed is expected to be."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the program was attached to the site."
}
},
"required": [
"id",
"siteId",
"siteName",
"siteSlug",
"loyaltyProgramId",
"programUrl",
"createdAt"
],
"additionalProperties": false,
"description": "The removed link."
}