Documentation

Audiences

Lists of contacts: create them, add one person or import up to 5,000 at a time with their opt-out status, page through the members, and remove people.

An audience is a list of contacts. Campaigns mail an audience, segments narrow one, and contact_added automations start when someone joins one. A contact exists once per workspace and can be on any number of audiences, so taking someone off a list and deleting the person are different requests.

Importing is where a migration from another provider happens: bring the old provider's unsubscribes, bounces and complaints across with a status before the first campaign, because that list is the only record of who must not be mailed. See Campaigns for the full flow.

The audience object

FieldTypeDescription
idstringThe audience's id, a UUID. Every path and body field that names an audience takes this, not the name.
namestringThe audience's name. Names are not unique.
contact_countintegerHow many contacts are on the list, recounted after each write through this API that adds or removes members.
created_atstring (ISO 8601)When the audience was created.
{
  "id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "name": "Newsletter",
  "contact_count": 1284,
  "created_at": "2026-09-01T10:00:00.000Z"
}

List audiences

GET /v1/audiences · requires contacts:read

Every audience in the workspace, newest first, with its id and contact_count. Paged with limit and cursor as described in Pagination.

Query parameters

ParameterTypeDefaultDescription
limitinteger50Page size, 1 to 100.
cursorstringnoneThe next_cursor from the previous page.

Example

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

Response

200 OK

{
  "object": "list",
  "data": [
    {
      "id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
      "name": "Newsletter",
      "contact_count": 1284,
      "created_at": "2026-09-01T10:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Create an audience

POST /v1/audiences · requires contacts:write

Creates an empty list. Nothing stops two audiences having the same name, so keep the returned id.

Body

FieldTypeRequiredDefaultDescription
namestringyes1 to 120 characters.

Example

curl -X POST https://api.sendraven.ai/v1/audiences \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "name": "Newsletter" }'

Response

201 Created

An audience object.

{
  "id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "name": "Newsletter",
  "contact_count": 0,
  "created_at": "2026-09-15T12:00:00.000Z"
}

Errors

StatusTypeWhen
422invalid_requestname fails validation, with details. The message is name is required when it is missing or empty, name must be 120 characters or fewer when it is too long, and name must be a string when it is another type.

Retrieve an audience

GET /v1/audiences/{id} · requires contacts:read

Returns one audience. Passing the audience's name instead of its id answers 404, and when the name matches an audience in the workspace (case-insensitively) the message gives its id.

Path parameters

ParameterDescription
idThe audience's id.

Example

curl https://api.sendraven.ai/v1/audiences/b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "name": "Newsletter",
  "contact_count": 1284,
  "created_at": "2026-09-01T10:00:00.000Z"
}

Errors

StatusTypeWhen
404not_foundNo audience with that id in this workspace.

Delete an audience

DELETE /v1/audiences/{id} · requires contacts:write

Deletes the list, not the people on it. Every contact on it loses this membership and keeps everything else: their other audiences, tags, attributes, engagement history, suppressions and topic preferences. Someone who was on no other list stays in the workspace with an empty audience_ids.

Nothing that refers to the audience is changed. A segment built on it stays, and matches nobody from then on.

Path parameters

ParameterDescription
idThe audience's id.

Example

curl -X DELETE https://api.sendraven.ai/v1/audiences/b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "deleted": true
}

Errors

StatusTypeWhen
404not_foundNo audience with that id in this workspace.

List the contacts on an audience

GET /v1/audiences/{id}/contacts · requires contacts:read

The members of one list, as contact objects, sorted by email address A to Z.

This endpoint's cursor is not the opaque timestamp cursor other lists use: next_cursor is the last address on the page, and the next page starts at the first address after it. Pass it back unchanged as cursor while has_more is true. after is accepted as an older name for cursor; when both are sent, cursor wins.

Path parameters

ParameterDescription
idThe audience's id.

Query parameters

ParameterTypeDefaultDescription
limitinteger50Page size, 1 to 100.
cursorstringnoneThe next_cursor from the previous page: an email address. Only addresses that sort after it are returned.
afterstringnoneAlias for cursor.

Example

curl "https://api.sendraven.ai/v1/audiences/b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e/contacts?limit=2" \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "object": "list",
  "data": [
    {
      "id": "5f0c6a9e-2b7d-4c1e-9a3f-8d2e1b6c4a70",
      "email": "ana@example.com",
      "first_name": "Ana",
      "last_name": "Lima",
      "audience_ids": ["b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e"],
      "tags": ["trial"],
      "attributes": { "plan": "pro" },
      "unsubscribed": false,
      "unsubscribed_at": null,
      "last_opened_at": "2026-09-14T08:12:44.000Z",
      "last_clicked_at": null,
      "last_sent_at": null,
      "last_active_at": null,
      "created_at": "2026-09-01T10:30:00.000Z"
    },
    {
      "id": "c3a8e5f1-9d2b-4a6c-8e7f-5b1d3c9a2e84",
      "email": "bo@example.com",
      "first_name": null,
      "last_name": null,
      "audience_ids": ["b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e", "e1d2c3b4-a5f6-4e7d-8c9b-0a1f2e3d4c5b"],
      "tags": [],
      "attributes": {},
      "unsubscribed": true,
      "unsubscribed_at": "2026-09-10T16:45:00.000Z",
      "last_opened_at": null,
      "last_clicked_at": null,
      "last_sent_at": null,
      "last_active_at": null,
      "created_at": "2026-09-02T09:15:00.000Z"
    }
  ],
  "has_more": true,
  "next_cursor": "bo@example.com"
}

Errors

StatusTypeWhen
404not_foundNo audience with that id in this workspace.

Add contacts to an audience

POST /v1/audiences/{id}/contacts · requires contacts:write

Adds one contact, or imports many. The shape of the body decides which: a JSON object adds one person and returns the contact, and a JSON array imports up to 5,000 and returns counts. Both go through the same import, so every rule below applies to both. Importing a large list one request per person is how a migration takes a week; send arrays.

Adding is an upsert on the address, and safe to re-run:

  • Existing people are updated, not duplicated. An address already in the workspace joins this list and keeps its other memberships. Names are overwritten only by a non-empty value, tags are added to the ones the person already has (none are removed), and attributes are merged key by key.
  • Attributes are typed by contact properties. Each value is coerced to its property's type, the same way a contact update coerces it. A key with no declared property creates a string property, and its values are stored as strings, so declare number and boolean properties before importing. A key that is empty, contains . or a null character, or starts with $ is refused.
  • status carries opt-outs across. unsubscribed sets the contact's unsubscribed flag and writes a marketing-scoped suppression, so the person still gets transactional mail. bounced sets the flag and writes a hard-bounce suppression for all mail; complained sets the flag and writes a complaint suppression for all mail. unsubscribed: true is a synonym for status: "unsubscribed", and status wins when both are sent. Each suppression's detail reads Imported from followed by the source parameter.
  • An opt-out stops what is already queued. For every row with status unsubscribed, bounced or complained, the address's pending scheduled sends are cancelled and its active automation enrolments end, exactly as an unsubscribe does, so a migrated opt-out does not still receive the rest of a sequence.
  • An import never resubscribes anyone. A row with status: "subscribed" (or no status) for someone already unsubscribed leaves them unsubscribed, with their suppression intact. The old provider's file can predate an opt-out made here. Resubscribing is a deliberate contact update.
  • Automations start only for people new to this audience, or for tags new to the contact, and never for a suppressed address. A single contact always starts the audience's contact_added automations, and the tag_added automations for each tag it did not already carry. An array starts them only with ?trigger_automations=true.

Leave trigger_automations off when importing an existing list. A migrated list is not a few thousand new signups, and a welcome sequence sent to all of it at once is a cold blast from a domain that has never mailed those people, the one deliverability mistake with no cheap recovery. See Automations.

An array is validated as a whole before anything is written: one invalid row fails the request with 422, and each problem in details has a path that starts with the row's index. Within an array, repeats of the same address (compared trimmed and lower-cased) after the first are skipped for the contact itself: only the first row's names, tags, attributes and status are applied to the contact. A repeat's status is still acted on for suppressions, so an address listed first as subscribed and again as unsubscribed ends up with a marketing suppression while a new or subscribed contact's unsubscribed flag stays false. Request bodies are limited to 5 MB; a larger body is answered with 413 payload_too_large, so split very large imports into several requests.

Path parameters

ParameterDescription
idThe audience's id.

Query parameters

ParameterTypeDefaultDescription
sourcestringthe APIWhere the list came from, such as Mailchimp. Recorded on each suppression the request writes as Imported from Mailchimp. Cut to 60 characters. An empty value records just Imported.
trigger_automationsbooleanfalseFor an array only: true starts contact_added automations for everyone new to this audience, and tag_added automations for every tag a contact did not already carry. Any value other than true counts as false. Ignored for a single contact, which always starts them.

Body

Either one contact object, or an array of 0 to 5,000 of them. Each contact takes:

FieldTypeRequiredDefaultDescription
emailstringyesA valid email address. Stored trimmed and lower-cased.
first_namestringnoUp to 120 characters.
last_namestringnoUp to 120 characters.
tagsarray of stringnoUp to 50 tags, each 1 to 60 characters. Lower-cased and trimmed, with runs of spaces turned into a hyphen.
attributesobjectnoCustom property values by key; each value a string, number or boolean. Keys cannot be empty, contain . or a null character, or start with $.
status"subscribed" | "unsubscribed" | "bounced" | "complained"no"subscribed"Standing with the previous sender. See above.
unsubscribedbooleannotrue is a synonym for status: "unsubscribed". Ignored when status is sent.
last_active_atstring (ISO 8601)noWhen the person was last active in your product, with a time zone. Kept only if later than the stored value. More than a day in the future is refused.

Example

One contact:

curl -X POST https://api.sendraven.ai/v1/audiences/b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e/contacts \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "email": "ana@example.com",
    "first_name": "Ana",
    "tags": ["trial"]
  }'

An import from another provider:

curl -X POST "https://api.sendraven.ai/v1/audiences/b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e/contacts?source=Mailchimp" \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '[
    { "email": "ana@example.com", "first_name": "Ana", "attributes": { "plan": "pro" } },
    { "email": "bo@example.com", "status": "unsubscribed" },
    { "email": "old@example.com", "status": "bounced" },
    { "email": "angry@example.com", "status": "complained" }
  ]'

Response

201 Created

For one contact, the contact object as it stands after the write, showing every audience, tag and attribute the person has:

{
  "id": "5f0c6a9e-2b7d-4c1e-9a3f-8d2e1b6c4a70",
  "email": "ana@example.com",
  "first_name": "Ana",
  "last_name": null,
  "audience_ids": ["b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e"],
  "tags": ["trial"],
  "attributes": {},
  "unsubscribed": false,
  "unsubscribed_at": null,
  "last_opened_at": null,
  "last_clicked_at": null,
  "last_sent_at": null,
  "last_active_at": null,
  "created_at": "2026-09-15T12:00:00.000Z"
}

For an array, counts:

FieldTypeDescription
insertedintegerPeople new to the workspace.
updatedintegerPeople who already existed in the workspace, whether or not they were already on this list.
skippedintegerRows not written to the contact because the same address appeared earlier in the array.
unsubscribedintegerRows with status unsubscribed. Counts rows, so a repeated address counts each time.
bouncedintegerRows with status bounced.
complainedintegerRows with status complained.
suppressedintegerSuppressions this request created. An address that already had a suppression of the same scope is not counted again.
properties_createdarray of stringKeys of contact properties created because a row carried an undeclared attribute.
automations_startedintegerAutomation enrolments started. 0 unless trigger_automations=true.
contact_countintegerThe audience's contact count after the import.
{
  "inserted": 3,
  "updated": 1,
  "skipped": 0,
  "unsubscribed": 1,
  "bounced": 1,
  "complained": 1,
  "suppressed": 3,
  "properties_created": ["plan"],
  "automations_started": 0,
  "contact_count": 4
}

Errors

StatusTypeWhen
404not_foundNo audience with that id in this workspace.
422invalid_requestAn attribute key is empty, contains . or a null character, or starts with $. Nothing in the request is written. The message starts attributes: and names the key, with details.
422invalid_requestA contact fails validation in any other way, or an array has more than 5,000 entries (message Validation failed, with details).
413payload_too_largeThe body is larger than 5 MB.

Remove a contact from an audience

DELETE /v1/audiences/{id}/contacts/{contact_id} · requires contacts:write

Takes one person off this list. The contact still exists and keeps their other audiences, tags, attributes, engagement history, suppressions and topic preferences. To remove the person from the workspace entirely, use Delete a contact.

It does not check that the person was on the list: removing someone who was not a member answers 200 with removed: true all the same. It does not end automation enrolments the person already has.

Path parameters

ParameterDescription
idThe audience's id.
contactIdThe contact's id, from GET /v1/contacts?email=. Not the email address.

Example

curl -X DELETE https://api.sendraven.ai/v1/audiences/b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e/contacts/5f0c6a9e-2b7d-4c1e-9a3f-8d2e1b6c4a70 \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "id": "5f0c6a9e-2b7d-4c1e-9a3f-8d2e1b6c4a70",
  "audience_id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "removed": true,
  "contact_count": 1283,
  "note": "The contact still exists and keeps their other audiences, suppression and history."
}

Errors

StatusTypeWhen
404not_foundNo audience with that id in this workspace, or no contact with that contactId. When contactId is an address belonging to a contact, the message gives the contact's id.