Automations
Create and run automations: triggers, steps, filters, the AI step, deliveries, webhooks listed as automations, run history, and AI step costs.
An automation is one trigger, 1 to 10 run steps, optional filters, an optional AI step, and up to 10 deliveries. See Automations for what each part does in the dashboard. All endpoints are prefixed with https://api.scrape.ly/v2. Authenticate with Authorization: Bearer $SCRAPELY_TOKEN.
Automations and schedules are the same objects. Every schedule is an automation with a schedule trigger and nothing else, so it is listed here too, and an automation with a schedule trigger is also listed under /v2/schedules.
Webhooks are automations too: a run event and one action (an HTTP request, a Slack, Discord, or email message, a Google Sheets export, or a run start). They stay in /v2/webhooks, which works exactly as before, and GET /v2/automations?include=webhooks lists them next to the others. See Webhooks as automations.
Any API key with the right scopes can manage automations, including the AI step: automations:read to read, and automations:write with actors:run and the run permission actor-default to change or run them. Keys limited to specific Actors can't create or change automations. See API keys & permissions. Actor run tokens get 403 insufficient-permissions on every write.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v2/automations | List your automations |
POST | /v2/automations | Create an automation |
GET | /v2/automations/{automationId} | Get an automation |
PUT | /v2/automations/{automationId} | Update an automation (only the parts you send) |
DELETE | /v2/automations/{automationId} | Delete an automation and its run history |
POST | /v2/automations/{automationId}/duplicate | Copy an automation |
POST | /v2/automations/{automationId}/runs | Run it now |
GET | /v2/automations/{automationId}/runs | Run history, newest first |
GET | /v2/automations/{automationId}/runs/{runId} | One run |
GET | /v2/users/me/usage/automations | AI step costs per automation run |
POST | /v2/webhooks/{webhookId}/convert-to-automation | Turn a Trigger Actor or Trigger task webhook into an automation. See Convert a webhook. |
Errors and limits
| Status | error.type | When |
|---|---|---|
400 | invalid-input | A part of the body is not valid. The message names the field, for example steps[0].actorId is required for an actor step or deliveries[1].content "ai" needs an AI step; use "digest" or add ai. |
400 | not-connected | A slack delivery while no Slack workspace is connected. |
400 | actor-not-built | A step runs one of your Actors that has no successful build yet. |
400 | actor-not-available | A step runs someone else's Actor that can't be run right now. |
403 | feature-not-enabled | Automations are not enabled for your account yet. |
403 | insufficient-permissions | The key's scopes don't cover the request, or a run token tried to write. |
403 | limit-reached | Creating an automation when you already have your plan's maximum. Automations and schedules share one limit. |
404 | automation-not-found | The automation does not exist or is not yours. Also for a runAutomation delivery that names someone else's automation. |
404 | automation-run-not-found | The run does not exist or belongs to another automation. |
404 | record-or-token-not-found | A step or trigger names an Actor that doesn't exist. |
404 | saved-input-not-found | A run-finished trigger names a saved input you don't own. |
409 | automation-busy | Run now while the automation is starting a run, or while its previous run is still going and Skip if the previous run is still going is on. |
| Limit | Value |
|---|---|
| Steps | 1 to 10 (at most 10 Actor steps and 10 saved input steps, as for schedules) |
| Filter conditions | 10 |
| Key fields for "only new items" | 1 to 5 |
| AI prompt | 2,000 characters |
| Deliveries | 10 |
| Step input | 500 KB of JSON |
| Run history | The last 30 runs per automation |
| Chains | An automation started by another automation (a run-finished trigger or a runAutomation delivery) can start another one, at most 3 automations deep |
The automation object
{
"id": "593d12cc-c97c-4ef0-bd5d-a49512326805",
"name": "daily-digest",
"title": "Daily digest",
"description": null,
"trigger": { "kind": "schedule", "cron": "0 7 * * *", "timezone": "Europe/Zurich" },
"steps": [
{
"id": "b62821cb7",
"kind": "savedInput",
"savedInputId": "9f2f54be-7f12-4efb-92a5-9310baa3a527",
"savedInputName": "link-preview-tech-sites",
"savedInputTitle": "Tech sites preview",
"actorId": "bf064b360dd844999",
"actorName": "link-preview-scraper",
"inputOverride": { "maxItems": 2 },
"runOptions": { "memoryMbytes": 512, "maxTotalChargeUsd": 0.05 }
},
{
"id": "26658f76a",
"kind": "actor",
"actorId": "8c2684237c614269b",
"actorName": "remoteok-scraper",
"input": { "searchQueries": ["engineer"], "maxResults": 3 },
"runOptions": { "maxItems": 3 }
}
],
"filters": null,
"ai": { "mode": "summarize", "prompt": "Two short bullet points: what the pages are and which remote jobs were found." },
"deliveries": [
{ "id": "cc8b00d2b34a", "channel": "dashboard", "content": "both", "target": null }
],
"options": {
"isEnabled": true,
"isExclusive": true,
"notifyOnFailure": true,
"maxWaitMinutes": 20,
"maxItemsPerStep": null
},
"nextRunAt": "2026-10-21T05:00:00.000Z",
"lastRunAt": null,
"createdAt": "2026-10-20T01:27:10.000Z",
"modifiedAt": "2026-10-20T04:44:07.000Z",
"lastRun": {
"id": "3c246aa2b45548c8b1be",
"status": "succeeded",
"triggerKind": "manual",
"startedAt": "2026-10-20T01:27:10.733Z",
"finishedAt": "2026-10-20T01:28:02.853Z",
"aiCostUsd": 0.010071
}
}| Field | Description |
|---|---|
source | automation, or webhook for a webhook listed as an automation. |
name | API name: letters, digits, and dashes, up to 63 characters, unique among your automations and schedules. Generated from title when you leave it out. |
title, description | Display title (up to 120 characters) and description (up to 2,000). |
trigger | What starts it. See Triggers. |
steps | The runs it starts, all at the same time. See Steps. |
filters, ai, deliveries, options | See Filters, AI step, Deliveries, and Options. |
nextRunAt | The next firing of a schedule trigger, or null (paused, manual, or run-finished). |
lastRunAt | The last firing of the schedule trigger. Runs started with Run now or by another trigger don't change it; use lastRun. |
lastRun | The newest run, with its status and AI cost, or null. |
Delivery secrets (webhook and Discord URLs) are encrypted when saved and never returned: responses show a masked target such as https://webhook.site/544f…4c2f.
Triggers
trigger | Starts the automation |
|---|---|
{ "kind": "manual" } | Only when you run it: Run now, POST …/runs, a runAutomation delivery, or the AI chat. |
{ "kind": "schedule", "cron": "0 9 * * 1", "timezone": "Europe/Berlin" } | On a cron expression, evaluated in timezone (an IANA name, default UTC). The rules are those of schedules. cronExpression is accepted for cron. |
{ "kind": "runFinished", "actorId": "…", "statuses": ["SUCCEEDED"] } | When one of your runs of that Actor finishes with one of the statuses. |
{ "kind": "runFinished", "savedInputId": "…", "statuses": ["SUCCEEDED", "FAILED"] } | When a run of that saved input finishes. |
Webhooks listed as automations have a fourth, read-only kind, runEvent; see Webhooks as automations.
A run-finished trigger needs exactly one of actorId (your own Actor, or a public one you run) or savedInputId (one of your saved inputs). statuses are SUCCEEDED, FAILED, ABORTED, and TIMED-OUT (TIMED_OUT is accepted); the default is ["SUCCEEDED"]. Each finished run starts the automation at most once, and an automation is never started by a run it started itself.
Steps
| Step | Fields |
|---|---|
| Saved input | kind: "savedInput", savedInputId, optional inputOverride and runOptions. |
| Actor | kind: "actor", actorId (ID or username/actor-name), optional input and runOptions. |
kind can be left out: a step with savedInputId is a saved input step, any other an Actor step.
- A saved input step runs the saved input as it is when the automation fires, so editing the saved input changes what the automation runs.
inputOverrideis merged over the saved input one level deep: the fields you list replace the saved ones, the others stay as saved. - An Actor step without
inputruns with your default saved input for that Actor, or the input schema's defaults. runOptionstakesbuild(your own Actors only),timeoutSecs,memoryMbytes,maxItems,maxTotalChargeUsd, andrestartOnError. Missing options come from the saved input, then from the Actor's defaults. There is no default spending cap: a step's run getsmaxTotalChargeUsdonly from the step, the saved input, or the Actor's own minimum cap. When the input leaves the Actor's ownmaxItemsfield empty and its input schema has one,maxItemsfills it too, within the schema's minimum and maximum.
Responses add id, actorName, and for saved input steps savedInputName and savedInputTitle.
Input from the finished run. With a runFinished trigger, text values in input and inputOverride can use the variables of webhook payload templates, filled from the run that triggered the automation: {{resource}} (that run, with dotted paths such as {{resource.defaultDatasetId}}), {{eventData.…}}, {{eventType}}, {{userId}}, {{createdAt}}, and the shortcuts such as {{actorRunId}} and {{defaultDatasetId}}. A string that is just one variable ("{{resource.defaultDatasetId}}") becomes that value, which may be a number or an object; a variable inside other text is inserted as text; unknown names are left as written. The values are filled in once per firing. With manual and schedule triggers the input is used as written.
{
"trigger": { "kind": "runFinished", "actorId": "<crawlerActorId>", "statuses": ["SUCCEEDED"] },
"steps": [
{ "kind": "actor", "actorId": "<enricherActorId>", "input": { "datasetId": "{{resource.defaultDatasetId}}", "note": "From run {{actorRunId}}" } }
]
}Every step run is a normal run on your account with the origin SCHEDULER, billed like any other run. It fires the run's webhooks, including ACTOR.RUN.CREATED.
Filters
{
"onlyWithResults": true,
"onlyNewItems": { "keyFields": ["url"] },
"conditions": [
{ "field": "price.amount", "op": "LESS_THAN", "value": 100 },
{ "field": "title", "op": "CONTAINS", "value": "remote" }
]
}Filters work on the items the steps returned, after all steps finished.
conditions: an item is kept only when every condition holds.fieldis a top-level or dotted field name.opis one ofEQUALS,NOT_EQUALS,GREATER_THAN,GREATER_THAN_OR_EQUAL,LESS_THAN,LESS_THAN_OR_EQUAL,CONTAINS,NOT_CONTAINS,IS_SET, andIS_NOT_SET(novaluefor the last two). Comparisons need a numbervalueand read numbers from text such as"€1,299.00". Text comparisons ignore case, andCONTAINSalso searches arrays.onlyNewItems: keeps only items whosekeyFieldsvalues were not in the previous run. On the first run every item counts as new. Only a short hash of the key values is stored, for the last run only (up to 10,000 items).onlyWithResults: when no item is left, nothing is delivered and the AI step doesn't run.onlyNewItemsdoes the same when there is nothing new.
AI step
{ "mode": "summarize", "prompt": "List the five cheapest offers with links." }mode | What the AI step does with the filtered items |
|---|---|
summarize | Writes a short Markdown summary following your prompt. |
filter | Decides from your prompt whether to deliver at all. A "no" skips the deliveries; an unclear answer delivers anyway. |
compare | Compares the items with the previous successful run and leads with what changed. |
extract | Returns a JSON array with the fields your prompt asks for, at most 100 objects. |
prompt is required, up to 2,000 characters. The step reads up to maxItemsPerStep items per step (200 by default), shortened to fit its input budget.
Deliveries
Each delivery has a channel, a content, and the fields of its channel:
channel | Fields | Sends |
|---|---|---|
dashboard | none | A dashboard notification that opens the run. |
email | email (optional; default your account email), columns (optional) | An email. Any address other than your account email gets a confirmation link first, and the response lists it under verifications. columns picks the results table of each step: [{ "step": 0, "title": "name", "fields": ["price", "address.city"] }], where step is the step's 0-based position, title the field shown as the row title (null picks it automatically), and fields up to 4 value columns. Steps not listed, and columns: null, pick their columns automatically. GET /v2/automations/email-fields?actorId=… (or savedInputId=…) lists the fields of your latest successful run to choose from. |
slack | none | A message to the channel connected under Settings. Needs a connected Slack workspace. |
discord | discordWebhookUrl (https://discord.com/api/webhooks/…), or webhookId of one of your Discord webhooks (a webhook with a discord notification) | A Discord message. |
webhook | url (http or https, public addresses only) | A signed POST with the run and its items. See Webhook delivery. |
googleSheets | sheetUrl, optional sheetName (first tab if empty) and maxRows (1 to 10,000, default 1,000) | Appends the kept items as rows. Share the sheet with the address from GET /v2/integrations/gsheets first. |
runAutomation | automationId (another of your automations) | Starts that automation. It must be enabled; an automation can't run itself. |
content is digest (the results digest: counts per step, links to the runs and datasets, and the first 5 items), ai (the AI step's text), or both. The default is both with an AI step and digest without one. ai and both need an AI step; googleSheets and runAutomation always use digest. When the AI step has no text, ai and both send the digest.
Email, Slack, Discord, and Google Sheets deliveries share the confirmation, unsubscribe, and rate-limit rules of webhook notifications.
On update, send a delivery back with its id and without a new url or discordWebhookUrl to keep the stored secret.
Webhook delivery
The request body:
{
"userId": "uSrIdExAmPlE123",
"createdAt": "2026-10-20T07:01:12.000Z",
"eventType": "AUTOMATION.RUN.SUCCEEDED",
"eventData": {
"automationId": "593d12cc-c97c-4ef0-bd5d-a49512326805",
"automationRunId": "3c246aa2b45548c8b1be",
"status": "SUCCEEDED",
"startedAt": "2026-10-20T07:00:00.000Z",
"actorRunIds": ["ece86a572f094bef8", "d5afaf264d5a443cb"]
},
"resource": {
"id": "3c246aa2b45548c8b1be",
"automationId": "593d12cc-c97c-4ef0-bd5d-a49512326805",
"triggerKind": "schedule",
"status": "succeeded",
"steps": [ { "index": 0, "kind": "savedInput", "runId": "ece86a572f094bef8", "status": "SUCCEEDED", "itemCount": 2, "datasetId": "…" } ],
"filter": { "totalItems": 5, "keptItems": 5, "newItems": null, "droppedByConditions": 0, "deliver": true, "reason": null },
"aiOutput": "…",
"aiCostUsd": 0.010071,
"automation": { "id": "593d12cc-c97c-4ef0-bd5d-a49512326805", "name": "Daily digest" },
"message": "Markdown of the delivery's content setting",
"items": [ { "...": "up to 100 kept items" } ],
"url": "https://my.scrape.ly/automations/593d12cc-c97c-4ef0-bd5d-a49512326805/runs/3c246aa2b45548c8b1be"
}
}eventType is AUTOMATION.RUN.FAILED (with status: "FAILED") when every step failed without results. resource is the automation run as Get run returns it, plus automation, message, items, and url.
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Scrapely-Webhook/1.0 |
X-Scrapely-Webhook | HMAC-SHA256 of the body, signed with a platform key, as for webhooks. |
X-Scrapely-Webhook-Dispatch-Id | ID of the dispatch record. |
X-Scrapely-Request-Origin | AUTOMATION |
The request also carries the reference platform's X-Apify-Webhook, X-Apify-Webhook-Dispatch-Id, and X-Apify-Request-Origin headers with the same values (origin WEBHOOK), so receivers written for that platform work unchanged.
You can't verify the signature, because the signing key is not available to you. To authenticate deliveries, put a secret in the URL's query string, for example https://example.com/hook?token=<long random string>, and check it in your endpoint. The URL is stored encrypted and only shown masked.
A delivery succeeds on any 2xx. Network errors, 429, and 5xx are tried 3 times in all, 1 and 2 seconds apart; other responses are not retried. Each attempt times out after 30 seconds, and redirects are not followed. Every delivery is listed in GET /v2/webhook-dispatches with the automation ID as webhookId.
Options
| Field | Default | Description |
|---|---|---|
isEnabled | true | A paused automation doesn't fire on its trigger. Run now still works. |
isExclusive | true with an AI step or deliveries, else false | Skip a firing while the previous run, or any of its step runs, is still going. The skipped firing shows in the run history. |
notifyOnFailure | true | The automation's failure alert. See Notifications. Same setting as notificationsEnabled in the Schedules API. |
maxWaitMinutes | 120 | How long a run waits for its step runs (1 to 1,440) before it filters and delivers what it has. |
maxItemsPerStep | 200 | Items read per step for filters, the digest, the AI step, and deliveries (1 to 1,000). |
Send null for maxWaitMinutes or maxItemsPerStep to go back to the default.
List automations
/v2/automations| Query param | Type | Description |
|---|---|---|
include | string | webhooks also lists your webhooks as automations (not ad-hoc ones). Without it, only automations are listed. |
actorId | string | Only automations with a step or a run-finished trigger that uses this Actor (ID or username~name). With include=webhooks, also webhooks whose condition names this Actor or one of its saved inputs, or that start it or one of its saved inputs. |
savedInputId | string | Only automations with a step or trigger that uses this saved input. With include=webhooks, also webhooks whose condition names it or that start it. |
search | string | Text in the API name (case-insensitive); for webhooks, in the description. |
sortBy | string | createdAt (default), name, isEnabled, lastRunAt, or nextRunAt. |
desc | boolean | Default is true (newest first). Pass desc=0 for ascending. |
offset / limit | number | Pagination. Default limit is 100, max 1000. |
Every automation is listed, plain schedules included. With include=webhooks, automations and webhooks are merged and sorted by createdAt, and total counts both. Returns { "data": { "total", "count", "offset", "limit", "desc", "items": [ ...automation ] } }.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/automations?actorId=calmswan70~link-preview-scraper&limit=20"/v2/automations?limit=5Create automation
/v2/automationstrigger and steps are required; everything else is optional. Returns 201 with { "data": { ...automation } }, plus verifications when an email delivery sent a confirmation link.
A daily summary of two Actors, sent by email and to a webhook:
curl -X POST https://api.scrape.ly/v2/automations \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Morning jobs digest",
"trigger": { "kind": "schedule", "cron": "0 7 * * 1-5", "timezone": "Europe/Berlin" },
"steps": [
{ "kind": "savedInput", "savedInputId": "<savedInputId>", "inputOverride": { "maxItems": 20 } },
{ "kind": "actor", "actorId": "<username>/<actor-name>", "input": { "searchQueries": ["engineer"], "maxResults": 20 } }
],
"filters": { "onlyNewItems": { "keyFields": ["url"] } },
"ai": { "mode": "summarize", "prompt": "The ten most interesting new jobs, one line each, with links." },
"deliveries": [
{ "channel": "email" },
{ "channel": "webhook", "content": "digest", "url": "https://example.com/hooks/jobs?token=<secret>" }
]
}'Run another saved input whenever a run of one succeeds:
{
"title": "Enrich after each crawl",
"trigger": { "kind": "runFinished", "savedInputId": "<crawlSavedInputId>", "statuses": ["SUCCEEDED"] },
"steps": [ { "savedInputId": "<enrichSavedInputId>" } ]
}Get automation
/v2/automations/{automationId}Returns { "data": { ...automation } }.
/v2/automations/{automationId}Update automation
/v2/automations/{automationId}Send only the parts you want to change: name, title, description, trigger, steps, filters, ai, deliveries, or options. A part you send replaces that part; options fields you leave out keep their values. Send "ai": null to remove the AI step; deliveries that sent its text then send the digest.
curl -X PUT https://api.scrape.ly/v2/automations/593d12cc-c97c-4ef0-bd5d-a49512326805 \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "options": { "isEnabled": false } }'Returns the updated automation.
Delete automation
/v2/automations/{automationId}Deletes the automation, its run history, and its "only new items" state. Returns 204 No Content. Runs it already started are not affected.
Duplicate automation
/v2/automations/{automationId}/duplicateCreates a copy with the same trigger, steps, filters, AI step, deliveries, and options. Stored webhook and Discord URLs are copied on the server. The copy is paused unless you send "isEnabled": true, so a scheduled automation doesn't fire twice. Optional body fields: title (default: the original title with (copy)), name, and isEnabled. Returns 201 with the copy.
Run now
/v2/automations/{automationId}/runsStarts a run now, whatever the trigger, also while the automation is paused. Returns 201 with the run: its steps have started, and the filters, AI step, and deliveries follow once they finish. Returns 409 automation-busy while another start of the same automation is in progress, or, with isExclusive, while the previous run is still going.
List runs
/v2/automations/{automationId}/runsThe run history, newest first: the last 30 runs. offset and limit (default 20, max 100) page through it. Returns { "data": { "total", "count", "offset", "limit", "items": [ ...run ] } }.
/v2/automations/{automationId}/runs?limit=5Get a run
/v2/automations/{automationId}/runs/{runId}{
"data": {
"id": "3c246aa2b45548c8b1be",
"automationId": "593d12cc-c97c-4ef0-bd5d-a49512326805",
"triggerKind": "manual",
"status": "succeeded",
"steps": [
{
"index": 0, "kind": "savedInput", "actorId": "bf064b360dd844999", "taskId": "9f2f54be-7f12-4efb-92a5-9310baa3a527",
"runId": "ece86a572f094bef8", "status": "SUCCEEDED", "itemCount": 2, "datasetId": "185ec2dd-ff21-4613-9590-ae36f1ce8c1d",
"error": null, "actorName": "link-preview-scraper", "savedInputName": "link-preview-tech-sites", "savedInputTitle": "Tech sites preview"
}
],
"filter": { "totalItems": 5, "keptItems": 5, "newItems": null, "droppedByConditions": 0, "deliver": true, "reason": null },
"aiOutput": "**Both steps succeeded: …**",
"aiCostUsd": 0.010071,
"aiStatus": "succeeded",
"aiNote": null,
"deliveries": [
{ "id": "cc8b00d2b34a", "channel": "dashboard", "status": "sent", "error": null, "sentAt": "2026-10-20T01:28:02.853Z", "dispatchId": null }
],
"error": null,
"chainDepth": 0,
"startedAt": "2026-10-20T01:27:10.733Z",
"finishedAt": "2026-10-20T01:28:02.853Z",
"automation": { "id": "593d12cc-c97c-4ef0-bd5d-a49512326805", "name": "daily-digest", "title": "Daily digest" }
}
}| Field | Values |
|---|---|
status | running (steps going), processing (filters and AI step), delivering, succeeded, failed (every step failed without results), or skipped (a firing that didn't run; error says why). |
triggerKind | schedule, manual, runFinished, chain (started by another automation), or migrated. |
steps[].status | The step run's status, PENDING while it waits for a free run slot, or FAILED when it couldn't start (error says why). |
filter | Items read, kept, new, and dropped by conditions, whether anything was delivered, and why not. |
aiStatus, aiNote | succeeded, skipped, or failed, with a note such as AI step skipped: not enough credits. |
aiCostUsd | What the AI step cost, or null without one. |
deliveries[] | Each delivery's status (sent, failed, or skipped) and error. dispatchId links webhook deliveries to their dispatch record. |
AI step costs
/v2/users/me/usage/automationsLists the AI step charges of a period, newest first (up to 1,000), with the automation run and automation each one belongs to. This is what the Automation AI steps section of Billing → Usage shows. Needs billing:read on a scoped key.
| Query param | Description |
|---|---|
from | ISO date or time, inclusive. Use it for a billing period that doesn't start on the 1st. |
to | ISO date or time, exclusive (default now). Needs from. At most 366 days after from. |
month | YYYY-MM, used when from is not set. Default: the current month (UTC). |
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/users/me/usage/automations?from=2026-10-12T00:00:00Z"month is null when you pass from. A bad date, to without from, or to not after from returns 400 invalid-input.
/v2/users/me/usage/automationsWebhooks as automations
Every webhook of your account, except ad-hoc ones, is also an automation with "source": "webhook". It has the webhook's ID, so GET /v2/automations/{webhookId} returns it. It is created and edited with the Webhooks API, which stays unchanged: its dispatches, retries, signatures, and integration tokens behave as before.
{
"id": "HG7ML7M8z78YcAPEB",
"source": "webhook",
"name": null,
"title": "Slack when the crawl fails",
"description": null,
"trigger": { "kind": "runEvent", "eventTypes": ["ACTOR.RUN.FAILED", "ACTOR.RUN.TIMED_OUT"], "actorId": "bf064b360dd844999", "savedInputId": null, "runId": null },
"steps": [],
"filters": null,
"ai": null,
"deliveries": [
{ "id": "webhook", "channel": "slack", "content": "event", "target": "#alerts · Acme", "message": null, "previewItems": 3, "onlyWithResults": false, "connected": true }
],
"options": { "isEnabled": true, "isExclusive": false, "notifyOnFailure": false, "maxWaitMinutes": null, "maxItemsPerStep": null },
"nextRunAt": null,
"lastRunAt": "2026-10-21T08:12:40.000Z",
"adminDisabled": null,
"createdAt": "2026-10-20T10:00:00.000Z",
"modifiedAt": "2026-10-20T10:00:00.000Z",
"lastRun": { "id": null, "status": "succeeded", "triggerKind": "runEvent", "startedAt": "2026-10-21T08:12:40.000Z", "finishedAt": "2026-10-21T08:12:40.000Z", "aiCostUsd": null },
"webhook": { "...": "the webhook as GET /v2/webhooks/{webhookId} returns it" }
}titleis the webhook'sdescription, or a generated title when it has none.triggeris{ "kind": "runEvent", "eventTypes", "actorId", "savedInputId", "runId" }: the webhook'seventTypesand itscondition(actorId,actorTaskId,actorRunId). All threenullmeans every run (and build) of your account.filtersis{ "onlyWithResults": true }for a notification with Only for runs with results, elsenull.deliveriesholds exactly one action, withid"webhook"andcontent"event":
channel | When | Fields |
|---|---|---|
webhook | An HTTP webhook | url, target (the URL), payloadTemplate, headersTemplate, shouldInterpolateStrings, ignoreSslErrors, doNotRetry |
slack, discord, email | A webhook with that notification | target, message, previewItems, onlyWithResults; connected for Slack, emailState for email |
googleSheets | A gsheets notification | target (the sheet link), sheetName, mode (append or replace), maxRows |
startRun | A webhook that calls /v2/actors/{actorId}/runs or /v2/actor-tasks/{taskId}/runs on this API | target, actorId or savedInputId, inputTemplate (the payload template), runOptions (from the URL's query) |
Header values are masked as in /v2/webhooks for keys that can't see them.
On a webhook's ID:
| Request | Does |
|---|---|
PUT /v2/automations/{webhookId} | Changes title (the webhook's description) and options.isEnabled. Anything else answers 400 invalid-input; edit it with PUT /v2/webhooks/{webhookId}. |
DELETE /v2/automations/{webhookId} | The same as DELETE /v2/webhooks/{webhookId}: deletes the webhook and its dispatches and revokes its integration token. 204. |
POST /v2/automations/{webhookId}/runs | Sends a test, as POST /v2/webhooks/{webhookId}/test. 201 with the test as a run. |
GET /v2/automations/{webhookId}/runs[/{runId}] | The webhook's dispatches as runs, newest first (see below). |
POST /v2/automations/{webhookId}/duplicate | Not available: 400. |
A dispatch as a run:
{
"id": "4f1c2a9e-…",
"automationId": "HG7ML7M8z78YcAPEB",
"triggerKind": "runEvent",
"status": "succeeded",
"eventType": "ACTOR.RUN.FAILED",
"actorRunId": "ece86a572f094bef8",
"actorId": "bf064b360dd844999",
"steps": [],
"filter": null,
"aiOutput": null, "aiCostUsd": null, "aiStatus": null, "aiNote": null,
"deliveries": [
{ "id": "webhook", "channel": "slack", "status": "sent", "error": null, "sentAt": "2026-10-21T08:12:40.000Z", "dispatchId": "4f1c2a9e-…", "statusCode": 200, "responseTimeMs": 412 }
],
"error": null,
"chainDepth": 0,
"startedAt": "2026-10-21T08:12:40.000Z",
"finishedAt": "2026-10-21T08:12:40.000Z",
"automation": { "id": "HG7ML7M8z78YcAPEB", "name": null, "title": "Slack when the crawl fails" }
}Convert a webhook
/v2/webhooks/{webhookId}/convert-to-automation/v2/webhooks/{webhookId}/convert-to-automation?dryRun=1Turns a Trigger Actor or Trigger task webhook (one that calls /v2/actors/{actorId}/runs or /v2/actor-tasks/{taskId}/runs on this API when a run finishes) into an automation with a run-finished trigger and the same run as its step. The webhook is then deleted, which revokes its integration token. With dryRun=1, the response shows the automation that would be created and nothing changes.
- The webhook's condition must name an Actor or a task, and its events must all be run-finished events (
ACTOR.RUN.SUCCEEDED,FAILED,ABORTED,TIMED_OUT). - A JSON object payload becomes the step input. Variables inside its strings, such as
"{{resource.defaultDatasetId}}", carry over as input from the finished run. A payload that isn't a JSON object (for example with a variable outside quotation marks), or one with variables andshouldInterpolateStrings: false, can't be converted. Without a payload, the step runs the saved input as saved, or the Actor with your default saved input. - Run options in the URL (
build,timeout,memory,maxItems,maxTotalChargeUsd,restartOnError) become the step's run options. - Slack, Discord, Email, and Google Sheets webhooks and ad-hoc webhooks can't be converted. They are already listed as automations.
Returns 201 with { "data": { "automation", "webhookId", "webhookDeleted": true, "integrationTokenRevoked": true, "notes" } }. A webhook that can't be converted without changing what it does returns 409 cannot-convert with the reason. Needs automations:write, actors:run, and webhooks:write on a scoped key.
Workflows (removed)
The AI workflows of the AI chat were replaced by automations. /v2/workflows* answers 410 endpoint-removed with Link: </v2/automations>; rel="successor-version". Use /v2/automations; an automation with an AI step and a delivery does what a workflow did.