API reference
Automations are the other half of AI Automations: a trigger (a contact is created, a deal changes stage, a date comes round, a webhook arrives, or a person enrolls contacts by hand) followed by steps that run for one contact at a time: send an email or a text, wait, branch, tag, update the contact, notify the team. With automations:read a key lists them, across every project, and reads each one's runs. With automations:enroll it enrolls Thrive contacts in a published automation, exactly as Enroll contacts does in the hub, and enrolling sends real messages: the steps run within about a minute and email the contact, and text them where texting is set up. The API never creates, edits, publishes or pauses an automation, and returns a summary of its steps, never their settings.
Nobody is messaged against their wishes. A contact who was erased from Thrive is not enrolled (erased). A contact who is on do-not-contact, has opted out of email or unsubscribed is enrolled, as in the hub, but every email step skips them and says why in the run; a contact on do-not-contact, one who replied STOP, or one who has not agreed to texts (when the project asks for consent, which it does unless its Settings say otherwise) gets no texts either. Other steps, such as adding a tag or notifying the team, still run. A run started through the API says so in Run history, with the key's name.
/v1/automationsautomations:readEvery automation in the account, across all its projects, most recently changed first: drafts, published and paused ones. Each carries its trigger, a summary of its steps (how many, which kinds, what it sends) and how many contacts it has run for. The steps' settings (addresses, message text, webhook URLs) are never included.
| Name | Type | Description |
|---|---|---|
limitquery | integer | How many to return. 1 to 100. Default 25. |
cursorquery | string | The next_cursor from the previous page. Leave it out for the first page. |
statusquery | string | Only automations with this status. One of draft, published, paused. |
project_idquery | string (uuid) | Only the automations in this project. |
| Field | Type | Description |
|---|---|---|
data | array of object | |
data[].id | string (uuid) | |
data[].project_id | string (uuid) | The project it belongs to. |
data[].name | string | |
data[].status | string | draft (never published), published (running) or paused. Only a published automation takes enrollments. |
data[].trigger | object | What starts it, as the hub's list shows it under Starts when. |
data[].trigger.type | string or null | The trigger, such as contact.created, deal.stage_changed, schedule or manual; null before one is chosen. |
data[].trigger.label | string | Its name in the hub, such as Contact created. |
data[].published_version | integer | The version that runs; 0 for a draft that was never published. |
data[].has_unpublished_changes | boolean | true when the draft in the builder differs from the published version. |
data[].step_count | integer | How many steps the version that runs has (the draft's, before it is published), branches included. |
data[].step_types | array of string | The kinds of step in it, each once, such as email.send, sms.send, wait or ai.write. More kinds may be added. |
data[].sends | array of string | What it sends: email and sms to the contact, team to people in the account. |
data[].enrollments | object | Its runs, test runs left out. |
data[].enrollments.total | integer | Every run, except a delivery its Run only if filter skipped. |
data[].enrollments.active | integer | Running or waiting now. |
data[].enrollments.completed | integer | |
data[].last_run_at | string or null (date-time) | When its latest run started; null if it has never run. |
data[].created_at | string (date-time) | |
data[].updated_at | string (date-time) | |
has_more | boolean | |
next_cursor | string or null |
The codes are listed under Errors.
curl "https://api.veragen.ai/v1/automations?limit=25" \
-H "Authorization: Bearer $VERAGEN_API_KEY"const res = await fetch("https://api.veragen.ai/v1/automations?limit=25", {
headers: { Authorization: `Bearer ${process.env.VERAGEN_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.veragen.ai/v1/automations",
params={"limit": 25},
headers={"Authorization": f"Bearer {os.environ['VERAGEN_API_KEY']}"},
)
data = res.json(){
"data": [
{
"id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"name": "Speed to lead",
"status": "published",
"trigger": {
"type": "contact.created",
"label": "Contact created"
},
"published_version": 3,
"has_unpublished_changes": false,
"step_count": 5,
"step_types": [
"email.send",
"notify.team",
"wait",
"task.create",
"end"
],
"sends": [
"email",
"team"
],
"enrollments": {
"total": 214,
"active": 9,
"completed": 198
},
"last_run_at": "2026-10-07T16:42:10.000Z",
"created_at": "2026-09-02T10:00:00.000Z",
"updated_at": "2026-10-01T08:15:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}/v1/automations/{id}automations:readOne automation: its status, trigger, step summary and run counts.
| Name | Type | Description |
|---|---|---|
idpath, required | string (uuid) | The automation's id. |
| Field | Type | Description |
|---|---|---|
id | string (uuid) | |
project_id | string (uuid) | The project it belongs to. |
name | string | |
status | string | draft (never published), published (running) or paused. Only a published automation takes enrollments. |
trigger | object | What starts it, as the hub's list shows it under Starts when. |
trigger.type | string or null | The trigger, such as contact.created, deal.stage_changed, schedule or manual; null before one is chosen. |
trigger.label | string | Its name in the hub, such as Contact created. |
published_version | integer | The version that runs; 0 for a draft that was never published. |
has_unpublished_changes | boolean | true when the draft in the builder differs from the published version. |
step_count | integer | How many steps the version that runs has (the draft's, before it is published), branches included. |
step_types | array of string | The kinds of step in it, each once, such as email.send, sms.send, wait or ai.write. More kinds may be added. |
sends | array of string | What it sends: email and sms to the contact, team to people in the account. |
enrollments | object | Its runs, test runs left out. |
enrollments.total | integer | Every run, except a delivery its Run only if filter skipped. |
enrollments.active | integer | Running or waiting now. |
enrollments.completed | integer | |
last_run_at | string or null (date-time) | When its latest run started; null if it has never run. |
created_at | string (date-time) | |
updated_at | string (date-time) |
The codes are listed under Errors.
curl "https://api.veragen.ai/v1/automations/{id}" \
-H "Authorization: Bearer $VERAGEN_API_KEY"const res = await fetch("https://api.veragen.ai/v1/automations/{id}", {
headers: { Authorization: `Bearer ${process.env.VERAGEN_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.veragen.ai/v1/automations/{id}",
headers={"Authorization": f"Bearer {os.environ['VERAGEN_API_KEY']}"},
)
data = res.json(){
"id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"name": "Speed to lead",
"status": "published",
"trigger": {
"type": "contact.created",
"label": "Contact created"
},
"published_version": 3,
"has_unpublished_changes": false,
"step_count": 5,
"step_types": [
"email.send",
"notify.team",
"wait",
"task.create",
"end"
],
"sends": [
"email",
"team"
],
"enrollments": {
"total": 214,
"active": 9,
"completed": 198
},
"last_run_at": "2026-10-07T16:42:10.000Z",
"created_at": "2026-09-02T10:00:00.000Z",
"updated_at": "2026-10-01T08:15:00.000Z"
}/v1/automations/{id}/enrollmentsautomations:enrollAdds Thrive contacts to a published automation, exactly as Enroll contacts does in the hub, and answers at once with who was enrolled and who was skipped, and why. Each enrolled contact gets a run; its steps begin within about a minute and send real messages: the emails the automation sends, and its texts where texting is set up. Follow them with List an automation's runs. Steps that use AI are billed to the account like any other AI use.
A contact is skipped, not refused, when it is not in this account or is archived (not_found), was erased from Thrive at their request (erased), is already in this automation (already_enrolled), has been through it before and the automation runs once per contact (enrolled_before), or is already in as many automations at once as the project allows (too_many_automations). Contacts on do-not-contact or opted out are enrolled, and the steps that would message them are skipped (see the Automations section).
The automation must be published: a draft or a paused one answers 409 automation_not_published and enrolls nobody. The trigger does not matter, and its Run only if filter is not applied: enrolling by hand is an instruction, as in the hub.
Idempotency-Key is required. Send a new unique value for each request and the same value again if you retry: a retry with the same key and the same body returns the first answer, with Idempotent-Replay: true, and enrolls nobody twice. If the first request did not finish, the retry answers 409 idempotency_outcome_unknown and nothing is done again; list the automation's runs to see whether it did. Keys are remembered for 24 hours. Run history shows each run as started through the API, with the key's name.
| Name | Type | Description |
|---|---|---|
idpath, required | string (uuid) | The automation's id. |
Idempotency-Keyheader, required | string | A unique value for this request, 8 to 64 printable characters (a UUID is ideal). Send the same value again to retry without enrolling anyone twice. |
| Field | Type | Description |
|---|---|---|
automation_id | string (uuid) | |
enrolled | integer | How many contacts were enrolled. |
runs | array of object | The run each enrolled contact got. |
runs[].id | string (uuid) | The run, for Get an automation run. |
runs[].contact_id | string (uuid) | |
skipped | array of object | The contacts not enrolled, each with why. |
skipped[].contact_id | string (uuid) | |
skipped[].reason | string |
The codes are listed under Errors.
curl -X POST "https://api.veragen.ai/v1/automations/{id}/enrollments" \
-H "Authorization: Bearer $VERAGEN_API_KEY" \
-H "Idempotency-Key: 6f1d2c3b-enroll-2026-10-07-1642" \
-H "Content-Type: application/json" \
-d '{"contact_ids": ["1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a"]}'const res = await fetch("https://api.veragen.ai/v1/automations/{id}/enrollments", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.VERAGEN_API_KEY}`, "Idempotency-Key": "6f1d2c3b-enroll-2026-10-07-1642", "Content-Type": "application/json" },
body: JSON.stringify({
"contact_ids": [
"1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
"2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a"
]
}),
});
const data = await res.json();import os, requests
res = requests.post(
"https://api.veragen.ai/v1/automations/{id}/enrollments",
headers={"Authorization": f"Bearer {os.environ['VERAGEN_API_KEY']}", "Idempotency-Key": "6f1d2c3b-enroll-2026-10-07-1642"},
json={
"contact_ids": [
"1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
"2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a"
]
},
)
data = res.json(){
"automation_id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"enrolled": 1,
"runs": [
{
"id": "7b2c3d4e-5f60-4b7c-9d8e-0f1a2b3c4d5e",
"contact_id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
}
],
"skipped": [
{
"contact_id": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a",
"reason": "already_enrolled"
}
]
}/v1/automations/{id}/runsautomations:readThe automation's runs, one per contact each time they went through it, newest first, however they started: by its trigger, enrolled in the hub, or through the API. These are the rows Run history shows. Test runs from the builder are included, with test: true.
| Name | Type | Description |
|---|---|---|
idpath, required | string (uuid) | The automation's id. |
limitquery | integer | How many to return. 1 to 100. Default 25. |
cursorquery | string | The next_cursor from the previous page. Leave it out for the first page. |
statusquery | string | Only runs with this status. One of running, waiting, succeeded, failed, stopped, skipped. |
| Field | Type | Description |
|---|---|---|
data | array of object | |
data[].id | string (uuid) | |
data[].automation_id | string (uuid) | |
data[].contact | object or null | Who it runs for; null for a scheduled run with no contact. |
data[].contact.id | string (uuid) | |
data[].contact.name | string | |
data[].status | string | As Run history shows it: running, waiting (at a wait, until next_at), succeeded, failed, stopped, or skipped (its Run only if filter did not match). |
data[].status_label | string | The same, in the words Run history uses. |
data[].started_by | string | What started it, as Run history's Started by column says: the trigger, the person who enrolled the contact, or the API key. |
data[].test | boolean | true for a test run from the builder, which sends only to the person testing. |
data[].version | integer | The published version it runs. |
data[].started_at | string (date-time) | |
data[].finished_at | string or null (date-time) | null until it ends. |
data[].next_at | string or null (date-time) | For a waiting run, when it carries on; null otherwise. |
data[].error | string or null | Why it failed or was stopped. |
has_more | boolean | |
next_cursor | string or null |
The codes are listed under Errors.
curl "https://api.veragen.ai/v1/automations/{id}/runs?limit=25" \
-H "Authorization: Bearer $VERAGEN_API_KEY"const res = await fetch("https://api.veragen.ai/v1/automations/{id}/runs?limit=25", {
headers: { Authorization: `Bearer ${process.env.VERAGEN_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.veragen.ai/v1/automations/{id}/runs",
params={"limit": 25},
headers={"Authorization": f"Bearer {os.environ['VERAGEN_API_KEY']}"},
)
data = res.json(){
"data": [
{
"id": "7b2c3d4e-5f60-4b7c-9d8e-0f1a2b3c4d5e",
"automation_id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"contact": {
"id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
"name": "Maya Lindqvist"
},
"status": "waiting",
"status_label": "Waiting until 2026-10-09 16:42 UTC",
"started_by": "Enrolled through the API by key “Website sign-ups” (vg_live_3fa9…)",
"test": false,
"version": 3,
"started_at": "2026-10-07T16:42:10.000Z",
"finished_at": null,
"next_at": "2026-10-09T16:42:10.000Z",
"error": null
}
],
"has_more": false,
"next_cursor": null
}/v1/automation-runs/{id}automations:readOne run of an automation, with each step it has run: its name, kind, status, how long it took and any error. A step that was skipped (an email to someone who unsubscribed, a text to someone who has not agreed to texts) is skipped. What a step sent (addresses, subject, text) is not included.
| Name | Type | Description |
|---|---|---|
idpath, required | string (uuid) | The run's id, from List an automation's runs or Enroll contacts. |
| Field | Type | Description |
|---|---|---|
id | string (uuid) | |
automation_id | string (uuid) | |
contact | object or null | Who it runs for; null for a scheduled run with no contact. |
contact.id | string (uuid) | |
contact.name | string | |
status | string | As Run history shows it: running, waiting (at a wait, until next_at), succeeded, failed, stopped, or skipped (its Run only if filter did not match). |
status_label | string | The same, in the words Run history uses. |
started_by | string | What started it, as Run history's Started by column says: the trigger, the person who enrolled the contact, or the API key. |
test | boolean | true for a test run from the builder, which sends only to the person testing. |
version | integer | The published version it runs. |
started_at | string (date-time) | |
finished_at | string or null (date-time) | null until it ends. |
next_at | string or null (date-time) | For a waiting run, when it carries on; null otherwise. |
error | string or null | Why it failed or was stopped. |
failed_step_id | string or null | For a failed run, the step it stopped at. |
steps | array of object | The steps it has run, in order. What a step sent (addresses, text) is not included. |
steps[].step_id | string | The step's id in the automation; t is the trigger. |
steps[].name | string | The step's name in the builder. |
steps[].type | string | What kind of step it is, such as email.send, sms.send, wait or trigger. |
steps[].status | string | |
steps[].started_at | string or null (date-time) | |
steps[].ms | integer | How long the step took. |
steps[].error | string or null | Why the step failed, or null. |
The codes are listed under Errors.
curl "https://api.veragen.ai/v1/automation-runs/{id}" \
-H "Authorization: Bearer $VERAGEN_API_KEY"const res = await fetch("https://api.veragen.ai/v1/automation-runs/{id}", {
headers: { Authorization: `Bearer ${process.env.VERAGEN_API_KEY}` },
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.veragen.ai/v1/automation-runs/{id}",
headers={"Authorization": f"Bearer {os.environ['VERAGEN_API_KEY']}"},
)
data = res.json(){
"id": "7b2c3d4e-5f60-4b7c-9d8e-0f1a2b3c4d5e",
"automation_id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"contact": {
"id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
"name": "Maya Lindqvist"
},
"status": "waiting",
"status_label": "Waiting until 2026-10-09 16:42 UTC",
"started_by": "Enrolled through the API by key “Website sign-ups” (vg_live_3fa9…)",
"test": false,
"version": 3,
"started_at": "2026-10-07T16:42:10.000Z",
"finished_at": null,
"next_at": "2026-10-09T16:42:10.000Z",
"error": null,
"failed_step_id": null,
"steps": [
{
"step_id": "t",
"name": "Contact created",
"type": "trigger",
"status": "ok",
"started_at": "2026-10-07T16:42:10.000Z",
"ms": 0,
"error": null
},
{
"step_id": "s1",
"name": "Email: thanks for getting in touch",
"type": "email.send",
"status": "ok",
"started_at": "2026-10-07T16:42:31.000Z",
"ms": 412,
"error": null
},
{
"step_id": "s2",
"name": "Tell the owner",
"type": "notify.team",
"status": "ok",
"started_at": "2026-10-07T16:42:32.000Z",
"ms": 268,
"error": null
},
{
"step_id": "s3",
"name": "Wait 2 days",
"type": "wait",
"status": "waiting",
"started_at": "2026-10-07T16:42:32.000Z",
"ms": 0,
"error": null
}
]
}