Create an automation
Creates a trigger-driven automation, optionally with its steps. Always created paused — enabling is a separate, deliberate call (POST /api/v1/automations/{id}/status) so a programmatic mistake can never start sending. Step objects are camelCase (they reuse the internal step schema); the surrounding body is snake_case like the rest of the API.
bearerAuth writeAuthorizationBearer <token>An API key from the dashboard under Settings → API keys, sent as Authorization: Bearer aem_….
Authorization has two independent axes.
The scope is ranked — a key satisfies any requirement at or below its own tier:
read— see messages, contacts and metrics. Changes nothing, and cannot send.write— everythingreaddoes, plus managing templates, contacts, automations, segments and suppressions. This is what editing a template needs.admin— everythingwritedoes, plus sending configuration: domains, senders, webhook registration, kill switch, daily cap, brand.
There is no approve scope. It was a rung once; it is not one now, and a key requested with it is rejected.
The approval grant (can_approve) is a separate boolean, not a rung. Delivering mail to a real inbox needs write and the grant. Keeping them on separate axes is what makes the review gate a control rather than a convention: a key that may propose is not automatically a key that may approve its own proposal.
Give your application the lowest tier that works. Most need write and the grant — the dashboard mints that combination as Send + manage; Manage only is the same rung with the grant withheld.
Scope: write
Idempotency-Key?stringMakes a retry safe (1–256 chars, kept 24 hours). The same key with the same body replays the first success with Idempotency-Replay: true; the same key with a different body, or while the first request is still running, is 409 conflict. A failed request releases its key. Use a value tied to the thing you are creating, e.g. onboarding/user_42.
1 <= length <= 256application/json- body
name*stringDisplay name, unique within the project.
2 <= length <= 120trigger_event*stringContact event that enrolls a contact.
1 <= length <= 120trigger_filter?array<>|Conditions over contact attributes ⊕ event properties. All must match to enroll.
priority?integerLower runs first when several automations match. Default 100.
0 <= value <= 1000exclusive?booleanWhen true (default), a contact already in this automation is not re-enrolled.
exit_events?array<>Events that end an in-flight run early, ORed.
items <= 20exit_event?stringDeprecated single-event form, still accepted. Prefer exit_events.
length <= 120allow_reenrollment?booleanAllow a contact to enroll again after finishing.
reenrollment_cooldown_seconds?integer0 <= value <= 9007199254740991steps?array<|||||||>Steps in order. Omit to create an empty shell and append later.
items <= 200Automation created, paused.
application/json- response
automation_id?stringstep_count?integerstatus?"paused""paused"curl -X POST 'https://sendandretain.com/api/v1/automations' \ -H 'Content-Type: application/json' \ -d '{ "name": "Win back", "trigger_event": "subscription.canceled", "steps": [ { "type": "send", "templateSlug": "win-back-1", "delaySeconds": 0 }, { "type": "delay", "delaySeconds": 259200 }, { "type": "send", "templateSlug": "win-back-2", "delaySeconds": 0, "subject": "One last thing" } ]}'{ "automation_id": "auto_win_back", "step_count": 3, "status": "paused"}