Documentation

Topics

Categories of marketing mail a person can opt out of one at a time, and each person's preferences across them.

A topic is a kind of mail someone can opt out of on its own: a newsletter, product updates, tips. Send under a topic and the unsubscribe link in that message opts the person out of that topic only, so someone who wants fewer newsletters can say so without pressing the spam button or losing every other kind of mail. The Topics guide covers the unsubscribe page and how topics interact with suppressions.

A preference is stored on the combination of workspace, email address and topic, never on a contact row. It belongs to the person, not to whichever audience they are in, so someone on two lists has one preference per topic, and it survives the contact being deleted. When a person has no preference recorded for a topic, the topic's default_subscribed applies. Defaults are not written out as rows, so changing a topic's default moves everyone who never chose.

Topics are addressed by key, not by id.

The topic object

Every field is always present.

FieldTypeDescription
idstringThe topic's id (a UUID). Not used by any endpoint.
keystringLower-case letters, digits and hyphens. Unique within the workspace. The identifier used in paths, in topic on POST /v1/emails, in topic_key on campaigns and automations, and in unsubscribe links.
namestringShown to the person on the preference page.
descriptionstring | nullShown on the preference page under the name. null when not set, including on a topic created before descriptions were stored.
default_subscribedbooleanWhether someone with no recorded preference receives this topic.
created_atstring (ISO 8601)When the topic was created.
{
  "id": "5d8e1a3c-7b2f-4e9d-a6c4-0f3b8e2d1a75",
  "key": "newsletter",
  "name": "Monthly newsletter",
  "description": "One email a month with what shipped.",
  "default_subscribed": true,
  "created_at": "2026-09-15T12:00:00.000Z"
}

List topics

GET /v1/topics · requires contacts:read

Returns every topic in the workspace, sorted by name, in the list envelope without paging. Up to 500 are returned; at 500, has_more is true.

Example

curl https://api.sendraven.ai/v1/topics \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "object": "list",
  "data": [
    {
      "id": "5d8e1a3c-7b2f-4e9d-a6c4-0f3b8e2d1a75",
      "key": "newsletter",
      "name": "Monthly newsletter",
      "description": "One email a month with what shipped.",
      "default_subscribed": true,
      "created_at": "2026-09-15T12:00:00.000Z"
    },
    {
      "id": "9c2f7e4a-1d6b-4a8e-b3f5-7e0d2c9a4b61",
      "key": "product-updates",
      "name": "Product updates",
      "description": null,
      "default_subscribed": false,
      "created_at": "2026-09-15T12:01:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Create or update a topic

POST /v1/topics · requires contacts:write

Creates a topic, or updates the one with this key if it already exists, in one write. A new topic returns 201; an existing one returns 200 with the stored topic after the update.

On an update, name is replaced, and description and default_subscribed are changed only when the body includes them; a field left out keeps its stored value. Send description: null to clear a description. The defaults, default_subscribed: true and no description, apply only when the topic is created.

Default-on suits an existing newsletter people already expect. Default-off suits anything a person should have to ask for. Changing the default of an existing topic moves everyone who never chose, so only send default_subscribed on an update when that is what you mean.

Body

FieldTypeRequiredDefaultDescription
keystringyesLower-case letters, digits and hyphens.
namestringyes1 to 120 characters.
descriptionstring | nullnonone on create, unchanged on updateUp to 300 characters. null clears it.
default_subscribedbooleannotrue on create, unchanged on updateWhether someone with no recorded preference receives this topic.

Example

curl -X POST https://api.sendraven.ai/v1/topics \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "key": "newsletter",
    "name": "Monthly newsletter",
    "description": "One email a month with what shipped.",
    "default_subscribed": true
  }'

Response

201 Created for a new topic, 200 OK for an update.

{
  "id": "5d8e1a3c-7b2f-4e9d-a6c4-0f3b8e2d1a75",
  "key": "newsletter",
  "name": "Monthly newsletter",
  "description": "One email a month with what shipped.",
  "default_subscribed": true,
  "created_at": "2026-09-15T12:00:00.000Z"
}

Errors

StatusTypeWhen
422invalid_requestThe body failed validation. details lists each field.

Delete a topic

DELETE /v1/topics/{key} · requires contacts:write

Deletes the topic and every preference recorded against it.

A topic that something still sends under cannot be deleted: an active or paused automation with that topic_key, or a campaign with it that is draft, scheduled, sending, paused or testing. The call answers 409 invalid_state naming each one, and nothing is deleted. Change the automation's topic_key or delete it, and delete the campaign or let it finish, then delete the topic. Finished campaigns (sent, failed) and draft automations do not block it.

Deleting a topic erases every opt-out from it, and nothing can restore them. Mail that still names the key is not sent. Creating a send, campaign, automation or segment that names it is refused with 422 unknown_topic. A send already scheduled or held for approval under it skips every recipient, and a saved segment with that topic_key matches nobody. A draft automation with that topic_key answers enrolments with 409 (invalid_state, reason topic_missing) once it is activated. Recreating the key brings back the default for everyone, including the people who had opted out.

Path parameters

ParameterDescription
keyThe topic's key.

Example

curl -X DELETE https://api.sendraven.ai/v1/topics/newsletter \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{ "key": "newsletter", "deleted": true }

Errors

StatusTypeWhen
404not_foundNo topic with this key in the workspace.
409invalid_stateAn active or paused automation, or a campaign that has not finished, still uses the topic. The message names each one (up to 20 of each) with its id and status.

Get preferences

GET /v1/topics/preferences · requires contacts:read

Returns every topic in the workspace, sorted by name, each with subscribed resolved for one address: the person's recorded choice where there is one, the topic's default where there is not. Check this before concluding that someone's mail is failing to arrive; an opt-out looks the same as a delivery problem from the outside.

This reports topic preferences only. An address suppressed for marketing receives no marketing mail whatever it shows here; check suppressions as well.

Query parameters

ParameterTypeDefaultDescription
emailstringrequiredThe address to look up. Matched case-insensitively.

Example

curl "https://api.sendraven.ai/v1/topics/preferences?email=ana@example.com" \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

Each entry is a topic object with subscribed added, a boolean, in the list envelope. One entry per topic, not paged: next_cursor is always null, and has_more is true only when the workspace has 500 topics or more, the most this returns.

{
  "object": "list",
  "data": [
    {
      "id": "5d8e1a3c-7b2f-4e9d-a6c4-0f3b8e2d1a75",
      "key": "newsletter",
      "name": "Monthly newsletter",
      "description": "One email a month with what shipped.",
      "default_subscribed": true,
      "created_at": "2026-09-15T12:00:00.000Z",
      "subscribed": false
    },
    {
      "id": "9c2f7e4a-1d6b-4a8e-b3f5-7e0d2c9a4b61",
      "key": "product-updates",
      "name": "Product updates",
      "description": null,
      "default_subscribed": false,
      "created_at": "2026-09-15T12:01:00.000Z",
      "subscribed": false
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Errors

StatusTypeWhen
422invalid_requestemail is missing or empty.

Set preferences

POST /v1/topics/preferences · requires contacts:write

Records one person's choice for one or more topics. Only do this when the person has asked: re-subscribing someone who opted out is what produces spam complaints.

Opting out (false) immediately ends the person's active enrolments in every automation sent under that topic, with reason unsubscribed_from_topic. Sends already scheduled under the topic are not cancelled, but each is checked again when it comes due and leaves out anyone opted out by then. Opting in (true) restarts nothing, and does not lift a suppression.

Every key is checked before anything is written. If any key names no topic, the request answers 422 unknown_topic and no preference is saved, so the call can be corrected and retried as a whole.

Body

FieldTypeRequiredDefaultDescription
emailstringyesA single email address. Stored lower-cased.
topicsobjectyesTopic key to true (subscribed) or false (opted out), for example { "newsletter": false }. Topics not named are left as they are.

Example

curl -X POST https://api.sendraven.ai/v1/topics/preferences \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "ana@example.com", "topics": { "newsletter": false, "product-updates": true } }'

Response

200 OK

The person's preferences across every topic after the change, in the same list envelope as Get preferences.

{
  "object": "list",
  "data": [
    {
      "id": "5d8e1a3c-7b2f-4e9d-a6c4-0f3b8e2d1a75",
      "key": "newsletter",
      "name": "Monthly newsletter",
      "description": "One email a month with what shipped.",
      "default_subscribed": true,
      "created_at": "2026-09-15T12:00:00.000Z",
      "subscribed": false
    },
    {
      "id": "9c2f7e4a-1d6b-4a8e-b3f5-7e0d2c9a4b61",
      "key": "product-updates",
      "name": "Product updates",
      "description": null,
      "default_subscribed": false,
      "created_at": "2026-09-15T12:01:00.000Z",
      "subscribed": true
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Errors

StatusTypeWhen
422invalid_requestemail is not an address, or topics is missing or has a value that is not a boolean.
422unknown_topicA key in topics names no topic in this workspace. The message starts with the field, for example topics.promos:. Nothing was saved.

Import preferences

POST /v1/topics/{key}/import · requires contacts:write

Sets the same preference for many addresses on one topic. This is the migration path: a previous provider knows who opted out of which list, and that has to be in place before the first send under the topic. Existing preferences for those addresses are overwritten.

An import with subscribed: false immediately ends the active enrolments of every address it names in each automation sent under this topic, with reason unsubscribed_from_topic, as Set preferences does for one person. Sends already scheduled under the topic are checked again when they come due, and leave out the addresses opted out. An import with subscribed: true restarts nothing. It does not touch suppressions; import those separately.

The request body as a whole is limited to 5 MB. A larger body is answered with 413 payload_too_large, so split a very long list across several calls.

Path parameters

ParameterDescription
keyThe topic's key.

Body

FieldTypeRequiredDefaultDescription
emailsarray of stringyes1 to 50,000 email addresses. Lower-cased, and duplicates written once.
subscribedbooleanyesThe preference to record for every address: false for opted out, true for subscribed.

Example

curl -X POST https://api.sendraven.ai/v1/topics/newsletter/import \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "emails": ["ana@example.com", "sam@example.com"], "subscribed": false }'

Response

201 Created

written is the number of distinct addresses whose preference was recorded.

{ "topic": "newsletter", "written": 2 }

Errors

StatusTypeWhen
404not_foundNo topic with this key in the workspace. Checked before the body.
413payload_too_largeThe request body is larger than 5 MB.
422invalid_requestemails is missing, empty, longer than 50,000 or contains something that is not an address, or subscribed is missing.