Automations
An automation is a published flow a contact runs through: a welcome series, an onboarding sequence, a win-back. You build the steps, waits and emails on the canvas in the app. The API does the one part only your backend knows: who enters, and when.
trial started -> POST /automations/{id}/enroll -> day3 sends the whole seriesThe Automation object
{
"id": "aut_9x4v0dz1k2m7",
"object": "automation",
"name": "Trial onboarding",
"status": "active",
"trigger": "api",
"audience_id": "aud_7k2m9x4v0dz1",
"live_version": 3,
"sandbox": false,
"created_at": "2026-09-01T09:00:00.000Z",
"updated_at": "2026-09-08T11:20:00.000Z"
}| Field | Notes |
|---|---|
status | draft, active, paused or archived. |
trigger | audience_join: the flow also fires on its own as people join the audience. api: only enrolling starts it. |
audience_id | The audience the flow runs over. The contact must already be in it, or be created by the same call. |
live_version | The published version number. null if never published, and a draft cannot be enrolled into. |
sandbox | true when the flow only mails members of your own organization. Re-stamped on every publish, so it follows your plan. |
List automations
/v1/automationsNewest first, cursor-paginated. No scope needed: it names flows, not people.
| Param | Notes |
|---|---|
status | draft, active, paused or archived. Archived flows are hidden unless you ask for them. |
limit, after | Standard pagination. |
An unknown status is 400 invalid_request, not an empty list.
curl "https://go.day3.app/api/v1/automations?status=active" \
-H "Authorization: Bearer $DAY3_API_KEY"Enroll a contact
/v1/automations/{automation_id}/enrollneeds automations:enrollPuts one person into the flow. day3 then sends them every email in it, on the flow’s schedule, with no further call from you.
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | Yes | Trimmed and lowercased first. |
attributes | object | No | Flat string to string. Creates the contact if it is new to the audience, merges into it if not. |
curl -X POST https://go.day3.app/api/v1/automations/aut_9x4v0dz1k2m7/enroll \
-H "Authorization: Bearer $DAY3_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: trial-start-user-8412" \
-d '{
"email": "jane@acme.com",
"attributes": { "plan": "trial", "company": "Acme" }
}'{
"object": "enrollment_result",
"outcome": "enrolled",
"enrollment_id": "aen_4v0dz1k9x2m7"
}attributes decides whether a stranger is created
- With
attributes, an unknown address is created in the flow’s audience assubscribed, then enrolled. Contacts rules apply: values must be strings, unknown keys register as fields,nulldeletes a key, and the merge is shallow. - Without
attributes, an unknown address returnsnot_subscribed. Nothing is created, nothing is sent.
Enrolling never grows a list by accident. Send attributes, even {}, when you
mean “create this person if they are new”.
Outcomes
Any understood call returns 200. Only enrolled started a run. Branch on
outcome, not on the status code.
outcome | What happened |
|---|---|
enrolled | A run started. enrollment_id is set. |
already_enrolled | The flow’s re-entry rule says this person is already in it. |
not_subscribed | Not in the audience, or not subscribed. Unsubscribed, bounced and pending contacts land here. |
suppressed | The address is on the account’s suppression list. |
entry_filter_no_match | The flow’s entry filter does not match this contact. |
sandbox_not_member | The flow is in sandbox mode and the address is not a member of your organization. |
wrong_audience | The contact exists, but in a different audience than the flow. |
automation_not_active | The published version is unusable. Rare, and worth reporting. |
Always send Idempotency-Key. Lifecycle events fire twice all the time, and a
duplicate enrollment sends the whole series twice. See
Idempotency.
A paused flow still accepts enrollments
Enrolling into a paused automation succeeds and the person waits at the
trigger until you resume. Pausing to fix a typo must not drop every signup in
the meantime.
A draft or archived automation is 409 invalid_request: nothing published to
run.
Why this needs a scope
Enroll requires the automations:enroll scope. Without
it: 403 insufficient_scope. Mint a new key; scopes cannot be added to an
existing one.
An enrollment promises a contact every email in the flow, with no further call from the key holder. That is a bigger commitment than one campaign send, not a smaller one. Listing is unscoped because flow names reach nobody.
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | invalid_email | The address is not valid. |
| 400 | invalid_request | An unknown status filter, or an attributes value that is not a string. |
| 403 | insufficient_scope | The key lacks automations:enroll. |
| 404 | not_found | No such automation on this account. |
| 409 | invalid_request | The automation is a draft or archived. Publish it first. |
| 409 | email_suppressed | The address is suppressed, so creating the contact was refused. |
409 email_suppressed only happens when you sent attributes: the contact write
was refused. The suppressed outcome is a 200: the contact exists and was not
enrolled. Both mean the address is unmailable.
From an AI editor
MCP exposes the same two operations as day3_list_automations and
day3_enroll_in_automation, with the same scope on enroll.
Not in the API
The canvas. Creating flows, editing nodes, wait steps, entry filters, re-entry rules, the enrollment browser and automation statistics are app-only. If you need one from code, it does not exist yet.