Loyalty programs

GET /loyalty-programs

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

Requires the loyalty:read permission.

Query parameters

limitnumber

The number of records to return.

cursorstring

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

sortname | -name | createdAt | -createdAt

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

includestring

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

filterstring

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

Request
curl \
  "https://your-store.meisei.app/api/loyalty-programs" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "data": [
    {
      "id": "A4Ws1igz",
      "name": "string",
      "autoMembership": "off",
      "settings": {
        "previousPointsNames": "..."
      },
      "sectionsEnabled": {
        "rules": "...",
        "offers": "..."
      },
      "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": "...",
        "rewards": "...",
        "sites": "..."
      }
    }
  ],
  "nextCursor": "string"
}

POST /loyalty-programs

Create a loyalty program. Name its points and it can hand them out, hold balances and offer things to spend them on; leave pointsNames out and it runs on coupons 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

namestringrequired

The program's display name, shown to members.

autoMembershipoff | onFirstActivity

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.

sectionsEnabledobject

The sections to show. Anything you leave out starts switched on.

pointsNamesobject | null

What the program calls its points, singular and plural. Null means the program has no points, so nothing earns, spends or displays them.

pointsExpiresAfterDaysinteger | null

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.

pointsBalancesExpireAtstring (date-time) | null

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.

rewardsExpireAfterDaysinteger | null

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.

expiringSoonDaysinteger | null

How many days of warning members get before a reward or a points balance lapses. Null means no warning goes out.

Request
curl \
  -X POST \
  "https://your-store.meisei.app/api/loyalty-programs" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "autoMembership": "off",
  "sectionsEnabled": {
    "rules": true,
    "offers": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0
}'
Response
{
  "id": "A4Ws1igz",
  "name": "string",
  "autoMembership": "off",
  "settings": {
    "previousPointsNames": null
  },
  "sectionsEnabled": {
    "rules": true,
    "offers": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0,
  "pausedAt": "2025-01-15T09:30:00Z",
  "publishedAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z"
}

GET /loyalty-programs/{id}

Get a single loyalty program by ID.

Requires the loyalty:read permission.

Path parameters

idstringrequired

The ID of the loyalty program.

Query parameters

includestring

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

Request
curl \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "id": "A4Ws1igz",
  "name": "string",
  "autoMembership": "off",
  "settings": {
    "previousPointsNames": null
  },
  "sectionsEnabled": {
    "rules": true,
    "offers": 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,
    "rewards": 0,
    "sites": 0
  }
}

PUT /loyalty-programs/{id}

Update a loyalty program's name, enrollment behavior, points, visible sections or paused state. 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. Triggers the loyaltyProgram.updated webhook.

Requires the loyalty:write permission.

Path parameters

idstringrequired

The ID of the loyalty program to update.

Body parameters

namestring

The program's display name.

autoMembershipoff | onFirstActivity

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.

sectionsEnabledobject

The sections to show or hide. Only the ones you send are changed.

pointsNamesobject | null

What the program calls its points, singular and plural. Null means the program has no points, so nothing earns, spends or displays them.

pointsExpiresAfterDaysinteger | null

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.

pointsBalancesExpireAtstring (date-time) | null

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.

rewardsExpireAfterDaysinteger | null

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.

expiringSoonDaysinteger | null

How many days of warning members get before a reward or a points balance lapses. Null means no warning goes out.

pausedboolean

Set to true to pause the program: it stops earning, and members keep their balances, rewards and history. Set to false to start it up again.

Request
curl \
  -X PUT \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "autoMembership": "off",
  "sectionsEnabled": {
    "rules": true,
    "offers": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0,
  "paused": true
}'
Response
{
  "id": "A4Ws1igz",
  "name": "string",
  "autoMembership": "off",
  "settings": {
    "previousPointsNames": null
  },
  "sectionsEnabled": {
    "rules": true,
    "offers": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0,
  "pausedAt": "2025-01-15T09:30:00Z",
  "publishedAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z"
}

DELETE /loyalty-programs/{id}

Permanently delete a loyalty program. Its rules, tiers, 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

idstringrequired

The ID of the loyalty program to delete.

Body parameters

reasonclosing_program | duplicate | replaced | test_data | otherrequired

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

notestring

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

Request
curl \
  -X DELETE \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "closing_program",
  "note": "string"
}'
Response
{
  "id": "A4Ws1igz",
  "name": "string",
  "autoMembership": "off",
  "settings": {
    "previousPointsNames": null
  },
  "sectionsEnabled": {
    "rules": true,
    "offers": true
  },
  "pointsNames": {
    "singular": "string",
    "plural": "string"
  },
  "pointsExpiresAfterDays": 0,
  "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
  "rewardsExpireAfterDays": 0,
  "expiringSoonDays": 0,
  "pausedAt": "2025-01-15T09:30:00Z",
  "publishedAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z"
}

POST /loyalty-programs/{id}/publish

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

Requires the loyalty:write permission.

Path parameters

idstringrequired

The ID of the loyalty program to publish.

Request
curl \
  -X POST \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz/publish" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "data": {
    "id": "A4Ws1igz",
    "name": "string",
    "autoMembership": "off",
    "settings": {
      "previousPointsNames": null
    },
    "sectionsEnabled": {
      "rules": true,
      "offers": true
    },
    "pointsNames": {
      "singular": "string",
      "plural": "string"
    },
    "pointsExpiresAfterDays": 0,
    "pointsBalancesExpireAt": "2025-01-15T09:30:00Z",
    "rewardsExpireAfterDays": 0,
    "expiringSoonDays": 0,
    "pausedAt": "2025-01-15T09:30:00Z",
    "publishedAt": "2025-01-15T09:30:00Z",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "warnings": [
    {
      "code": "noRules",
      "message": "string"
    }
  ]
}

POST /loyalty-programs/{id}/preview

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

Requires the loyalty:write permission.

Path parameters

idstringrequired

The ID of the loyalty program to preview.

Request
curl \
  -X POST \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz/preview" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "token": "string",
  "expiresAt": "2025-01-15T09:30:00Z",
  "sites": [
    {
      "siteId": "A4Ws1igz",
      "siteName": "string",
      "siteSlug": "string",
      "url": "string"
    }
  ]
}

GET /loyalty-programs/{id}/sites

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

Requires the loyalty:read permission.

Path parameters

idstringrequired

The ID of the loyalty program.

Request
curl \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz/sites" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "data": [
    {
      "id": "A4Ws1igz",
      "siteId": "A4Ws1igz",
      "siteName": "string",
      "siteSlug": "string",
      "loyaltyProgramId": "A4Ws1igz",
      "programUrl": "string",
      "createdAt": "2025-01-15T09:30:00Z"
    }
  ]
}

POST /loyalty-programs/{id}/sites

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

Requires the loyalty:write permission.

Path parameters

idstringrequired

The ID of the loyalty program.

Body parameters

siteIdstringrequired

The ID of the site to attach the program to.

Request
curl \
  -X POST \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz/sites" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "siteId": "string"
}'
Response
{
  "id": "A4Ws1igz",
  "siteId": "A4Ws1igz",
  "siteName": "string",
  "siteSlug": "string",
  "loyaltyProgramId": "A4Ws1igz",
  "programUrl": "string",
  "createdAt": "2025-01-15T09:30:00Z"
}

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

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

Requires the loyalty:write permission.

Path parameters

idstringrequired

The ID of the loyalty program.

siteIdstringrequired

The ID of the site the program is attached to.

Body parameters

programUrlstring (uri) | nullrequired

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

Request
curl \
  -X PATCH \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz/sites/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "programUrl": "https://example.com"
}'
Response
{
  "id": "A4Ws1igz",
  "siteId": "A4Ws1igz",
  "siteName": "string",
  "siteSlug": "string",
  "loyaltyProgramId": "A4Ws1igz",
  "programUrl": "string",
  "createdAt": "2025-01-15T09:30:00Z"
}

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

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

Requires the loyalty:write permission.

Path parameters

idstringrequired

The ID of the loyalty program.

siteIdstringrequired

The ID of the site to detach it from.

Request
curl \
  -X DELETE \
  "https://your-store.meisei.app/api/loyalty-programs/A4Ws1igz/sites/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "id": "A4Ws1igz",
  "siteId": "A4Ws1igz",
  "siteName": "string",
  "siteSlug": "string",
  "loyaltyProgramId": "A4Ws1igz",
  "programUrl": "string",
  "createdAt": "2025-01-15T09:30:00Z"
}