Topics
Manage subscription topics and the contacts subscribed to them.
Topics are named subscription categories, such as product-updates or security-alerts. When a topic has opt_in_required set to true, only contacts who explicitly subscribed receive emails for it; otherwise emails go to everyone except those who unsubscribed.
List topics
GET /v1/topics
Requires read_only scope.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Results per page (max 100). |
cursor | string | Pagination cursor from the previous response's next_cursor. |
Response
{
"data": [
{
"id": "top_abc123",
"name": "product-updates",
"description": "Product feature announcements and release notes",
"opt_in_required": true,
"created_at": "2025-03-12T09:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}
Create a topic
POST /v1/topics
Requires full_access scope. Topic names are unique per organization; reusing an existing name returns 409 with code topic_name_taken.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Topic name, unique per org (1-200 characters). |
description | string | No | Human-readable description of the topic. |
opt_in_required | boolean | No | If true, only explicit subscribers receive the topic. Defaults to false. |
Example request
{
"name": "product-updates",
"description": "Product feature announcements and release notes",
"opt_in_required": true
}
Response
{
"id": "top_abc123",
"name": "product-updates",
"description": "Product feature announcements and release notes",
"opt_in_required": true,
"created_at": "2025-03-12T09:00:00Z"
}
Get a topic
GET /v1/topics/:id
Requires read_only scope.
Response
{
"id": "top_abc123",
"name": "product-updates",
"description": "Product feature announcements and release notes",
"opt_in_required": true,
"created_at": "2025-03-12T09:00:00Z"
}
Delete a topic
DELETE /v1/topics/:id
Requires full_access scope.
Response
{
"deleted": true
}
Subscribe a contact to a topic
POST /v1/topics/:id/subscribe
Requires full_access scope. The contact must already exist in the organization, matched by email; otherwise the request returns 404 with code contact_not_found.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email of the contact to subscribe. |
Example request
{
"email": "jane@example.com"
}
Response
{
"contact_id": "ct_abc123",
"topic_id": "top_abc123",
"subscribed": true
}
Unsubscribe a contact from a topic
POST /v1/topics/:id/unsubscribe
Requires full_access scope. The contact must already exist in the organization, matched by email.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email of the contact to unsubscribe. |
Example request
{
"email": "jane@example.com"
}
Response
{
"contact_id": "ct_abc123",
"topic_id": "top_abc123",
"subscribed": false
}
List subscribers for a topic
GET /v1/topics/:id/subscribers
Requires read_only scope. Returns all subscription records for the topic, including unsubscribed contacts, ordered by updated_at descending.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Results per page (max 100). |
cursor | string | Pagination cursor from the previous response's next_cursor. |
Response
{
"data": [
{
"contact_id": "ct_abc123",
"email": "jane@example.com",
"subscribed": true,
"updated_at": "2025-03-12T09:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}