Tags & Lists
Tags and lists are two complementary ways to organize your contacts in SendSeven. Tags are color-coded labels for flexible categorization. Lists are named collections of contacts used for campaigns and bulk operations.
Tags vs. Lists
| Aspect | Tags | Lists |
|---|---|---|
| Apply to | Contacts and conversations | Contacts only |
| Use case | Flexible categorization, filtering | Campaign targeting, bulk operations |
| Visual | Color-coded labels | Named groups |
| Multiple | A resource can have many tags | A contact can be in many lists |
Use tags for dynamic categorization and visual organization. Use lists for campaign recipient groups and structured audience segments.
Required Scopes
| Scope | Purpose |
|---|---|
contacts:read or conversations:read | List and view tags |
tags:create | Create tags |
tags:update | Rename or recolor tags |
tags:delete | Delete tags |
contacts:update | Add/remove tags on contacts |
conversations:update | Add/remove tags on conversations |
lists:read | List and view contact lists |
lists:create | Create new lists |
lists:update | Add/remove contacts from lists |
lists:read + contacts:read | List the contacts on a list, and preview or count which contacts a set of conditions would match |
lists:delete | Delete lists |
Tags
List Tags
curl -X GET "https://api.sendseven.com/api/v1/tags?page=1&page_size=20" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
{
"items": [
{
"id": "tag_vip",
"name": "VIP",
"color": "#EF4444",
"description": "High-value customers",
"created_at": "2026-01-10T09:00:00Z"
},
{
"id": "tag_new",
"name": "New Customer",
"color": "#10B981",
"description": "Recently onboarded",
"created_at": "2026-01-12T14:00:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total": 3,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
}
Usage counts
Add include_usage=true to see how many contacts and conversations carry each tag. Every item then also has contact_count and conversation_count (0 when the tag is unused). The counts cover the tags on the returned page. Leave the parameter off when you don't need the counts; the plain list is faster.
curl -X GET "https://api.sendseven.com/api/v1/tags?include_usage=true&page=1&page_size=20" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
{
"items": [
{
"id": "tag_vip",
"name": "VIP",
"color": "#EF4444",
"description": "High-value customers",
"created_at": "2026-01-10T09:00:00Z",
"contact_count": 128,
"conversation_count": 342
}
],
"pagination": { "page": 1, "page_size": 20, "total": 1, "total_pages": 1, "has_next": false, "has_prev": false }
}
Create a Tag
curl -X POST "https://api.sendseven.com/api/v1/tags" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Enterprise",
"color": "#6366F1",
"description": "Enterprise-tier customers"
}'
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tag name (max 50 chars, unique per workspace) |
color | string | No | Hex color code (e.g., #EF4444) |
description | string | No | Tag description (max 255 chars) |
Tag names are unique per workspace. The comparison ignores case, accents and trailing spaces, so VIP, vip and vip count as the same name. Creating or renaming a tag to an existing name returns 400:
{"detail": "A tag with the name 'VIP' already exists"}
Tags are also used as team-member skills for routing. See Skill-Based Routing.
Update a Tag
curl -X PUT "https://api.sendseven.com/api/v1/tags/tag_enterprise" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"color": "#8B5CF6",
"description": "Enterprise and strategic accounts"
}'
Delete a Tag
Deleting a tag removes it from all contacts and conversations it is applied to. This removal does not send contact.tag_removed webhooks and does not start contact_tag_removed flows; only removing a tag from a contact does.
curl -X DELETE "https://api.sendseven.com/api/v1/tags/tag_enterprise" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
{"success": true, "message": "Tag deleted successfully"}
A tag that a custom inbox uses in its filter rules cannot be deleted. Remove it from those inboxes first; until then the call returns 409:
{
"error": "This tag can't be deleted because it is used in the filter rules of these inboxes: “Support DE”. Remove it from those inbox rules first (Settings > Inboxes), then delete it.",
"code": "TAG_IN_USE_BY_INBOX",
"details": {"inboxes": [{"id": "inbox_1", "name": "Support DE"}]}
}
Unlike most errors, this body is not wrapped in detail.
Add a Tag to a Contact
Scope contacts:update. Idempotent: adding a tag the contact already has succeeds without change.
curl -X POST "https://api.sendseven.com/api/v1/contacts/contact_xyz789/tags/tag_vip" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
Remove a Tag from a Contact
curl -X DELETE "https://api.sendseven.com/api/v1/contacts/contact_xyz789/tags/tag_vip" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
Add a Tag to a Conversation
Scope conversations:update. DELETE on the same path removes it.
curl -X POST "https://api.sendseven.com/api/v1/conversations/conv_abc123/tags/tag_urgent" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
To tag many contacts at once, use the add_tags / remove_tags bulk actions.
Lists
List All Lists
curl -X GET "https://api.sendseven.com/api/v1/lists?page=1" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
{
"items": [
{
"id": "list_newsletter",
"name": "Newsletter Subscribers",
"description": "Main weekly newsletter recipients",
"list_type": "newsletter",
"contact_count": "2450",
"created_at": "2026-01-05T10:00:00Z",
"updated_at": "2026-02-10T12:00:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total": 5,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
}
contact_count is returned as a string. A dynamic list's segment_conditions are included only when your API key has contacts:read, because conditions can name custom fields; without it they are null. Ids, names and counts are always returned.
Create a List
curl -X POST "https://api.sendseven.com/api/v1/lists" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Product Launch Invites",
"description": "Contacts to invite to the Q2 product launch event",
"list_type": "newsletter"
}'
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | List name (1 to 255 characters) |
list_type | string | Yes | newsletter (contacts subscribe per channel) or dynamic (membership computed from segment_conditions). static exists only for older lists |
description | string | No | List description (max 1000 characters) |
segment_conditions | object | Dynamic lists | The condition tree, see Dynamic lists |
slug, newsletter_config, show_in_preference_center, opt_in_action | No | Newsletter settings (sign-up keywords, welcome messages, preference center). See Getting Subscribers | |
is_default | boolean | No | Make this the workspace's default newsletter list. Not allowed for dynamic lists (400) |
Dynamic lists
A dynamic list has no members of its own: it shows every contact that matches its segment_conditions at the moment you read it. Every dynamic list must start from the subscribers of at least one newsletter list, so nobody who never subscribed to anything can end up in it. The top level of the tree is an AND group whose first child is that list membership:
{
"name": "German VIP subscribers",
"list_type": "dynamic",
"segment_conditions": {
"operator": "AND",
"groups": [
{"type": "condition", "field": "has_optin", "operator": "equals", "value": true, "list_id": "list_newsletter", "channel_type": "any"},
{"type": "condition", "field": "has_tag", "value": "tag_vip"},
{"type": "condition", "field": "custom_field", "key": "country", "operator": "equals", "value": "DE"}
]
}
}
Several membership conditions at the top level mean "subscribed to all of them"; wrap them in an OR group (containing only membership conditions) for "subscribed to any of them". Violations return 422 with detail.code audience_list_membership_required (no top-level membership), audience_membership_list_invalid (the list is unknown or not a newsletter list) or audience_conditions_invalid (malformed tree).
How conditions match:
- Negative operators (
not_equals,not_contains,is_empty, ...) also match contacts that have no value at all. - A condition on a custom field that has been deleted matches nobody, so the list shrinks rather than silently widening. If you later create a field with the same key, the condition uses the new field.
- Number custom fields compare numerically (
gt,gte,lt,lte,between):10is greater than9. Values that are not plain numbers never match a number comparison. - A malformed condition matches nobody.
Check a tree before saving it with POST /lists/evaluate-count (body: the condition tree, returns {"count": n}), or preview the matching contacts for an existing list with POST /lists/{list_id}/evaluate (same body, returns the contacts and total_contacts; nothing is saved). Both need lists:read and contacts:read, because they reveal which contacts match. Count a saved list with POST /lists/{list_id}/count (lists:read only).
Add a Contact to a List
curl -X POST "https://api.sendseven.com/api/v1/lists/list_launch/members" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_001",
"channel_type": "whatsapp"
}'
{"success": true, "message": "Contact subscribed to list", "member_id": "sub_8c1d2e"}
| Field | Required | Description |
|---|---|---|
contact_id | Yes | The contact |
channel_type | Newsletter lists | The channel the contact subscribes on: email, sms, whatsapp, telegram, messenger, instagram, ... A contact can subscribe to the same list on several channels. Omitting it still falls back to email, but that fallback is deprecated |
channel_id | Telegram, Messenger, Instagram | The channel the subscription belongs to |
Dynamic lists take their members from their conditions: add the contact to the newsletter list the dynamic list starts from instead. To add many contacts at once, use the add_to_list bulk action or an import with list_id.
Remove a Contact from a List
curl -X DELETE "https://api.sendseven.com/api/v1/lists/list_launch/members/contact_003?channel_type=whatsapp" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
Returns 204. For newsletter lists pass channel_type: it removes that one channel's subscription. Dynamic lists return 400 (Cannot remove contacts from dynamic lists).
Contacts on a List
GET /lists/{list_id}/contacts (scopes lists:read and contacts:read) returns the list's contacts, paginated. You can also filter GET /contacts by list_id together with list_subscription_status and list_subscription_channel (see List Contacts).
Error Responses
Most errors return detail as a message string; the cases below that carry a machine-readable code are listed with it. See Error Handling for all response shapes.
| Status | detail | Description |
|---|---|---|
| 400 | "A tag with the name 'VIP' already exists" | Tag name already used in the workspace |
| 401 | "Invalid or expired API token" | Missing or invalid token |
| 403 | "Missing required permission(s): tags:create" | Token lacks a required scope |
| 404 | "Tag not found", "List not found", "Contact not found" | Unknown ID, or it belongs to another workspace |
| 409 | code: TAG_IN_USE_BY_INBOX | The tag is used by a custom inbox filter (body not wrapped in detail, see Delete a Tag) |
| 422 | code: audience_* | Invalid dynamic list conditions |
| 422 | list of field errors | Invalid request body |
Next Steps
- Manage Contacts -- full contact CRUD operations
- Custom Fields -- store custom data on contacts
- Messaging Campaigns -- target lists with campaigns