Skip to content
API

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

MethodPathDescription
GET/v2/automationsList your automations
POST/v2/automationsCreate 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}/duplicateCopy an automation
POST/v2/automations/{automationId}/runsRun it now
GET/v2/automations/{automationId}/runsRun history, newest first
GET/v2/automations/{automationId}/runs/{runId}One run
GET/v2/users/me/usage/automationsAI step costs per automation run
POST/v2/webhooks/{webhookId}/convert-to-automationTurn a Trigger Actor or Trigger task webhook into an automation. See Convert a webhook.

Errors and limits

Statuserror.typeWhen
400invalid-inputA 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.
400not-connectedA slack delivery while no Slack workspace is connected.
400actor-not-builtA step runs one of your Actors that has no successful build yet.
400actor-not-availableA step runs someone else's Actor that can't be run right now.
403feature-not-enabledAutomations are not enabled for your account yet.
403insufficient-permissionsThe key's scopes don't cover the request, or a run token tried to write.
403limit-reachedCreating an automation when you already have your plan's maximum. Automations and schedules share one limit.
404automation-not-foundThe automation does not exist or is not yours. Also for a runAutomation delivery that names someone else's automation.
404automation-run-not-foundThe run does not exist or belongs to another automation.
404record-or-token-not-foundA step or trigger names an Actor that doesn't exist.
404saved-input-not-foundA run-finished trigger names a saved input you don't own.
409automation-busyRun 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.
LimitValue
Steps1 to 10 (at most 10 Actor steps and 10 saved input steps, as for schedules)
Filter conditions10
Key fields for "only new items"1 to 5
AI prompt2,000 characters
Deliveries10
Step input500 KB of JSON
Run historyThe last 30 runs per automation
ChainsAn 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

.json
{
  "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
  }
}
FieldDescription
sourceautomation, or webhook for a webhook listed as an automation.
nameAPI name: letters, digits, and dashes, up to 63 characters, unique among your automations and schedules. Generated from title when you leave it out.
title, descriptionDisplay title (up to 120 characters) and description (up to 2,000).
triggerWhat starts it. See Triggers.
stepsThe runs it starts, all at the same time. See Steps.
filters, ai, deliveries, optionsSee Filters, AI step, Deliveries, and Options.
nextRunAtThe next firing of a schedule trigger, or null (paused, manual, or run-finished).
lastRunAtThe last firing of the schedule trigger. Runs started with Run now or by another trigger don't change it; use lastRun.
lastRunThe 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

triggerStarts 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

StepFields
Saved inputkind: "savedInput", savedInputId, optional inputOverride and runOptions.
Actorkind: "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. inputOverride is 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 input runs with your default saved input for that Actor, or the input schema's defaults.
  • runOptions takes build (your own Actors only), timeoutSecs, memoryMbytes, maxItems, maxTotalChargeUsd, and restartOnError. Missing options come from the saved input, then from the Actor's defaults. There is no default spending cap: a step's run gets maxTotalChargeUsd only from the step, the saved input, or the Actor's own minimum cap. When the input leaves the Actor's own maxItems field empty and its input schema has one, maxItems fills 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.

.json
{
  "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

.json
{
  "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. field is a top-level or dotted field name. op is one of EQUALS, NOT_EQUALS, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, CONTAINS, NOT_CONTAINS, IS_SET, and IS_NOT_SET (no value for the last two). Comparisons need a number value and read numbers from text such as "€1,299.00". Text comparisons ignore case, and CONTAINS also searches arrays.
  • onlyNewItems: keeps only items whose keyFields values 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. onlyNewItems does the same when there is nothing new.

AI step

.json
{ "mode": "summarize", "prompt": "List the five cheapest offers with links." }
modeWhat the AI step does with the filtered items
summarizeWrites a short Markdown summary following your prompt.
filterDecides from your prompt whether to deliver at all. A "no" skips the deliveries; an unclear answer delivers anyway.
compareCompares the items with the previous successful run and leads with what changed.
extractReturns 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:

channelFieldsSends
dashboardnoneA dashboard notification that opens the run.
emailemail (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.
slacknoneA message to the channel connected under Settings. Needs a connected Slack workspace.
discorddiscordWebhookUrl (https://discord.com/api/webhooks/…), or webhookId of one of your Discord webhooks (a webhook with a discord notification)A Discord message.
webhookurl (http or https, public addresses only)A signed POST with the run and its items. See Webhook delivery.
googleSheetssheetUrl, 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.
runAutomationautomationId (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:

.json
{
  "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.

HeaderValue
Content-Typeapplication/json
User-AgentScrapely-Webhook/1.0
X-Scrapely-WebhookHMAC-SHA256 of the body, signed with a platform key, as for webhooks.
X-Scrapely-Webhook-Dispatch-IdID of the dispatch record.
X-Scrapely-Request-OriginAUTOMATION

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

FieldDefaultDescription
isEnabledtrueA paused automation doesn't fire on its trigger. Run now still works.
isExclusivetrue with an AI step or deliveries, else falseSkip a firing while the previous run, or any of its step runs, is still going. The skipped firing shows in the run history.
notifyOnFailuretrueThe automation's failure alert. See Notifications. Same setting as notificationsEnabled in the Schedules API.
maxWaitMinutes120How long a run waits for its step runs (1 to 1,440) before it filters and delivers what it has.
maxItemsPerStep200Items 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

GET/v2/automations
Query paramTypeDescription
includestringwebhooks also lists your webhooks as automations (not ad-hoc ones). Without it, only automations are listed.
actorIdstringOnly 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.
savedInputIdstringOnly automations with a step or trigger that uses this saved input. With include=webhooks, also webhooks whose condition names it or that start it.
searchstringText in the API name (case-insensitive); for webhooks, in the description.
sortBystringcreatedAt (default), name, isEnabled, lastRunAt, or nextRunAt.
descbooleanDefault is true (newest first). Pass desc=0 for ascending.
offset / limitnumberPagination. 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 ] } }.

.bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/automations?actorId=calmswan70~link-preview-scraper&limit=20"
GET/v2/automations?limit=5

Create automation

POST/v2/automations

trigger 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:

.bash
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:

.json
{
  "title": "Enrich after each crawl",
  "trigger": { "kind": "runFinished", "savedInputId": "<crawlSavedInputId>", "statuses": ["SUCCEEDED"] },
  "steps": [ { "savedInputId": "<enrichSavedInputId>" } ]
}

Get automation

GET/v2/automations/{automationId}

Returns { "data": { ...automation } }.

GET/v2/automations/{automationId}

Update automation

PUT/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.

.bash
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

DELETE/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

POST/v2/automations/{automationId}/duplicate

Creates 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

POST/v2/automations/{automationId}/runs

Starts 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

GET/v2/automations/{automationId}/runs

The 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 ] } }.

GET/v2/automations/{automationId}/runs?limit=5

Get a run

GET/v2/automations/{automationId}/runs/{runId}
.json
{
  "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" }
  }
}
FieldValues
statusrunning (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).
triggerKindschedule, manual, runFinished, chain (started by another automation), or migrated.
steps[].statusThe step run's status, PENDING while it waits for a free run slot, or FAILED when it couldn't start (error says why).
filterItems read, kept, new, and dropped by conditions, whether anything was delivered, and why not.
aiStatus, aiNotesucceeded, skipped, or failed, with a note such as AI step skipped: not enough credits.
aiCostUsdWhat 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

GET/v2/users/me/usage/automations

Lists 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 paramDescription
fromISO date or time, inclusive. Use it for a billing period that doesn't start on the 1st.
toISO date or time, exclusive (default now). Needs from. At most 366 days after from.
monthYYYY-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.

GET/v2/users/me/usage/automations

Webhooks 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.

.json
{
  "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" }
}
  • title is the webhook's description, or a generated title when it has none.
  • trigger is { "kind": "runEvent", "eventTypes", "actorId", "savedInputId", "runId" }: the webhook's eventTypes and its condition (actorId, actorTaskId, actorRunId). All three null means every run (and build) of your account.
  • filters is { "onlyWithResults": true } for a notification with Only for runs with results, else null.
  • deliveries holds exactly one action, with id "webhook" and content "event":
channelWhenFields
webhookAn HTTP webhookurl, target (the URL), payloadTemplate, headersTemplate, shouldInterpolateStrings, ignoreSslErrors, doNotRetry
slack, discord, emailA webhook with that notificationtarget, message, previewItems, onlyWithResults; connected for Slack, emailState for email
googleSheetsA gsheets notificationtarget (the sheet link), sheetName, mode (append or replace), maxRows
startRunA webhook that calls /v2/actors/{actorId}/runs or /v2/actor-tasks/{taskId}/runs on this APItarget, 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:

RequestDoes
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}/runsSends 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}/duplicateNot available: 400.

A dispatch as a run:

.json
{
  "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

POST/v2/webhooks/{webhookId}/convert-to-automation
POST/v2/webhooks/{webhookId}/convert-to-automation?dryRun=1

Turns 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 and shouldInterpolateStrings: 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.