Create a series

POST /api/v1/series Creates the series and its version 1 draft, in one transaction. Answers 201. This is the route that POST /api/v1/sequences refuses: there, triggerType: "fixed_date" returns 400, because a series needs its audience and its start date in the same call. Here the trigger is imposed — sending another triggerType in the body changes nothing.

Authorization

RoleAccess
OWNERAllowed
ADMINAllowed
MEMBERRefused — 403

Request body

FieldTypeRequiredDescription
namestringYes1 to 100 characters
listIdstring (uuid)YesThe list the series sends to
startsAtstring (ISO 8601)NoDay 1. Optional here — a series is created then scheduled — and required at publish
audienceSegmentIdstring (uuid)NoNarrows the list
audienceTagIdstring (uuid)NoNarrows it further
json
{
  "name": "March launch",
  "listId": "9f8e7d6c-1111-4bbb-8ccc-ddddeeeeffff",
  "startsAt": "2026-03-15T08:00:00.000Z",
  "audienceSegmentId": null,
  "audienceTagId": null
}

Example

bash
curl -s -X POST 'https://www.agentsmail.io/api/v1/series' \
  -H "x-api-key: $AGENTMAIL_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "name": "March launch",
    "listId": "9f8e7d6c-1111-4bbb-8ccc-ddddeeeeffff",
    "startsAt": "2026-03-15T08:00:00.000Z"
  }'

Response

CodeWhen
201The series and its version 1 draft exist
400Invalid body, or startsAt is not a readable date
401Missing or invalid key
403The key's role is MEMBER

Response fields

Same shape as list series, for the single object in data.

Example response

json
{
  "success": true,
  "data": {
    "id": "7d7d7d7d-8888-4ccc-9ddd-eeee00001111",
    "name": "March launch",
    "status": "paused",
    "startsAt": "2026-03-15T08:00:00.000Z",
    "listId": "9f8e7d6c-1111-4bbb-8ccc-ddddeeeeffff",
    "audienceSegmentId": null,
    "audienceTagId": null,
    "topicId": null,
    "createdAt": "2026-03-01T10:00:00.000Z",
    "updatedAt": "2026-03-01T10:00:00.000Z"
  }
}

Agent recipe

Creating the series is the first of four steps — on its own it sends nothing. The id you get back is a sequence id, and the steps are added through the Sequences API.
js
const headers = {
  'x-api-key': process.env.AGENTMAIL_API_KEY,
  'content-type': 'application/json',
}
const api = 'https://www.agentsmail.io/api/v1'

// 1. The series, with its audience and its start date
const {data: series} = await fetch(`${api}/series`, {
  method: 'POST',
  headers,
  body: JSON.stringify({name: 'March launch', listId, startsAt: '2026-03-15T08:00:00.000Z'}),
}).then((r) => r.json())

// 2. Its version 1 draft already exists — find it
const {data: versions} = await fetch(`${api}/sequences/${series.id}/versions`, {
  headers,
}).then((r) => r.json())
const draft = versions.find((v) => v.status === 'draft')

// 3. The steps, on the Sequences API — in TWO calls each for a send.
//    Creating a node only takes its `type` (plus `waitHours` on a wait, or
//    `templateId` to start from a template): a new send step is born with the
//    default content and no subject. The subject, the content and the send
//    time are written by a PATCH on the node that comes back.
const nodes = `${api}/sequences/${series.id}/versions/${draft.id}/nodes`

const addNode = (body) =>
  fetch(nodes, {method: 'POST', headers, body: JSON.stringify(body)})
    .then((r) => r.json())
    .then((r) => r.data)

const editNode = (id, body) =>
  fetch(`${nodes}/${id}`, {method: 'PATCH', headers, body: JSON.stringify(body)})
    .then((r) => r.json())
    .then((r) => r.data)

// One PATCH per FAMILY: the subject and the content are one family, the send
// time is another, and sending two families in a single call is refused.
const teaser = await addNode({type: 'send_email'})
await editNode(teaser.id, {subject: 'Something is coming', content: html})
// The time of day, in minutes from midnight. Leave it out and the step goes
// out as soon as the wait before it has elapsed.
await editNode(teaser.id, {sendTimeMinutes: 540})

// A wait gives the DURATION between two sends.
await addNode({type: 'wait', waitHours: 72})

const launch = await addNode({type: 'send_email'})
await editNode(launch.id, {subject: 'It is here', content: html2})
await editNode(launch.id, {sendTimeMinutes: 540})

// 4. Publish. This is where a series without a start date is refused, by name.
await fetch(`${api}/sequences/${series.id}/versions/${draft.id}/publish`, {
  method: 'POST',
  headers,
}).then((r) => r.json())

Notes

A new series is paused. Activating it is a PATCH with {"status": "active"}, and it is refused until a version is published.