Skip to main content
POST
/
api
/
campaigns
/
{id}
/
sequence
/
nodes
Add a sequence node
curl --request POST \
  --url https://app.puffle.ai/api/campaigns/{id}/sequence/nodes \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "type": "connection_request",
  "position": 4503599627370495,
  "subject": "<string>",
  "body": "<string>",
  "include_message": true,
  "is_reply": true,
  "delay_days": 4503599627370495,
  "delay_hours": 4503599627370495,
  "delay_minutes": 4503599627370495
}
'
{ "sequence_nodes": [ { "id": "bbbb1111-1111-1111-1111-111111111111", "campaign_id": "8c2b2d4e-e29b-41d4-a716-446655440000", "position": 0, "type": "email", "subject": "Quick question about {{company}}", "body": "Hi {{first_name}}, ..." }, { "id": "bbbb2222-1111-1111-1111-111111111111", "campaign_id": "8c2b2d4e-e29b-41d4-a716-446655440000", "position": 1, "type": "delay", "delay_days": 3 }, { "id": "bbbb3333-1111-1111-1111-111111111111", "campaign_id": "8c2b2d4e-e29b-41d4-a716-446655440000", "position": 2, "type": "end" } ] }

Overview

Inserts a new node into the campaign sequence. Draft campaigns only — every mutation on this collection is gated on status === "draft". This collection exposes three verbs:
VerbOperationPurpose
POSTaddSequenceNodeInsert a new node (this page). Omit position to drop it right before the terminal end node.
PATCHmoveSequenceNodeReorder an existing node — see Move a sequence node.
DELETEdeleteSequenceNodeRemove a node — see Delete a sequence node.
To edit a node’s content (subject, body, delay values, AI purpose), use updateSequenceNode against the singular /sequence/nodes/{nodeId} route. Type is immutable after creation — to change it, delete and re-add. Every mutation on this collection is backed by an atomic Postgres RPC with row-level locking, so concurrent adds, moves, and deletes can’t corrupt the position ordering.

AI agent notes

Typical build order.
  1. POST /api/campaigns to create a draft.
  2. Loop over your desired nodes and POST /api/campaigns/{id}/sequence/nodes for each — omit position and every node lands just before the auto-inserted terminal end.
  3. When done, POST /api/campaigns/{id}/launch.
Channel validation. The parent campaign’s type determines which node kinds are legal:
  • linkedin campaigns accept connection_request, message, delay, ai, end.
  • email campaigns accept email, delay, ai, end.
Read-back fallback. The response is normally { sequence_nodes: [...] } — the full list, already ordered. On the rare occasion the post-mutation read-back fails, the server returns { mutated: true, node_id }. In that case refetch via GET /api/campaigns/{id}.Position omitted = before the end. Don’t set position unless you specifically need to insert somewhere other than the natural end-of-sequence slot. The server handles ordering.AI nodes. Adding { type: "ai" } creates the base row; populate purpose and output_type with a follow-up PUT /api/campaigns/{id}/sequence/nodes/{nodeId} call.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Path Parameters

id
string<uuid>
required

Campaign UUID

Pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

Body

application/json

Draft-only. Any caller-supplied id is stripped — the server generates the node UUID. Extra fields are tolerated and forwarded to the RPC.

type
enum<string>
required

Kind of node to create. Required. Channel must match the parent campaign.

Available options:
connection_request,
message,
email,
delay,
ai,
end
position
integer | null

0-indexed insert position. If omitted or null, the node is inserted immediately before the terminal end node.

Required range: 0 <= x <= 9007199254740991
subject
string

Email subject template. email nodes only.

body
string

Message body template — supports %first_name%, %company%, etc. Used by connection_request, message, and email nodes.

include_message
boolean

connection_request only. If true, the invite includes a message body (<=300 chars).

is_reply
boolean

email only. If true, the email is sent as a reply on the previous email's thread.

delay_days
integer

delay only. Days to wait before advancing.

Required range: 0 <= x <= 9007199254740991
delay_hours
integer

delay only. Hours to wait before advancing.

Required range: 0 <= x <= 9007199254740991
delay_minutes
integer

delay only. Minutes to wait before advancing.

Required range: 0 <= x <= 9007199254740991
{key}
any

Response

Node inserted. Returns the fresh ordered list of sequence nodes. If the post-mutation read-back fails, falls back to { mutated: true, node_id }.

Successful mutation response. The server re-fetches the full sequence and returns it so clients don't have to round-trip a GET.

sequence_nodes
object[]
required

Fresh ordered list of nodes after the mutation. Absent (replaced by mutated: true + optional node_id) only if the server's best-effort read-back after the mutation failed.