---
title: "Start a subscriber operation"
method: POST
path: "/subscribers/operations"
tags: ["Subscribers"]
---

# Start a subscriber operation

`POST /subscribers/operations`

Requires subscribers:read. Tagging and cancelling a tagging task also require subscribers:tag; creating tag definitions requires tags:write; triggering automations requires automations:trigger. Personal keys retain current company role restrictions. Company keys retain company-scoped authority. Workers recheck authority on every page. Returns immediately with a durable ID. Retry the same requestKey after an uncertain response. Active tasks have a seven-day processing deadline. Completed/failed/cancelled records are retained seven days. Inspect failures before retrying interrupted tagging; an uncertain action is never automatically replayed.

## Request body

- SubscriberOperationStart
  - `kind` 'add_tags', required
  - `requestKey` string, required — Reuse after an uncertain response. Different normalized settings with the same company/key return 409.
  - `audience` object — Defaults to all contacts. Selection walks live pages before mutations, excludes contacts created after the request, and is not a point-in-time database snapshot. Provide root or filters, never both.
    - `subscriberIds` string[]
    - `filters` FilterLeaf[]
      - `kind` 'filter' — Required when the filter is inside a v2 root group.
      - `id` string, required
      - `field` 'status' | 'phone' | 'smsStatus' | 'tag' | 'email' | 'emailProvider' | 'added' | 'firstName' | 'lastName' | 'list' | 'attribute' | 'event' | 'segment' | 'stripeProduct' | 'stripeCurrentProduct' | 'stripeTrialProduct' | 'commerceProduct' | 'commerceCollection' | 'emailSent' | 'emailDelivered' | 'emailOpened' | 'emailClicked' | 'emailBounced' | 'emailComplained', required
      - `operator` 'is' | 'is_not' | 'is_empty' | 'is_not_empty' | 'contains' | 'not_contains' | 'less_than' | 'more_than' | 'is_temporary_bounce' | 'is_permanent_bounce' | 'at_least' | 'less_than_count' | 'gte' | 'lte' | 'gt' | 'lt', required — Valid operators depend on the field. status/segment: is, is_not. smsStatus: is, is_not (values: subscribed, unsubscribed, not_subscribed). phone: is_not_empty, is_empty (empty value). tag: contains, not_contains, is_empty, is_not_empty. email: contains, not_contains for domain or substring matching, is, is_not for an exact case-insensitive address. emailProvider/list: is, is_not, is_empty, is_not_empty. firstName/lastName: contains, not_contains, is_empty, is_not_empty. added: less_than, more_than. attribute: is, is_not, is_empty, is_not_empty, gte, lte, gt, lt, contains, not_contains. event and email engagement fields: is, is_not, at_least, less_than_count. emailBounced also supports is_temporary_bounce and is_permanent_bounce. stripeProduct: is, is_not, at_least, less_than_count. stripeCurrentProduct/stripeTrialProduct: is, is_not, gte, lte, gt, lt. commerceProduct/commerceCollection: is, is_not, at_least, less_than_count.
      - `value` string, required — Event filters use `eventName:30d` or `eventName:5:30d`. Segment filters use a segment ID. Email engagement fields use a rolling time window (`7d`, `30d`, `90d`, `180d`, `all`), a specific campaign via `campaign:<campaign_id>`, an email-type scope via `marketing:<timeRange>` (marketing-policy campaign, automation, and Send API traffic) or `transactional:<timeRange>` (transactional-policy sends; with is/is_not or the emailBounced subtype operators; scopes require a send-time policy snapshot, so ambiguous older events remain unscoped), or `count:timeRange` (such as `10:30d` or `10:all`) with at_least/less_than_count. Stripe product filters use `prod_123` for bought/current/trialing checks, `prod_123:3` for payment thresholds, and product-scoped values such as `prod_123:is_canceled`, `prod_123:cancels_at:2026-05-26`, `prod_123:end_at:2026-05-26`, or `prod_123:start_at:7 days ago`. Commerce product filters use `provider:productId` (provider one of `shopify`, `woocommerce`, `api`), optionally with an order-count threshold (`shopify:42:2`); a bare product ID matches the ID on any provider. Commerce collection filters use a collection ID or handle (`skincare`), optionally provider-prefixed and/or with an order-count threshold (`shopify:skincare:2`), and match anyone whose orders contain any product currently in that collection.
    - `root` FilterGroup — A nested AND/OR filter group.
      - `kind` 'group', required
      - `id` string, required
      - `joinOperator` 'and' | 'or', required
      - `children` union[], required
        - union
          - FilterLeaf — A single subscriber filter rule.
            - `kind` 'filter' — Required when the filter is inside a v2 root group.
            - `id` string, required
            - `field` 'status' | 'phone' | 'smsStatus' | 'tag' | 'email' | 'emailProvider' | 'added' | 'firstName' | 'lastName' | 'list' | 'attribute' | 'event' | 'segment' | 'stripeProduct' | 'stripeCurrentProduct' | 'stripeTrialProduct' | 'commerceProduct' | 'commerceCollection' | 'emailSent' | 'emailDelivered' | 'emailOpened' | 'emailClicked' | 'emailBounced' | 'emailComplained', required
            - `operator` 'is' | 'is_not' | 'is_empty' | 'is_not_empty' | 'contains' | 'not_contains' | 'less_than' | 'more_than' | 'is_temporary_bounce' | 'is_permanent_bounce' | 'at_least' | 'less_than_count' | 'gte' | 'lte' | 'gt' | 'lt', required — Valid operators depend on the field. status/segment: is, is_not. smsStatus: is, is_not (values: subscribed, unsubscribed, not_subscribed). phone: is_not_empty, is_empty (empty value). tag: contains, not_contains, is_empty, is_not_empty. email: contains, not_contains for domain or substring matching, is, is_not for an exact case-insensitive address. emailProvider/list: is, is_not, is_empty, is_not_empty. firstName/lastName: contains, not_contains, is_empty, is_not_empty. added: less_than, more_than. attribute: is, is_not, is_empty, is_not_empty, gte, lte, gt, lt, contains, not_contains. event and email engagement fields: is, is_not, at_least, less_than_count. emailBounced also supports is_temporary_bounce and is_permanent_bounce. stripeProduct: is, is_not, at_least, less_than_count. stripeCurrentProduct/stripeTrialProduct: is, is_not, gte, lte, gt, lt. commerceProduct/commerceCollection: is, is_not, at_least, less_than_count.
            - `value` string, required — Event filters use `eventName:30d` or `eventName:5:30d`. Segment filters use a segment ID. Email engagement fields use a rolling time window (`7d`, `30d`, `90d`, `180d`, `all`), a specific campaign via `campaign:<campaign_id>`, an email-type scope via `marketing:<timeRange>` (marketing-policy campaign, automation, and Send API traffic) or `transactional:<timeRange>` (transactional-policy sends; with is/is_not or the emailBounced subtype operators; scopes require a send-time policy snapshot, so ambiguous older events remain unscoped), or `count:timeRange` (such as `10:30d` or `10:all`) with at_least/less_than_count. Stripe product filters use `prod_123` for bought/current/trialing checks, `prod_123:3` for payment thresholds, and product-scoped values such as `prod_123:is_canceled`, `prod_123:cancels_at:2026-05-26`, `prod_123:end_at:2026-05-26`, or `prod_123:start_at:7 days ago`. Commerce product filters use `provider:productId` (provider one of `shopify`, `woocommerce`, `api`), optionally with an order-count threshold (`shopify:42:2`); a bare product ID matches the ID on any provider. Commerce collection filters use a collection ID or handle (`skincare`), optionally provider-prefixed and/or with an order-count threshold (`shopify:skincare:2`), and match anyone whose orders contain any product currently in that collection.
          - FilterGroup — recursive
    - `filterJoinOperator` 'and' | 'or'
    - `activeOnly` boolean
    - `search` string
    - `listId` string
  - `tags` string[], required — Tag names, normalized like single-contact tags.
  - `triggerAutomations` boolean — Requires automations:trigger.

## Response `202`

Operation accepted or existing operation replayed

- SubscriberOperationResponse
  - `success` boolean, required
  - `operation` SubscriberOperation, required
    - `id` string, required
    - `companyId` string, required
    - `kind` 'add_tags', required
    - `status` 'queued' | 'running' | 'completed' | 'failed' | 'cancelled', required
    - `total` integer, required — Selected contacts; grows while selection is queued.
    - `processed` integer, required
    - `succeeded` integer, required
    - `failed` integer, required
    - `error` string, nullable, required
    - `failures` object[], required
      - `subscriberId` string, required
      - `error` string, required
    - `createdAt` string, date-time, required
    - `completedAt` string, date-time, nullable, required
    - `expiresAt` string, date-time, required — Processing deadline while active; retention deadline after completion.

## Other responses

- `400` — Invalid tagging input
- `401` — Authentication required
- `403` — Required scope or company role is missing
- `404` — Operation does not exist in this company
- `409` — Request key conflict or cancellation race
- `503` — Operation could not be created; retry with the same request key

## Changes

- **2026-09-08** `f1d8cfd9faaf` — 1 info
  - added `filter, group` discriminator mapping keys to the `audience/root/children/items/` request property
- **2026-09-07** `449d6c640cc7` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/sequenzy/apis/sequenzy-api/changes/subscribers/operations/post.md)

---

[API](https://skmtc.dev/sequenzy/apis/sequenzy-api.md) · [All operations](https://skmtc.dev/sequenzy/apis/sequenzy-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sequenzy/apis/sequenzy-api/revisions/e93563b0d37e?raw)
