---
title: "Tag, move, unsubscribe, or delete many contacts at once"
method: POST
path: "/contacts/bulk"
tags: ["Campaigns"]
---

# Tag, move, unsubscribe, or delete many contacts at once

`POST /contacts/bulk`

## Request

- Base URL: `https://sendhq.cc/api/v1`
- URL: `https://sendhq.cc/api/v1/contacts/bulk`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/sendhq/apis/sendhq-api/docs/contacts/bulk/post.ts> (a function with typed inputs; its response is checked against the schema)

Apply one `action` to up to 10,000 contacts: the IDs in `contact_ids`, or every contact matching `filter` (the same filters as GET /contacts). Actions: `tag` and `untag` (with `tags`), `add_to_list` and `remove_from_list` (with `list_id`), `unsubscribe`, and `delete`. Adding to a list mirrors each contact's status, so nobody is resubscribed. `delete` is permanent and requires `confirm_count` equal to the number of contacts it would delete; without it the API returns 409 with that `count`. IDs from another workspace are ignored.

## Request body

- union
  - object
    - `action` 'tag' | 'untag' | 'add_to_list' | 'remove_from_list' | 'unsubscribe' | 'delete', required
    - `contact_ids` string[], required
    - `filter` object: The same filters as GET /contacts.
      - `list_id` string
      - `status` 'subscribed' | 'pending' | 'unsubscribed' | 'bounced' | 'complained' | 'dormant'
      - `query` string: Email or name substring; `*` is a wildcard on the email (`*@example.com`).
      - `tag` string
      - `source` 'form' | 'api' | 'import'
      - `after` string, date-time: Signed up at or after.
      - `before` string, date-time: Signed up before.
      - `segment_id` string
    - `tags` string[]: For `tag` and `untag`.
    - `list_id` string: For `add_to_list` and `remove_from_list`.
    - `confirm_count` integer: For `delete`: the number of contacts that will be deleted.
  - object
    - `action` 'tag' | 'untag' | 'add_to_list' | 'remove_from_list' | 'unsubscribe' | 'delete', required
    - `contact_ids` string[]
    - `filter` object, required: The same filters as GET /contacts.
      - `list_id` string
      - `status` 'subscribed' | 'pending' | 'unsubscribed' | 'bounced' | 'complained' | 'dormant'
      - `query` string: Email or name substring; `*` is a wildcard on the email (`*@example.com`).
      - `tag` string
      - `source` 'form' | 'api' | 'import'
      - `after` string, date-time: Signed up at or after.
      - `before` string, date-time: Signed up before.
      - `segment_id` string
    - `tags` string[]: For `tag` and `untag`.
    - `list_id` string: For `add_to_list` and `remove_from_list`.
    - `confirm_count` integer: For `delete`: the number of contacts that will be deleted.

## Response `200`

Successful response

- object
  - `ok` boolean, required
  - `action` string, required
  - `count` integer, required

## Other responses

- `400`: Invalid request
- `401`: Missing or invalid authentication
- `402`: A paid entitlement is required for this recipient or feature
- `403`: The workspace, domain, policy, or suppression state forbids the operation
- `404`: Tenant-scoped resource not found
- `409`: Resource state or idempotency conflict
- `413`: Attachment count or bytes exceed the accepted limit
- `422`: Request validation or suppression check failed
- `423`: Sending is paused for this sender identity or workspace; the message gives the reason and how to appeal
- `429`: A monthly, daily, or trial usage limit was reached
- `500`: Unexpected server error
- `502`: An upstream provider operation failed
- `503`: A required provider or signing configuration is unavailable
- `default`: An error response using SendHQ's standard JSON envelope

---

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