Customers

GET /customers

List the organization's customers, newest first by default. Supports search, filtering and cursor pagination.

Requires the customers: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.

sortemail | -email | createdAt | -createdAt | string

The sort order. Prefix a field with - to sort descending. Defaults to -createdAt. A custom field sorts as metadata.<key>, records without a value last; GET /metadata-definitions lists the fields.

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, email, firstName, lastName, metadata, emailOptOut, emailOptOutSource, createdAt, tags.name, externalIds.externalId, orders.createdAt, orders.totalCents. A custom field's metadata.<key> path takes the operators its type supports: $gt, $gte, $lt and $lte for numbers and dates, $includes for text and pick-any fields, and $exists for any key. GET /metadata-definitions lists the fields. 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, externalId, externalIds, ordersCount, reviewsCount, wishlistsCount, loyaltyPointsBalance, tags.

Request
curl \
  "https://your-store.meisei.app/api/customers" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "data": [
    {
      "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",
      "importJob": {
        "id": "...",
        "type": "...",
        "action": "...",
        "row": "...",
        "createdAt": "..."
      },
      "externalId": "string",
      "externalIds": ["..."],
      "ordersCount": 0,
      "reviewsCount": 0,
      "wishlistsCount": 0,
      "loyaltyPointsBalance": 0,
      "tags": ["..."]
    }
  ],
  "nextCursor": "string"
}

PATCH /customers/bulk-update

Add or remove tags and metadata, or set the marketing email opt-out, across every customer matching a filter, or an exact list of ids, in one request. Sending neither applies the changes to every customer in the organization. At least one change is required. Each customer that actually changes triggers the customer.updated webhook.

Requires the customers:write permission.

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

Query parameters

filterstring

A JSON-encoded filter document selecting the customers to update. Omit it and every customer in the organization is updated. See Filtering for the syntax.

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.

Body parameters

idsstring[]

The ids of the customers to update, for changing an exact list. Combined with filter and search using AND when those are sent too.

addTagsstring[]

Tag names to add. Tags that do not exist yet are created.

removeTagsstring[]

Tag names to remove.

addMetadataobject

Metadata keys to set, merged onto whatever each customer already has.

removeMetadataKeysstring[]

Metadata keys to delete.

emailOptOutboolean

Set to true to stop sending every matching customer marketing email, false to opt them all back in. Omit to leave it alone.

emailOptOutSourcecustomer | merchant | import | shopify

Where the opt-out came from. Defaults to merchant.

Request
curl \
  -X PATCH \
  "https://your-store.meisei.app/api/customers/bulk-update" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "string"
  ],
  "addTags": [
    "string"
  ],
  "removeTags": [
    "string"
  ],
  "addMetadata": "...",
  "removeMetadataKeys": [
    "string"
  ],
  "emailOptOut": true,
  "emailOptOutSource": "customer"
}'
Response
{
  "count": 0
}

POST /customers/import

Create or update customers in bulk, matching on externalIds first and then on email. A customer matched by an external ID takes the row's email when no other customer has it. Every column you send overwrites what is already stored, so send complete records rather than partial ones. Rows naming a site that doesn't exist are skipped and returned in skipped; set strict to fail on the first such row instead. Each new customer triggers the customer.created webhook and each existing one customer.updated.

Requires the customers:write permission.

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

Body parameters

dataobject[]required

The customers to create or update.

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/customers/import" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "data": [
    {
      "email": "user@example.com",
      "phone": "string",
      "firstName": "string",
      "lastName": "string",
      "metadata": "...",
      "tags": [
        "..."
      ],
      "emailOptOut": true,
      "emailOptOutSource": "customer",
      "externalId": "string",
      "externalIds": [
        "..."
      ]
    }
  ],
  "strict": true
}'
Response
{
  "data": [
    {
      "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",
      "importJob": {
        "id": "...",
        "type": "...",
        "action": "...",
        "row": "...",
        "createdAt": "..."
      },
      "externalId": "string",
      "externalIds": ["..."],
      "ordersCount": 0,
      "reviewsCount": 0,
      "wishlistsCount": 0,
      "loyaltyPointsBalance": 0,
      "tags": ["..."]
    }
  ],
  "skipped": [
    {
      "index": 0,
      "message": "string"
    }
  ],
  "notices": [
    {
      "index": 0,
      "message": "string"
    }
  ]
}

GET /customers/{id}

Get a single customer. The customer may be named by Meisei's ID or by its ID on one of your sites, such as a Shopify ID: Meisei's is tried first, then every site. Send by to say which kind it is, and site when the same external ID is on more than one of your sites (otherwise that answers 400).

Requires the customers:read permission.

Path parameters

idstringrequired

The customer's ID, or its ID on one of your sites. URL-encode a gid:// one.

Query parameters

byid | externalId

What the ID in the path is: id for Meisei's own ID, externalId for its ID on one of your sites (such as a Shopify ID, in either form). Leave it out to try Meisei's ID first and then every site.

sitestring

The slug of the site an external ID belongs to. Only needed when the same ID is on more than one of your sites.

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, externalId, externalIds, ordersCount, reviewsCount, wishlistsCount, loyaltyPointsBalance, tags.

Request
curl \
  "https://your-store.meisei.app/api/customers/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "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",
  "importJob": {
    "id": "A4Ws1igz",
    "type": "customers",
    "action": "created",
    "row": 0,
    "createdAt": "2025-01-15T09:30:00Z"
  },
  "externalId": "string",
  "externalIds": [
    {
      "site": "string",
      "id": "string"
    }
  ],
  "ordersCount": 0,
  "reviewsCount": 0,
  "wishlistsCount": 0,
  "loyaltyPointsBalance": 0,
  "tags": ["string"]
}

PATCH /customers/{id}

Update a customer's metadata, notes, tags or marketing email opt-out. Profile fields such as the email and name come from the store and cannot be edited here. Triggers the customer.updated webhook. The customer may be named by Meisei's ID or by its ID on one of your sites, such as a Shopify ID: Meisei's is tried first, then every site. Send by to say which kind it is, and site when the same external ID is on more than one of your sites (otherwise that answers 400).

Requires the customers:write permission.

Path parameters

idstringrequired

The customer's ID, or its ID on one of your sites. URL-encode a gid:// one.

Query parameters

byid | externalId

What the ID in the path is: id for Meisei's own ID, externalId for its ID on one of your sites (such as a Shopify ID, in either form). Leave it out to try Meisei's ID first and then every site.

sitestring

The slug of the site an external ID belongs to. Only needed when the same ID is on more than one of your sites.

Body parameters

metadataobject

Replaces the customer's metadata wholesale. Omit to leave it alone.

notesstring

Internal notes about the customer.

tagsstring[]

The customer's full set of tag names. Omit to leave the tags alone.

emailOptOutboolean

Set to true to stop sending the customer marketing email, false to opt them back in. Omit to leave it alone.

emailOptOutSourcecustomer | merchant | import | shopify

Where the opt-out came from. Defaults to merchant.

Request
curl \
  -X PATCH \
  "https://your-store.meisei.app/api/customers/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "metadata": "...",
  "notes": "string",
  "tags": [
    "string"
  ],
  "emailOptOut": true,
  "emailOptOutSource": "customer"
}'
Response
{
  "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",
  "importJob": {
    "id": "A4Ws1igz",
    "type": "customers",
    "action": "created",
    "row": 0,
    "createdAt": "2025-01-15T09:30:00Z"
  },
  "externalId": "string",
  "externalIds": [
    {
      "site": "string",
      "id": "string"
    }
  ],
  "ordersCount": 0,
  "reviewsCount": 0,
  "wishlistsCount": 0,
  "loyaltyPointsBalance": 0,
  "tags": ["string"]
}

DELETE /customers/{id}

Delete a customer along with their orders, loyalty memberships and tags. Their reviews are kept but unlinked and unpublished, unless the organization has cascadeDeleteReviews turned on. Triggers the customer.deleted webhook. The customer may be named by Meisei's ID or by its ID on one of your sites, such as a Shopify ID: Meisei's is tried first, then every site. Send by to say which kind it is, and site when the same external ID is on more than one of your sites (otherwise that answers 400).

Requires the customers:write permission.

Path parameters

idstringrequired

The customer's ID, or its ID on one of your sites. URL-encode a gid:// one.

Query parameters

byid | externalId

What the ID in the path is: id for Meisei's own ID, externalId for its ID on one of your sites (such as a Shopify ID, in either form). Leave it out to try Meisei's ID first and then every site.

sitestring

The slug of the site an external ID belongs to. Only needed when the same ID is on more than one of your sites.

Request
curl \
  -X DELETE \
  "https://your-store.meisei.app/api/customers/A4Ws1igz" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"
Response
{
  "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",
  "importJob": {
    "id": "A4Ws1igz",
    "type": "customers",
    "action": "created",
    "row": 0,
    "createdAt": "2025-01-15T09:30:00Z"
  },
  "externalId": "string",
  "externalIds": [
    {
      "site": "string",
      "id": "string"
    }
  ],
  "ordersCount": 0,
  "reviewsCount": 0,
  "wishlistsCount": 0,
  "loyaltyPointsBalance": 0,
  "tags": ["string"]
}

POST /customers/{id}/send-email

Send a customer one of an automation's own emails, filled in with the data you send. It is marketing email unless you pass transactional, so a customer who has opted out, an imported customer who has never been emailed and a suppressed address are skipped: the first two answer with a 409 saying so, and nothing is sent. A transactional email skips only suppressed addresses. It goes out through the same queue, send ramp and pauses as every other email the store sends.

Requires the customers:write permission.

Path parameters

idstringrequired

The ID of the customer to email.

Body parameters

emailstringrequired

The key of the automation's email to send, as the automation lists it.

automationIdstring

The automation whose email it is. Leave it out when an automation's own script is calling, which sends one of its own emails.

dataobject

What the email's subject, preview line and body fill their variables from, beside customer and site, which Meisei fills in itself.

siteIdstring

The site the email is sent for, which decides its branding, sending address and unsubscribe link. Defaults to the organization's first site.

transactionalboolean

Send it as transactional email, which reaches a customer who has opted out of marketing. Only use it for a message about something the customer did or holds, such as an order, a reward they claimed or their account: your store is responsible for that, and a store whose transactional mail draws complaints has it paused. Suppressed addresses are still skipped.

Request
curl \
  -X POST \
  "https://your-store.meisei.app/api/customers/A4Ws1igz/send-email" \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "string",
  "automationId": "string",
  "data": "...",
  "siteId": "string",
  "transactional": true
}'
Response
{
  "to": "string",
  "subject": "string"
}