Loyalty memberships

POST /loyalty-memberships/import

Bring loyalty members in from another platform in bulk, matching the customer by email and the program, tier, and currencies by name. Each member is enrolled, placed in their tier without pinning it, and each balance is written as a single opening-adjustment transaction so history starts clean. Importing a member again reconciles their balances to the new values instead of stacking a second opening. Rows whose references don't resolve are skipped and returned in skipped rather than failing the whole batch; set strict to fail on the first such row instead. To import a CSV instead, upload it and queue an import job with POST /import-jobs.

Requires the loyalty:write permission.

Costs 10 rate limit tokens instead of the usual one. See Rate limits.

Body parameters

dataobject[]required

The members to enroll or reconcile.

strictboolean

When true, a row that references something missing fails the whole import with an error. Off by default: rows whose references don't resolve are skipped and reported in skipped.

Request
curl \
  -X POST \
  "https://your-store.meisei.app/api/loyalty-memberships/import" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "data": [
    {
      "email": "user@example.com",
      "program": "string",
      "tier": "string",
      "points": 0,
      "customerMetadata": "..."
    }
  ],
  "strict": true
}'
Response
{
  "imported": [
    {
      "index": 0,
      "loyaltyMembershipId": "A4Ws1igz"
    }
  ],
  "skipped": [
    {
      "index": 0,
      "message": "string"
    }
  ]
}

GET /loyalty-memberships

List loyalty program memberships, newest first. Members who left are included; an active membership has a null leftAt. Supports search, filtering and cursor pagination.

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.

sortcreatedAt | -createdAt | leftAt | -leftAt

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

searchstring

A free-text search term. Every word has to match, from the start of a word, in any order and ignoring case and accents, against the record's most identifying fields. Combined with filter using AND.

filterstring

A JSON-encoded filter document, for example {"createdAt":{"$gte":"2025-01-01T00:00:00Z"}}. Filterable fields: id, loyaltyProgramId, loyaltyTierId, tierAssignedManually, leftAt, createdAt, customer.id, customer.email, customer.firstName, customer.lastName, customer.metadata, customer.emailOptOut, customer.emailOptOutSource, customer.createdAt, tier.id, tier.name, tags.name. See Filtering for the syntax.

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: importJob, customer, tier, pointsBalance, tierProgress, earnedPoints.

Request
curl \
  "https://your-store.meisei.app/api/loyalty-memberships" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "data": [
    {
      "id": "A4Ws1igz",
      "customerId": "A4Ws1igz",
      "loyaltyProgramId": "A4Ws1igz",
      "loyaltyTierId": "A4Ws1igz",
      "tierAssignedManually": true,
      "tierAttainedAt": "2025-01-15T09:30:00Z",
      "leftAt": "2025-01-15T09:30:00Z",
      "createdAt": "2025-01-15T09:30:00Z",
      "updatedAt": "2025-01-15T09:30:00Z",
      "importJob": {
        "id": "...",
        "type": "...",
        "action": "...",
        "row": "...",
        "createdAt": "..."
      },
      "customer": {
        "id": "...",
        "email": "...",
        "phone": "...",
        "firstName": "...",
        "lastName": "...",
        "country": "...",
        "metadata": "...",
        "notes": "...",
        "emailOptOut": "...",
        "emailOptOutAt": "...",
        "emailOptOutSource": "...",
        "createdAt": "...",
        "updatedAt": "..."
      },
      "tier": {
        "id": "...",
        "name": "...",
        "loyaltyProgramId": "...",
        "threshold": "...",
        "pointsMultiplier": "...",
        "color": "...",
        "metadata": "...",
        "archivedAt": "...",
        "createdAt": "...",
        "updatedAt": "..."
      },
      "pointsBalance": 0,
      "tierProgress": {
        "currentTier": "...",
        "nextTier": "...",
        "earned": "...",
        "remaining": "..."
      },
      "earnedPoints": {
        "lifetime": "...",
        "rolling12Months": "..."
      }
    }
  ],
  "nextCursor": "string"
}

POST /loyalty-memberships

Enroll a customer in a loyalty program. A customer who previously left is restored with their balances intact unless freshStart is set. Enrolling an active member fails with a conflict. Triggers the loyaltyMembership.created webhook.

Requires the loyalty:write permission.

Body parameters

customerIdstringrequired

The customer to enroll.

loyaltyProgramIdstringrequired

The loyalty program to enroll them in.

freshStartboolean

Only matters for a customer who previously left the program. When true, their earlier balances, rewards, and tier are cleared so the membership starts over; otherwise rejoining restores them.

Request
curl \
  -X POST \
  "https://your-store.meisei.app/api/loyalty-memberships" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "customerId": "string",
  "loyaltyProgramId": "string",
  "freshStart": true
}'
Response
{
  "id": "A4Ws1igz",
  "customerId": "A4Ws1igz",
  "loyaltyProgramId": "A4Ws1igz",
  "loyaltyTierId": "A4Ws1igz",
  "tierAssignedManually": true,
  "tierAttainedAt": "2025-01-15T09:30:00Z",
  "leftAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z",
  "importJob": {
    "id": "A4Ws1igz",
    "type": "customers",
    "action": "created",
    "row": 0,
    "createdAt": "2025-01-15T09:30:00Z"
  },
  "customer": {
    "id": "A4Ws1igz",
    "email": "user@example.com",
    "phone": "string",
    "firstName": "string",
    "lastName": "string",
    "country": "string",
    "metadata": "...",
    "notes": "string",
    "emailOptOut": true,
    "emailOptOutAt": "2025-01-15T09:30:00Z",
    "emailOptOutSource": "customer",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "tier": {
    "id": "A4Ws1igz",
    "name": "string",
    "loyaltyProgramId": "A4Ws1igz",
    "threshold": {
      "type": "earnedPoints",
      "amount": 0,
      "window": "lifetime"
    },
    "pointsMultiplier": 0,
    "color": "string",
    "metadata": "...",
    "archivedAt": "2025-01-15T09:30:00Z",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "pointsBalance": 0,
  "tierProgress": {
    "currentTier": {
      "id": "A4Ws1igz",
      "name": "string",
      "color": "string",
      "pointsMultiplier": 0
    },
    "nextTier": {
      "id": "A4Ws1igz",
      "name": "string",
      "color": "string",
      "threshold": {
        "type": "...",
        "amount": "...",
        "window": "..."
      }
    },
    "earned": 0,
    "remaining": 0
  },
  "earnedPoints": {
    "lifetime": 0,
    "rolling12Months": 0
  }
}

GET /loyalty-memberships/{id}

Get a single loyalty membership by ID.

Requires the loyalty:read permission.

Path parameters

idstringrequired

The ID of the membership.

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: importJob, customer, tier, pointsBalance, tierProgress, earnedPoints.

Request
curl \
  "https://your-store.meisei.app/api/loyalty-memberships/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "id": "A4Ws1igz",
  "customerId": "A4Ws1igz",
  "loyaltyProgramId": "A4Ws1igz",
  "loyaltyTierId": "A4Ws1igz",
  "tierAssignedManually": true,
  "tierAttainedAt": "2025-01-15T09:30:00Z",
  "leftAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z",
  "importJob": {
    "id": "A4Ws1igz",
    "type": "customers",
    "action": "created",
    "row": 0,
    "createdAt": "2025-01-15T09:30:00Z"
  },
  "customer": {
    "id": "A4Ws1igz",
    "email": "user@example.com",
    "phone": "string",
    "firstName": "string",
    "lastName": "string",
    "country": "string",
    "metadata": "...",
    "notes": "string",
    "emailOptOut": true,
    "emailOptOutAt": "2025-01-15T09:30:00Z",
    "emailOptOutSource": "customer",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "tier": {
    "id": "A4Ws1igz",
    "name": "string",
    "loyaltyProgramId": "A4Ws1igz",
    "threshold": {
      "type": "earnedPoints",
      "amount": 0,
      "window": "lifetime"
    },
    "pointsMultiplier": 0,
    "color": "string",
    "metadata": "...",
    "archivedAt": "2025-01-15T09:30:00Z",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "pointsBalance": 0,
  "tierProgress": {
    "currentTier": {
      "id": "A4Ws1igz",
      "name": "string",
      "color": "string",
      "pointsMultiplier": 0
    },
    "nextTier": {
      "id": "A4Ws1igz",
      "name": "string",
      "color": "string",
      "threshold": {
        "type": "...",
        "amount": "...",
        "window": "..."
      }
    },
    "earned": 0,
    "remaining": 0
  },
  "earnedPoints": {
    "lifetime": 0,
    "rolling12Months": 0
  }
}

PUT /loyalty-memberships/{id}

Assign or pin a member's tier. Setting loyaltyTierId pins the member to that tier so recalculation leaves them there; passing tierAssignedManually: false unpins them and recalculates their tier right away. Triggers the loyaltyMembership.updated webhook, and loyaltyMembership.tierChanged as well when the tier changes.

Requires the loyalty:write permission.

Path parameters

idstringrequired

The ID of the membership to update.

Body parameters

loyaltyTierIdstring | null

The tier to place the member in, or null to clear it. Setting a tier pins it so the loyalty tiers automation leaves the member where you put them, unless tierAssignedManually is passed as false.

tierAssignedManuallyboolean

Whether the member's tier is pinned. Set false to hand the member back to the loyalty tiers automation, which places them on its next pass.

Request
curl \
  -X PUT \
  "https://your-store.meisei.app/api/loyalty-memberships/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "loyaltyTierId": "string",
  "tierAssignedManually": true
}'
Response
{
  "id": "A4Ws1igz",
  "customerId": "A4Ws1igz",
  "loyaltyProgramId": "A4Ws1igz",
  "loyaltyTierId": "A4Ws1igz",
  "tierAssignedManually": true,
  "tierAttainedAt": "2025-01-15T09:30:00Z",
  "leftAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z",
  "importJob": {
    "id": "A4Ws1igz",
    "type": "customers",
    "action": "created",
    "row": 0,
    "createdAt": "2025-01-15T09:30:00Z"
  },
  "customer": {
    "id": "A4Ws1igz",
    "email": "user@example.com",
    "phone": "string",
    "firstName": "string",
    "lastName": "string",
    "country": "string",
    "metadata": "...",
    "notes": "string",
    "emailOptOut": true,
    "emailOptOutAt": "2025-01-15T09:30:00Z",
    "emailOptOutSource": "customer",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "tier": {
    "id": "A4Ws1igz",
    "name": "string",
    "loyaltyProgramId": "A4Ws1igz",
    "threshold": {
      "type": "earnedPoints",
      "amount": 0,
      "window": "lifetime"
    },
    "pointsMultiplier": 0,
    "color": "string",
    "metadata": "...",
    "archivedAt": "2025-01-15T09:30:00Z",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "pointsBalance": 0,
  "tierProgress": {
    "currentTier": {
      "id": "A4Ws1igz",
      "name": "string",
      "color": "string",
      "pointsMultiplier": 0
    },
    "nextTier": {
      "id": "A4Ws1igz",
      "name": "string",
      "color": "string",
      "threshold": {
        "type": "...",
        "amount": "...",
        "window": "..."
      }
    },
    "earned": 0,
    "remaining": 0
  },
  "earnedPoints": {
    "lifetime": 0,
    "rolling12Months": 0
  }
}

DELETE /loyalty-memberships/{id}

Remove a member from their loyalty program. The membership is kept and flagged as left so re-enrolling can restore their balances; leaving twice returns a 404. Triggers the loyaltyMembership.deleted webhook.

Requires the loyalty:write permission.

Path parameters

idstringrequired

The ID of the membership to remove.

Request
curl \
  -X DELETE \
  "https://your-store.meisei.app/api/loyalty-memberships/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "id": "A4Ws1igz",
  "customerId": "A4Ws1igz",
  "loyaltyProgramId": "A4Ws1igz",
  "loyaltyTierId": "A4Ws1igz",
  "tierAssignedManually": true,
  "tierAttainedAt": "2025-01-15T09:30:00Z",
  "leftAt": "2025-01-15T09:30:00Z",
  "createdAt": "2025-01-15T09:30:00Z",
  "updatedAt": "2025-01-15T09:30:00Z",
  "importJob": {
    "id": "A4Ws1igz",
    "type": "customers",
    "action": "created",
    "row": 0,
    "createdAt": "2025-01-15T09:30:00Z"
  },
  "customer": {
    "id": "A4Ws1igz",
    "email": "user@example.com",
    "phone": "string",
    "firstName": "string",
    "lastName": "string",
    "country": "string",
    "metadata": "...",
    "notes": "string",
    "emailOptOut": true,
    "emailOptOutAt": "2025-01-15T09:30:00Z",
    "emailOptOutSource": "customer",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "tier": {
    "id": "A4Ws1igz",
    "name": "string",
    "loyaltyProgramId": "A4Ws1igz",
    "threshold": {
      "type": "earnedPoints",
      "amount": 0,
      "window": "lifetime"
    },
    "pointsMultiplier": 0,
    "color": "string",
    "metadata": "...",
    "archivedAt": "2025-01-15T09:30:00Z",
    "createdAt": "2025-01-15T09:30:00Z",
    "updatedAt": "2025-01-15T09:30:00Z"
  },
  "pointsBalance": 0,
  "tierProgress": {
    "currentTier": {
      "id": "A4Ws1igz",
      "name": "string",
      "color": "string",
      "pointsMultiplier": 0
    },
    "nextTier": {
      "id": "A4Ws1igz",
      "name": "string",
      "color": "string",
      "threshold": {
        "type": "...",
        "amount": "...",
        "window": "..."
      }
    },
    "earned": 0,
    "remaining": 0
  },
  "earnedPoints": {
    "lifetime": 0,
    "rolling12Months": 0
  }
}