All docs

Sequence Lifecycle

Create, preview, update, archive, and read analytics for sequences

The Sequences page covers basic operations (list, get, run, pause/resume, manage enrolled contacts). This page covers the lifecycle endpoints used to author and inspect a sequence end-to-end - typically what a CLI or build tool needs.

All endpoints share the /sequences prefix and require a Bearer API key.

EndpointMethodDescription
/sequencesPOSTCreate a sequence (with optional steps + channels)
/sequences/{id}/previewGETRead sequence + steps + channels (no enrollment)
/sequences/{id}PATCHUpdate sequence metadata
/sequences/{id}/steps/{step_id}PATCHUpdate a single step
/sequences/{id}/pausePOSTPause a live sequence
/sequences/{id}/resumePOSTResume a paused sequence
/sequences/{id}/analyticsGETComprehensive analytics report
/sequences/{id}DELETESoft-delete (archive) a sequence

A sequence in a transitional status (scheduling, pausing, resuming, terminating) cannot be PATCHed - you’ll get 409 Conflict. Wait for the operation to settle.

For machine-readable schemas see the interactive API docs.

Enum reference

  • step_type (case-insensitive): EMAIL | PHONE | SMS | WHATSAPP | HEYREACH | MANUAL_DIALER. The alias linkedin maps to HEYREACH. For Unipile-based LinkedIn pass UNIPILE explicitly.
  • input_type: ON_DEMAND | MANUAL_TEMPLATE | AI_GENERATED_TEMPLATE. Aliases: template / manualMANUAL_TEMPLATE; ai / ai_templateAI_GENERATED_TEMPLATE.
  • channel_type: SMTP | GMAIL | INBOXKIT | PHONE | LINKEDIN | SMS | WHATSAPP.

step_data by channel

step_data is a free-form object whose meaningful keys depend on step_type. Unknown keys are silently ignored — a key that belongs to another channel has no effect. Pick the keys for your step’s channel from the sections below.

EMAIL

{
  "subject": "...",
  "body": "...",
  "instructions": "...",
  "email_type": "plain"
}
KeyTypeDescription
subjectstringEmail subject line
bodystringEmail body — supports {{merge_tokens}}
instructionsstringUsed when input_type is AI_GENERATED_TEMPLATE
email_typestring"plain" (default) or "html"

HEYREACH / LinkedIn

step_type: "HEYREACH" (or the linkedin alias). linkedin_action_type is required — a LinkedIn step without it is accepted at create time but fails at send time.

linkedin_action_typeCopy / config keys
CONNECTION_REQUESTlinkedin_connection_message (string), linkedin_connection_fallback (optional string), linkedin_connection_withdraw_days (optional integer, default 25)
MESSAGElinkedin_message_text (string) — supports {{merge_tokens}}
INMAILlinkedin_inmail_subject (string), linkedin_inmail_body (string)
FOLLOW— (no copy)
VIEW_PROFILE— (no copy)
LIKE_POSTlinkedin_like_reaction_type (optional integer: 0=Like, 1=Celebrate, 2=Support, 3=Love, 4=Insightful, 5=Funny; default 1), linkedin_like_react_before_days (optional integer, default 3)
{
  "linkedin_action_type": "MESSAGE",
  "linkedin_message_text": "Hi {{first_name}}, ..."
}

For AI-generated LinkedIn copy: set input_type: "AI_GENERATED_TEMPLATE" on the step, plus step_data.linkedin_input_mode: "instructions" and step_data.linkedin_instructions: "<prompt>". Use linkedin_input_mode: "manual" with the explicit copy keys above for fixed text.

PHONE / MANUAL_DIALER

{
  "dial_dnc": false,
  "phone_options": ["mobile", "direct"]
}
KeyTypeDescription
dial_dncbooleanDial Do-Not-Call numbers (default false)
phone_optionsstring[]Phone types to dial

SMS / WHATSAPP

{
  "message_body": "...",
  "media_urls": ["https://..."],
  "whatsapp_template_sid": "...",
  "whatsapp_template_variables": {}
}
KeyTypeDescription
message_bodystringMessage text — supports {{merge_tokens}}
media_urlsstring[]WhatsApp media attachment URLs
whatsapp_template_sidstringWhatsApp Business template SID
whatsapp_template_variablesobjectValues for the template variables

Create Sequence

POST /sequences

Create a sequence in drafted status. You can optionally include steps[] and channels[] in the same call.

Request Body

FieldTypeRequiredDescription
namestringYesSequence name
user_emailstringYesOwner email address
descriptionstringNoFree-form description
finish_on_replybooleanNo (default true)Stop contacting on reply
send_in_same_threadbooleanNo (default false)Send follow-ups in the same email thread
wait_for_new_contactsbooleanNo (default false)Keep sequence waiting for new enrollments
associated_list_idintegerNoContact list to associate
campaign_idstringNoAI Studio campaign UUID to link
sequence_kindstringNo (default "cold_outbound")"cold_outbound" or "nurture". Immutable after creation.
pinned_mailbox_idintegerNoRequired when sequence_kind is "nurture"; ignored otherwise. Immutable after creation.
stepsobject[]NoStep configs — see step config table below
channelsobject[]NoChannel configs — see channel config table below

Step config

FieldTypeRequiredDescription
step_orderintegerYesOrder within the sequence (1-indexed)
step_typestringYesEMAIL, PHONE, SMS, WHATSAPP, HEYREACH, MANUAL_DIALER (or alias linkedin)
input_typestringNo (default ON_DEMAND)ON_DEMAND, MANUAL_TEMPLATE, or AI_GENERATED_TEMPLATE
time_intervalintegerNo (default 0)Delay in seconds after the previous step
step_dataobjectNoStep content — see step_data by channel above. LinkedIn steps use linkedin_* keys, not body.

Channel config

FieldTypeRequiredDescription
channel_idintegerYesMailbox or phone resource ID
channel_valuestringYesThe address or number (e.g. [email protected])
channel_typestringYesSMTP, GMAIL, INBOXKIT, PHONE, LINKEDIN, SMS, or WHATSAPP
channel_dataobjectNoOptional metadata

Example

cURL

curl -X POST "https://be.graph8.com/api/v1/sequences" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q2 Outbound",
    "user_email": "[email protected]",
    "associated_list_id": 5,
    "steps": [
      {
        "step_order": 1,
        "step_type": "EMAIL",
        "input_type": "MANUAL_TEMPLATE",
        "time_interval": 0,
        "step_data": {"subject": "Quick intro", "body": "Hi {{first_name}}..."}
      }
    ],
    "channels": [
      {
        "channel_id": 12,
        "channel_value": "[email protected]",
        "channel_type": "SMTP"
      }
    ]
  }'

Response 201 Created

{
  "data": {
    "id": "seq_abc",
    "name": "Q2 Outbound",
    "status": "drafted",
    "created_at": "2026-04-15T10:00:00Z"
  }
}

Errors

StatusMeaning
400Invalid step or channel config
422Nurture sequence missing or mismatched pinned_mailbox_id

Preview Sequence

GET /sequences/{sequence_id}/preview

Read-only view of a sequence with all steps and channels. Does not enroll anyone.

Path Parameters

ParameterTypeDescription
sequence_idstringSequence UUID

Example

cURL

curl "https://be.graph8.com/api/v1/sequences/seq_abc/preview" \
  -H "Authorization: Bearer $API_KEY"

Response

{
  "data": {
    "id": "seq_abc",
    "name": "Q2 Outbound",
    "status": "drafted",
    "description": null,
    "steps": [
      {
        "id": "stp_1",
        "step_order": 1,
        "step_type": "EMAIL",
        "input_type": "MANUAL_TEMPLATE",
        "time_interval": 0,
        "step_data": {"subject": "Quick intro", "body": "..."}
      }
    ],
    "channels": [
      {"id": "ch_1", "channel_id": 12, "channel_value": "[email protected]", "channel_type": "SMTP"}
    ]
  }
}

Update Sequence

PATCH /sequences/{sequence_id}

Update sequence metadata. Send only the fields you want to change. Returns 409 if the sequence is in a transitional status (scheduling, pausing, resuming, terminating).

Path Parameters

ParameterTypeDescription
sequence_idstringSequence UUID

Request Body

All fields are optional.

FieldTypeDescription
namestringSequence name
descriptionstringDescription
is_sharedbooleanShare with the org
finish_on_replybooleanFinish on reply
send_in_same_threadbooleanSame-thread follow-ups
wait_for_new_contactsbooleanWait for new contacts
schedule_idstringUUID from GET /schedules
appointment_idintegerInteger from GET /event-types
textual_agent_namestringAgent name from GET /voice/dialer/agents (since 0.9.7)
voice_agent_namestringSame agent picker — pass the same value as textual_agent_name for the common “one agent, all channels” case

Example

cURL

curl -X PATCH "https://be.graph8.com/api/v1/sequences/seq_abc" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Q2 Outbound v2", "send_in_same_thread": true}'

Errors

StatusMeaning
404Sequence not found
409Sequence is in a transitional status (scheduling, pausing, resuming, terminating)

Update Step

PATCH /sequences/{sequence_id}/steps/{step_id}

Update a single step. The same 409 rule applies if the parent sequence is transitioning.

Path Parameters

ParameterTypeDescription
sequence_idstringSequence UUID
step_idstringStep UUID

Request Body

All fields are optional.

FieldTypeDescription
step_dataobjectStep content — see step_data by channel above. Replaced wholesale, not merged. Send the complete object every time; any key you omit is dropped.
time_intervalintegerDelay in seconds after the previous step
step_typestringEMAIL, PHONE, SMS, WHATSAPP, HEYREACH, MANUAL_DIALER
input_typestringON_DEMAND, MANUAL_TEMPLATE, or AI_GENERATED_TEMPLATE

Example

cURL

curl -X PATCH "https://be.graph8.com/api/v1/sequences/seq_abc/steps/stp_1" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"step_data": {"subject": "Quick intro v2", "body": "Updated..."}}'

Pause / Resume

POST /sequences/{sequence_id}/pause POST /sequences/{sequence_id}/resume

Pause a live sequence (or resume a paused one). Returns the number of contacts affected.

Response

{
  "data": {
    "sequence_id": "seq_abc",
    "status": "paused",
    "contacts_affected": 150
  }
}

Analytics

GET /sequences/{sequence_id}/analytics

Returns a comprehensive analytics report - overview KPIs, performance over time, engagement breakdown, contact distribution, per-step metrics, and sender distribution. The shape mirrors the internal SequenceReports model.

Example

cURL

curl "https://be.graph8.com/api/v1/sequences/seq_abc/analytics" \
  -H "Authorization: Bearer $API_KEY"

Response

{
  "data": {
    "overview": {"sent": 320, "opened": 180, "replied": 22},
    "performance": {"open_rate": 0.56, "reply_rate": 0.07},
    "engagement": {"by_day": [...]},
    "timeline": [...],
    "contact_distribution": {"active": 150, "completed": 100, "stopped": 70},
    "step_breakdown": [
      {"step_id": "stp_1", "sent": 320, "opened": 180}
    ],
    "step_creation_methods": [...],
    "sender_distribution": [...]
  }
}

The full schema lives at /api/v1/docs under SequenceAnalyticsResponse.


Delete (Archive)

DELETE /sequences/{sequence_id}

Soft-deletes a sequence by setting is_archived = true. Already-archived sequences return 400. Sequences in certain non-terminal states cannot be archived and return 409.

Example

cURL

curl -X DELETE "https://be.graph8.com/api/v1/sequences/seq_abc" \
  -H "Authorization: Bearer $API_KEY"

Errors

StatusMeaning
400Sequence is already archived
404Sequence not found
409Sequence cannot be archived in its current state
Build with graph8