Sequences
Sequences are multi-channel outreach flows (Email, WhatsApp, LinkedIn). Each sequence is a step graph: people are enrolled as Sequence People, and each enrollment executes as a Sequence Run. CRUD goes through REST; step editing and lifecycle operations go through GraphQL.
/rest/sequences/rest/sequences/{id}/rest/sequences/rest/sequences/{id}/rest/sequences/{id}Managing Steps (GraphQL)
Steps are stored as JSON on the sequence and managed exclusively through GraphQL mutations at POST /graphql. Every step has explicit nextStepIds routing; unconnected steps are dead ends.
Create a step
mutation CreateSequenceStep($input: CreateSequenceStepInput!) {
createSequenceStep(input: $input) { stepsDiff }
}
# input
{
"sequenceId": "uuid",
"stepType": "SEND_EMAIL",
"parentStepId": "uuid-of-parent-or-null",
"position": { "x": 0, "y": 200 },
"isFirstStep": false
}Returns a stepsDiff array, not the step itself. Fetch the sequence at depth=1 afterwards to get the new step's UUID. LinkedIn message steps use SEND_LINKEDIN (not SEND_LINKEDIN_MESSAGE).
Update a step
mutation UpdateSequenceStep($input: UpdateSequenceStepInput!) {
updateSequenceStep(input: $input) {
id name type settings valid isFirstStep nextStepIds
}
}
# input (excerpt)
{
"sequenceId": "uuid",
"step": {
"id": "step-uuid",
"type": "SEND_EMAIL",
"nextStepIds": ["next-step-uuid"],
"settings": {
"input": {
"subject": "Hi {{person.name.firstName}}",
"body": "<p>Hello {{person.name.firstName}},</p>",
"days": [1, 2, 3, 4, 5],
"window": { "start": "09:00", "end": "17:00" }
}
}
}
}The full settings object is required. Sending only settings.input silently resets other fields (delay, conditions, output schema) to defaults. Fetch the current step first and merge your changes.
Delete or duplicate a step
mutation DeleteSequenceStep($input: DeleteSequenceStepInput!) {
deleteSequenceStep(input: $input) { stepsDiff }
}
mutation DuplicateSequenceStep($input: DuplicateSequenceStepInput!) {
duplicateSequenceStep(input: $input) { stepsDiff }
}
# input for both
{ "sequenceId": "uuid", "stepId": "step-uuid" }Duplicate a sequence or start from a template
mutation DuplicateSequence($input: DuplicateSequenceInput!) {
duplicateSequence(input: $input) {
success newSequenceId duplicatedSendersCount warnings
}
}
# duplicateType: SEQUENCE_ONLY | ALL_SEQUENCE | SEQUENCE_INCOMPLETE_RUNS
mutation CreateSequenceFromTemplate($input: CreateSequenceFromTemplateInput!) {
createSequenceFromTemplate(input: $input) {
success sequenceId stepsCreated
}
}
# input: { "name": "My Sequence", "templateType": "LINKEDIN_OUTREACH" }Senders
A SequenceSender is a platform identity (email address, WhatsApp number, or LinkedIn account) attached to a workspace member. Senders must exist before a sending step can run.
# List senders, then filter by platformType and workspaceMemberId
curl -G "https://app.usedalil.ai/rest/sequenceSenders" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "filter=platformType[eq]:LINKEDIN"mutation ActivateSequenceSenders($input: ActivateSequenceSendersInput!) {
activateSequenceSenders(input: $input)
}
# input: { "senderActions": [{ "sequenceSenderId": "uuid", "actionType": "message" }] }
mutation RemoveSender($input: RemoveSenderInput!) {
removeSender(input: $input) {
success totalAffectedPeople reassignedCount stoppedCount warnings
}
}LinkedIn steps need a senderId that is a SequenceSender ID, not a workspace member ID.
Enrollment and Runs
Each enrolled contact is a sequencePerson (status: ACTIVE or PAUSED, plus flags like hasReplied, hasClickedLink, hasUnsubscribed). Each enrollment executes as a sequenceRun.
# Enrolled people for a sequence
curl -G "https://app.usedalil.ai/rest/sequencePeople" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "filter=sequenceId[eq]:sequence-uuid" \
--data-urlencode "depth=1"
# Runs with execution state
curl -G "https://app.usedalil.ai/rest/sequenceRuns" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "filter=status[eq]:FAILED"Debugging and recovery
# Enrolled people with per-step resolved content and invalid variables
query ResolvedSequencePeople($filter: SequencePeopleFilterInput!) {
resolvedSequencePeople(filter: $filter) {
edges { node {
personId status
steps { id type resolvedInput invalidVariables }
} }
totalCount
}
}
# All error groups for a sequence
query SequenceErrors($filter: SequenceErrorsFilterInput!) {
sequenceErrors(filter: $filter) {
peopleErrors { sequencePersonId failedStepId failedStepError }
senderErrors { senderId platformType status reason }
completedNoMessage { sequencePersonId reason }
}
}
# Retry failed steps / reset a person's run
mutation RetrySequenceSteps($input: [RetrySequenceStepInput!]!) {
retrySequenceSteps(input: $input)
}
mutation ResetSequencePersonRun($input: ResetSequencePersonRunInput!) {
resetSequencePersonRun(input: $input)
}Status Reference
Common Gotchas
- Steps live in RAW_JSON on the sequence; manage them only through the GraphQL step mutations, never by PATCHing
steps. updateSequenceStepneeds the full merged settings object; partial settings silently reset the rest.- Message bodies cannot be empty strings: an empty
body(email) ormessage(LinkedIn) passes the API but blocks activation in the UI. - Any sending step with
valid: falseblocks activation (exception: CREATE_TASK reports valid: false even when configured; this is a known quirk). - Response wrappers are
.data.sequence/.data.sequences, following the resource path segment.
Was this page helpful?
Your feedback helps us improve our documentation.