Publish a version

POST /api/v1/sequences/{sequenceId}/versions/{versionId}/publish Freezes the draft, dates it, and switches the sequence's pointer to it. This is the only moment the graph is checked, and the only moment it has consequences.

What a refused publication means

Every defect answers 400, names itself, and names the nodes involved — all of them at once, so you fix in one pass instead of publishing again to discover the next one.
DefectWhat it meansHow to fix it
emptyThe version has no node at allAdd at least one node
no entryEvery node has something pointing at it — there is no way inRemove the wire that closes the loop at the top
multiple entriesTwo nodes have nothing pointing at themWire one behind the other; a journey has one door
cycleThe wires come back on themselves — a contact would loop foreverRemove the wire that goes backwards
orphanA node is unreachable from the entryWire it in, or delete it
dangling branchA branch leads nowhereWire its end, or drop the branch
ambiguous branchA node has two outgoing wires with no way to chooseKeep one wire
edge out of graphA wire points at a node of another versionDelete that wire and rewire inside this version
empty sendA send_email node has no subject or no contentPATCH the node with a subject and HTML
incomplete actionAn add_tag / remove_tag node has no tagPATCH the node with a tagId
end with successorAn end_sequence node has a node after itDelete what follows: nothing runs after the end
invalid webhookA webhook node has no URL, or one the policy refusesPATCH the node with an https:// public URL
A version that is not a draft answers 400 too: a published version is a snapshot, republishing it in place would rewrite what contacts are walking. Use rollback instead.

Authorization

Role in the organizationThis endpoint
OWNERAllowed
ADMINAllowed
MEMBERRefused — 403
A key carries the role its creator holds in this organization — never a role on AgentsMail itself. See API keys.

Example

bash
curl -X POST -H "x-api-key: $AGENTMAIL_API_KEY" \
  "https://www.agentsmail.io/api/v1/sequences/$SEQUENCE_ID/versions/$VERSION_ID/publish"

Response

  • 200 OK — the call succeeded.
  • 401 Unauthorized — missing or invalid key, or its owner left the organization.
  • 403 Forbidden — the key's owner is a member of the organization, not an admin.
  • 404 Not Found — no such resource, or it belongs to another organization. The API never confirms that an id exists to a caller who has no right to it.
  • 429 Too Many Requests — over 120 requests in a minute for this key.

Response fields

FieldTypeDescription
successbooleanIndicates if the operation was successful
data.idstringUnique identifier for the version
data.sequenceIdstringThe sequence it belongs to
data.versionnumberIts rank, starting at 1
data.statusstringdraft or published
data.publishedAtstringnull
data.triggerTypestringlist_joined or tag_added
data.triggerTargetIdstringThe list or tag that enrols contacts
data.createdAtstringISO creation date
data.updatedAtstringISO date of the last change

Example response

json
{
  "success": true,
  "data": {
    "id": "4d19c0a2",
    "sequenceId": "9b3f2d10",
    "version": 1,
    "status": "subscribed",
    "publishedAt": "2026-08-23T10:22:05.000Z",
    "triggerType": "list_joined",
    "triggerTargetId": "0b7c51e8",
    "createdAt": "2026-08-23T10:22:05.000Z",
    "updatedAt": "2026-08-23T10:22:05.000Z"
  }
}

Agent recipe

Build the graph freely, then read the refusal: validation happens at publish time, not on every edit. A 400 here is the graph being refused, not a bug.
js
const publish = await fetch(
  `${base}/sequences/${sequenceId}/versions/${versionId}/publish`,
  {method: 'POST', headers}
)

if (publish.status === 400) {
  const {error, details} = await publish.json()
  // `details` names the steps at fault — fix the draft and publish again
  return repairDraft(details ?? error)
}

// Nothing runs until the sequence itself is active
await fetch(`${base}/sequences/${sequenceId}`, {
  method: 'PATCH',
  headers: {...headers, 'content-type': 'application/json'},
  body: JSON.stringify({status: 'active'}),
})