Skip to content
API PlatformDevelopers
POST/v1/contacts/tagstable

Add tags to contacts

Needs
Create and edit contacts, tags, contact groups and custom fields (crm:write)
Plan
Any plan with API access
Limits
60 a minute per key
Dry run
Yes — every check runs with ?dry_run=true, nothing changes
Undo
Remove the tags again with Remove tags from contacts.

Adds tags to up to 500 contacts at once. Tag names that do not exist yet are created. Contacts keep their other tags; tagging twice is harmless.

Try it

Body

Contact ids.

Tag names or ids. Names that do not exist yet are created.

Protects against doing it twice if you retry: a retry with the same key gets the first answer back instead of running again.

Test mode (dry run): nothing will change
Turns test mode off for this page only. It switches back when you leave the page.

Code and response

curl -X POST 'https://mcp.wa-api.cloud/v1/contacts/tag?dry_run=true' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "contact_ids": [
    "48213"
  ],
  "tags": [
    "opted-out"
  ]
}'

The code reads your key from $API_KEY.

Body

Body fields
FieldTypeWhat it is
contact_idsrequiredarray of string | integerContact ids.1–500 itemsSigned in? Pick one from your data with “My data”.
tagsrequiredarray of string | integerTag names or ids. Names that do not exist yet are created.1–20 itemsSigned in? Pick one from your data with “My data”.

Headers

Headers
FieldTypeWhat it is
AuthorizationrequiredheaderBearer $API_KEY — your API key.
Api-VersionheaderThe API version to use, e.g. 2026-10-01. Default: the version your key is pinned to.one of: 2026-10-01 · pattern ^\d{4}-\d{2}-\d{2}$
Idempotency-KeyheaderAny unique string (8–128 characters). A retry with the same key returns the first answer instead of running twice. Kept 24 hours.pattern ^[A-Za-z0-9._:-]+$ · 8–128 characters

Response 200

Counts and per-contact failures.

Response fields
FieldTypeWhat it is
requestedrequiredinteger-9007199254740991–9007199254740991
succeededrequiredinteger-9007199254740991–9007199254740991
failedrequiredinteger-9007199254740991–9007199254740991
failuresrequiredarray of objectPer-contact failures (first 50).
idrequiredstring
coderequiredstring
messagerequiredstring
stopped_earlyrequiredbooleantrue when a fatal error (permission, rate limit) stopped the remaining batches; retry those ids.
matchedinteger or nullBy filter: the contacts that matched when the change started (the ones changed).-9007199254740991–9007199254740991
dry_runbooleantrue when this was a dry run: every check ran and nothing changed.

Errors

Errors are application/problem+json. Branch on code.

StatusCodeWhen
400invalid_input

A field is missing or has the wrong format. errors[] points at each field.

401unauthenticated

The Authorization header is missing, the key is unknown, expired or revoked.

403entitlement_required

The workspace's plan does not include API access (api_access).

403insufficient_scope

The key does not have the permission this operation needs.

409conflict

Also returned while a request with the same Idempotency-Key is still running.

429rate_limited

The key or workspace went over its rate limit. Wait for Retry-After seconds.

503upstream_unavailable

A service behind the API is briefly unavailable. Safe to retry with backoff.

504timeout

The change did not finish in time. Retry with the same Idempotency-Key: it never runs twice.

Examples

Mark a contact as opted out

Request body

{
  "contact_ids": [
    "48213"
  ],
  "tags": [
    "opted-out"
  ]
}

Response 200

{
  "requested": 1,
  "succeeded": 1,
  "failed": 0,
  "failures": [],
  "stopped_early": false
}

Webhook events

These events can fire after this call. Subscribe an endpoint to hear about them.

Operation path

The same operation is also at POST /v1/ops/crm_tag_contacts, with every field in the JSON body.