Apify

Runs an actor or a saved task in your Apify account and hands its results to the next node — a Google Maps scrape, a website crawl, an Instagram profile, whatever the Store has. For most actors that is one step: run, wait, receive the items. For the slow ones there is a start operation, a status check and a dataset read, which is the polling loop you would otherwise build by hand with three HTTP nodes and a token pasted in each.

Setup

One credential, of type Apify: the API token from Apify Console → Settings → API & Integrations. Add it under Settings → Credentials or straight from the node, then pick it. There is no OAuth — Apify does not offer one to third parties, so Zapier, Make and n8n paste a token too.

⚠The token is the whole account. Every actor you run and every dataset you read goes through it, and the node sends it on every call — including the dataset read. That matters because Apify accounts default to General resource access: anyone with the ID, which makes dataset URLs readable without a token at all. Set the account to Restricted in Settings → Security and let this node be the thing that knows the token.

Operations

The Operation dropdown decides what the node does and which fields appear under it. These are the exact values the API validates against:

ValueIn the dropdownApify callWhat it does
runActorAndWaitRun actor and waitPOST /actors/…/run-sync-get-dataset-itemsRuns the actor and returns its dataset items in the same step. Waits up to 5 minutes.
runTaskRun task and waitPOST /actor-tasks/…/run-sync-get-dataset-itemsSame, for a saved task — an actor with its input already set in Apify. Overrides are optional.
startRunStart actor runPOST /actors/…/runsStarts the actor and returns the run right away: its id, status, defaultDatasetId. Optionally waits up to 60 s first.
getRunGet runGET /actor-runs/…The current state of a run — RUNNING, SUCCEEDED, FAILED… This is the check in a polling loop.
getDatasetItemsGet dataset itemsGET /datasets/…/itemsReads a dataset's items, usually the run's defaultDatasetId. Limit, offset and a field list.
getKeyValueRecordGet key-value recordGET /key-value-stores/…/records/…One record from a store — the run's OUTPUT, a results map, anything the actor stored by name.
abortRunAbort runPOST /actor-runs/…/abortStops a run that is still going. Returns the run with its final state.

Left out on purpose as operations: listing your actors and datasets, Apify webhooks and the request queue. None of those is needed to run an actor and read what it produced, and the four run/read operations above cover every shape of that. Your actors and tasks are still listed where you set one up — see actorId below.


Configuration

FieldOperationsWhat it is
actorIdrunActorAndWait, startRunPick it from the list of actors your token has created or run, most recently run first. Typing in the list's search box also searches the Apify Store, so an actor you haven't run yet shows up under Apify Store with how it charges (free, pay per event, pay per result…). You can still type it as username~actor-name (apify~web-scraper, compass~crawler-google-places) or its ID — templates allowed when typed.
taskIdrunTaskPick it from your account's saved tasks, or type it as username~task-name or its ID.
inputrunActorAndWait, startRun, runTaskThe actor's input — each actor documents its own. Paste the JSON from the Apify console (Input → JSON) as-is, or write simple fields one per line as field: value. Strings inside may use {{payload.x}}. Empty = the actor's defaults (or, for a task, the task's saved input).
limitthe three that return itemsHow many items to bring into the flow. Empty = all of them — careful with big datasets.
cleanthe three that return itemsApify's clean flag: drops empty items and fields starting with #. Default Yes.
waitForFinishSecsstartRun0–60. If the run finishes in time, the returned run already says SUCCEEDED and you can skip the loop. Empty = come back immediately.
runIdgetRun, abortRunUsually {{payload.id}} from the Start actor run above it.
datasetIdgetDatasetItemsUsually {{payload.defaultDatasetId}} from the run.
offset, fieldsgetDatasetItemsSkip the first N items; keep only these comma-separated fields in each item.
storeId, recordKeygetKeyValueRecordThe store (usually {{payload.defaultKeyValueStoreId}}) and the record name (OUTPUT).
timeoutSecs, memoryMbytes, buildthe three that start a runAdvanced. Override the actor's defaults for this run. Empty = the actor's own.
gracefullyabortRunYes lets the actor finish its current item (Apify waits up to 30 s). Default No.

Which one you want

Run actor and wait — the one to start with

One node, one step. The node starts the run, follows it until it finishes, and reads its dataset — it hands the items down, marked iterable, so a Loop underneath walks them one by one and a Sheets or Mongo node writes each. Along with the items it hands down the run: runId, status, defaultDatasetId and defaultKeyValueStoreId, so a Get dataset items further down can page through the rest. Most Store actors on a normal input finish well inside the window.

The window is 5 minutes. If the run is still going by then, the node fails with a message that gives its runId and dataset — the run keeps going in Apify, so don't start another: check it with Get run and read it with Get dataset items. A run that ends FAILED, ABORTED or TIMED-OUT fails the node with that status. If an actor regularly needs longer, use the next shape.

Start, check, read — for slow actors

Three operations and a Conditional that loops back, which is the pattern this node was built to replace three raw HTTP calls with:

Polling a long run
Webhook
  → Apify "Start"        operation: startRun
                           actorId: compass~crawler-google-places
                           input:   { "searchStringsArray": ["{{payload.query}}"], "maxCrawledPlaces": 200 }
                           waitForFinishSecs: 60
                         → hands down { id, status, defaultDatasetId, defaultKeyValueStoreId, … }
  → Apify "Check"        operation: getRun
                           runId: {{payload.id}}
                         → hands down the run again, with its current status
  → Conditional          payload.status equals "SUCCEEDED"
      ✓ → Apify "Read"   operation: getDatasetItems
                           datasetId: {{payload.defaultDatasetId}}
                           limit: 200
                         → hands down { items: [...], count } — iterable
      ✗ → back to "Check", with "Wait before looping back" set to 30 s

Two details make it work. Get run hands the run down again, so the Conditional reads {{payload.status}} and the read node picks the dataset id straight off the payload — nothing has to be fetched by name. And the loop-back wait lives in the Conditional (Advanced → Wait before looping back), so the check does not hammer Apify; the engine caps the loop at 50 cycles so a run that never finishes cannot spin forever. See Looping back (polling) on the Conditional page.

Put waitForFinishSecs: 60 on the start and the loop often does not even run: if the actor is done within the minute, the first Check already sees SUCCEEDED. For a run gone wrong, add a branch on FAILED or ABORTED so the loop exits instead of waiting for a success that will not come.

Run task and wait

A task is an actor with its input saved in Apify. Use it when the same scrape runs on a schedule and the only thing that changes per event is one or two fields — the Input overrides box merges on top of the saved input, so startUrls: [{ "url": "{{payload.website}}" }] is the whole configuration.

Get key-value record

Some actors do not write a dataset; they store one result under a name in the run's key-value store — OUTPUT is the convention, a screenshot actor stores images. The node reads one record and parses it if it is JSON; text (plain, CSV, XML, HTML) comes back as text with its content type, and anything binary — an image, a PDF — comes back as a file (_file), the same way the HTTP Request node hands down a download, ready to attach or upload. Records larger than 8 MB fail with a message that says so.

Datasets, stores and keys can be picked. Most of the time they come from the flow — the run's defaultDatasetId or defaultKeyValueStoreId — and that is still Use the run's ID from the flow under the list. To read a fixed one, pick it from the list: the named ones by name, the latest runs' by actor and date. Pick a store and its record keys are listed too.


What downstream nodes receive

The node hands its own result to whatever comes next, replacing the payload it was given. After a Run actor and wait, Run task or Get dataset items:

jsonOutput payload — items
{
  "_meta": { "iterable": true, "iterateField": "items", "count": 2 },
  "items": [
    { "title": "Niza Restaurante", "totalScore": 4.6, "phone": "+52 …", "website": "http://www.niza.mx/" },
    { "title": "…", "totalScore": 4.2 }
  ],
  "count": 2,
  "runId": "ABHqEsz3sjW5Kq7Ye",
  "status": "SUCCEEDED",
  "defaultDatasetId": "dSyF9Kk3aSqX8Yb2N",
  "defaultKeyValueStoreId": "kVrT2Lq8sZfB4Wc1M"
}

Iterable over items: a Loop underneath walks them, and a node that writes rows writes one per item. The run fields come with Run actor and wait and Run task, not with Get dataset items. After a Start actor run, Get run or Abort run the run comes down at the root, so its fields are one hop away:

jsonOutput payload — a run
{
  "_meta": { "iterable": false, "count": 1 },
  "id": "ABHqEsz3sjW5Kq7Ye",
  "actId": "nwua9Gu5YrADL7ZDj",
  "status": "RUNNING",
  "startedAt": "2026-09-13T15:02:11.000Z",
  "defaultDatasetId": "dSyF9Kk3aSqX8Yb2N",
  "defaultKeyValueStoreId": "kVrT2Lq8sZfB4Wc1M",
  "stats": { "…": "…" }
}

A Get key-value record hands down { key, contentType, value }, with value parsed when the record is JSON, and a _file instead of value when it is binary.

ℹBecause the payload is replaced, a node below the Apify node reads {{payload.items}} or {{payload.status}}. Anything it needs from the original event it fetches by name — {{$("Webhook").payload.query}} — in a node that supports cross-node references, such as Email, Notification or Code.

Example — leads from Google Maps

Search → scrape → one row per place
Webhook  "New search"        { "query": "restaurantes en Polanco", "max": 50 }
  → Apify "Scrape places"    operation: runActorAndWait
                               actorId: compass~crawler-google-places
                               input: {
                                 "searchStringsArray": ["{{payload.query}}"],
                                 "maxCrawledPlaces": "{{payload.max}}",
                                 "language": "es"
                               }
                               limit: 50
                             → hands down { items: [ {title, phone, website, totalScore, …} ], count }
  → Loop                     walks payload.items, one place per pass
  → Google Sheets            append row: {{payload.title}} · {{payload.phone}} · {{payload.website}}

{{payload.max}} inside the input is a template in a JSON value. When a value is only a reference — "{{payload.max}}", "{{payload.queries}}" — it lands in the actor's input with the event's own type: a number stays a number and a list a list, even though you write it in quotes. With text around it, or a filter like {{payload.zip | trim}}, it is text — so a zip code like 01234 or a long ID arrives exactly as written. The limit is what enters the flow; the actor's own cap (maxCrawledPlaces) is what it scrapes — set both, or a generous actor fills the run with items you then pay to skip.


AI toolkit mode

Once the credential is picked, the node asks for a Mode. Single operation is everything above: one operation, your templates, every run. AI toolkit turns the node into seven tools that an AI Node — or an MCP client, through one of your MCP servers — calls by name, writing the actor's input and the options per call.

ToolDoesArguments
run_actorRuns an actor and waits for its dataset items — Run actor and waitactor_id · input_json? · limit? · clean? · timeout_secs? · memory_mbytes? · build?
start_actor_runStarts an actor and returns the run without waiting for it — Start actor runactor_id · input_json? · wait_for_finish_secs? · timeout_secs? · memory_mbytes? · build?
run_taskRuns a saved task and waits for its items — Run task and waittask_id · input_json? · limit? · clean? · timeout_secs? · memory_mbytes? · build?
get_runThe status of a run and the IDs of its dataset and storerun_id
get_dataset_itemsReads the items of a dataset, a page at a timedataset_id · limit? · clean? · offset? · fields?
get_key_value_recordReads one record of a key-value storestore_id · record_key
abort_runStops a run that is still goingrun_id · gracefully?

input_json is the actor's input as a JSON object, written by the model. When the node's Single operation names a fixed actor or task, actor_id and task_id become optional and default to it — the model only names one to run something else.

What you control

Everything below lives in the Settings card of the node's AI Toolkit section — all optional: the defaults work.

  • Actors the model can run. Every run spends your credits, so it starts closed: this node's actor + the list below means the actor or task of the Single operation plus the ones you add. user/name, user~name and the ID all name the same actor. Any actor lets the model run anything your token reaches. Reading runs, datasets and records is never limited.
  • Limits. The model decides: runs return 50 items unless it asks for more, up to 200; a run can last up to an hour and use up to 8 GB of memory, and when it doesn't say, the actor's own timeout and memory apply. What you set in Single operation doesn't count in toolkit mode, and the answer says when a request was capped.
  • Characters per call sent to the model. 20,000 by default (about 5,000 tokens), anything from 1,000 to 200,000.

What goes back to the model

The same response as the operation. Items that don't fit the characters limit are left out whole — never half an item — and a notes field says how many, so the model can read the rest with get_dataset_items and offset, or ask for fewer fields.run_actor and run_task return the run too — runId and defaultDatasetId — so the model pages through that dataset instead of running the actor again. A limit of 0 or less means the default 50, and input_json must be a JSON object: a list is refused before anything runs. Arguments are used as written: a {{payload.x}} the model writes is not resolved, so it can't read the output of other nodes in the flow.

Testing a tool

⚠Only a tool call picks the tool. In AI toolkit mode, only an AI Node, an MCP client or a voice agent chooses which tool the node runs. Run Test, a flow or a webhook run the operation saved on the node: an operation in their payload that names another tool is refused.
✦To try a tool, call it from an AI Node or from an MCP client. Its arguments look like this:
jsonA tool call
{ "operation": "get_dataset_items", "dataset_id": "your-dataset-id", "limit": 5 }

Reading costs nothing; run_actor runs the actor for real.

⚠A node in toolkit mode is not part of the pipeline. Events arriving through connections made before you switched are refused — the node only runs the tool it is asked for.

Gotchas

  • Running an actor spends Apify credits — from a flow, from Run Test and from an agent in toolkit mode alike. Run Test runs it for real. Point a test at a small input (maxItems: 5) while you set the node up.
  • 5 minutes is Apify's ceiling for the synchronous operations, and a 408 does not stop the run — it only stops the wait. Switch to Start + Get run + Get dataset items for slow actors.
  • waitForFinishSecs caps at 60. That is Apify's maximum for a start call; anything higher is clipped.
  • An input that is not valid JSON is rejected before anything is called. The message names the problem; nothing runs.
  • Run, dataset, store and task IDs belong to the account of the token. Connect a different Apify credential and a saved runId may point at something the new token cannot read — the node says so under the credential.
  • An empty limit is all the items, not none. On a 40 000-row dataset that is a 40 000-item payload.
  • The credential must be apify. Any other type is refused when the node is created, with the reason.