SonarSend API Endpoints Sequences

Sequences

Automated flows, and enrolling contacts into them.

4 endpoints Base URL · https://api.sonarsend.com Auth · X-API-Key
The Sequence object 12 fields
allow_reentryboolean

false (default) = a contact enrolls at most once ever; true = re-entry allowed once no longer actively enrolled.

created_atdate-time
edgesobject[]

Single-sequence GET only. Graph edges (id, sequence_id, from_node, to_node, branch_label, created_at); branch_label is null for an unlabelled edge, true/false on a condition node.

entry_node_idstring · nullable

The graph node traversal starts at; null on an unconfigured draft.

entry_triggerobject · nullable

What auto-enrolls contacts ({ trigger_type, trigger_config }), or null. A DSL object — shape varies by trigger type.

exit_conditionsobject · nullable

Global early-exit rules, or null. A DSL object.

iduuid

Canonical sequence ID. Pass it to POST /{id}/enroll.

namestring
nodesobject[]

Single-sequence GET only. Typed graph nodes (id, sequence_id, type, config, created_at); config shape varies by node type.

statusenum

Only an active sequence is enrollable — a draft or paused one refuses enrolment with SEQUENCE_NOT_ENROLLABLE.

One of: draft, active, archived, paused
tenant_iduuid
updated_atdate-time

List sequences

GET /t/{slug}/api/sequences/

The workspace's sequences with their status. Filter by status and substring-match the name with q. Use it to resolve a sequence id at startup instead of hard-coding one — a rebuilt flow gets a new id, and a hard-coded one would then fail exactly when you'd want it to. Rows are the bare sequence (no graph). Not paginated.

status string
q string

A page of Sequence objects, under data.

200

The workspace's sequences.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/sequences" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "data": [
    {
      "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "name": "Acme onboarding",
      "status": "draft",
      "entry_node_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "entry_trigger": {}
    }
  ],
  "next_cursor": "eyJpZCI6IjljMjE4OTVkIn0",
  "has_more": true
}

Get a sequence

GET /t/{slug}/api/sequences/{id}

One sequence with its full node and edge graph. Read-only, and useful mostly for confirming you are pointed at the flow you think you are — the graph is authored in the app, not over the API.

id string Required

The Sequence object.

200

The sequence with its graph.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/sequences/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "name": "Acme onboarding",
  "status": "draft",
  "entry_node_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "entry_trigger": {}
}

Enroll a contact

POST /t/{slug}/api/sequences/{id}/enroll

Enrolls a contact into an active sequence, resolving-or-creating the contact from the identifier(s) you send. At least one of contact_id, email, external_id is required (400 MISSING_IDENTIFIER otherwise); they are tried in that order of precedence.

  • contact_id — an exact lookup. No match is 404 CONTACT_NOT_FOUND; it never creates.
  • email — resolve-or-create. A new contact is created if none matches, carrying any profile fields you send (first_name, last_name, company, custom_fields, tags, stage_id) and, if you also sent external_id, that value stamped on it — so a signup webhook can enrol someone who does not exist yet in one call.
  • external_id alone — a lookup only. It cannot create a contact, because a contact needs an email; no match is 404 CONTACT_NOT_FOUND. Send it together with email when you want create-if-missing.

Because of the precedence, sending contact_id (or email) together with external_id is fine — the higher one resolves the contact, the external_id is persisted. Unknown fields are rejected (400 INVALID_INPUT).

Idempotent: a contact who is already actively enrolled returns status: "duplicate" with 200 — a clean no-op. Pass an idempotency_key for at-least-once delivery; a retry with the same key is a no-op even if the first response never reached you.

contact_created on the response tells you whether this call created the contact or matched one. A draft or paused sequence refuses with 422 SEQUENCE_NOT_ENROLLABLE; the per-contact enrollment rate limit is 429 ENROLL_LIMIT (distinct from the per-key request limit).

id string Required
companystring
contact_iduuid
custom_fieldsobject
emailemail
external_idstring
first_namestring
idempotency_keystring
last_namestring
stage_iduuid
tagsstring[]
contact_createdboolean
contact_idstring
enrollment_idstring
statusenum
One of: enrolled, duplicate, sequence_not_active, enroll_limit
200

Enrolled (enrolled) or an idempotent no-op (duplicate). contact_id and contact_created are present on the API-key resolve-or-create path.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/sequences/9c21895d-57f0-4a15-a1df-4dc6835d4f80/enroll" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
    "email": "jamie@example.com",
    "external_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
    "idempotency_key": "welcome-series",
    "first_name": "Jamie",
    "last_name": "Reed"
  }'
Response 200
{
  "status": "enrolled",
  "enrollment_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "contact_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "contact_created": true
}

List a contact's enrollments

GET /t/{slug}/api/sequences/contact/{contactId}/enrollments

Every enrollment for one contact across ALL the workspace's sequences — the "did my enroll land, and where is it now" endpoint. A contact with no enrollments returns an empty data array, not a 404. Not paginated.

contactId string Required
dataobject[]
current_node_idstring · nullable

The graph node the contact is currently at; null once exited.

enrollment_iduuid
entered_atdate-time
exit_reasonstring · nullable

Why it ended; null while active.

exited_atdate-time · nullable

When the enrollment ended; null while active.

outcomestring

Coarse classification derived from status + exit reason.

sequence_iduuid
sequence_namestring
statusstring

Enrollment status, e.g. active, completed, exited, paused.

has_moreboolean
next_cursorstring · nullable

Opaque. Pass back as cursor for the next page. Null when there is no continuation — which for most endpoints means the last page, and always means a collection that is not paginated. The account-wide event feed is the exception: it returns a resumable cursor even on its final page (see that endpoint).

200

The contact's enrollments across all sequences.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/sequences/contact/{contactId}/enrollments" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "data": [
    {
      "enrollment_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "sequence_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "sequence_name": "Acme onboarding",
      "status": "active",
      "outcome": "string",
      "current_node_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80"
    }
  ],
  "next_cursor": "eyJpZCI6IjljMjE4OTVkIn0",
  "has_more": true
}