---
title: "Run Actors · Scrape.ly Docs"
description: "Start runs from the dashboard, API, clients, or CLI, with input, run options, and limits."
url: https://scrape.ly/docs/run-actors
markdown: https://scrape.ly/docs/run-actors.md
---

# Run Actors

> Start runs from the dashboard, API, clients, or CLI, with input, run options, and limits.

A run is input (a JSON object) plus run options such as memory and timeout. Start one from the dashboard, the API, a client, or the CLI:

```bash [cURL]
curl -X POST "https://api.scrape.ly/v2/actors/jane~google-maps-scraper/runs?memory=1024&timeout=600" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms": ["coffee"], "maxResults": 50}'
```

```js [JavaScript]
import { ScrapelyClient } from 'scrapely-client';

const client = new ScrapelyClient({ token: process.env.SCRAPELY_TOKEN, baseUrl: 'https://api.scrape.ly' });
const run = await client.actor('jane~google-maps-scraper').call(
    { searchTerms: ['coffee'], maxResults: 50 },
    { memory: 1024, timeout: 600 },
);
```

```python [Python]
import os
from scrapely_client import ScrapelyClient

client = ScrapelyClient(os.environ['SCRAPELY_TOKEN'], api_url='https://api.scrape.ly')
run = client.actor('jane~google-maps-scraper').call(
    run_input={'searchTerms': ['coffee'], 'maxResults': 50},
    memory_mbytes=1024,
)
```

| To | See |
| --- | --- |
| Run from the browser | [Run from the dashboard](#run-from-the-dashboard) |
| Start, wait for, or sync-run over HTTP | [Run from the API](#run-from-the-api) |
| Set memory, timeout, cost cap, build | [Query parameters](#query-parameters) |
| Watch, abort, rerun, resurrect | [Monitor runs](#monitor-runs), [Abort, rerun, and resurrect](#abort-rerun-and-resurrect) |
| Fix a 4xx | [Limits and errors](#limits-and-errors) |

## Run from the dashboard

Open the Actor from the [Store](https://scrape.ly/docs/store.md) or from **Actors** in the side menu.

**Actors** (the **Used actors** tab, next to **Saved inputs**) lists the Actors you've created or run, most recently run first (**Last run started**). Use the same dropdown to show everything (**Pricing model**) or one pricing model (**Free** or **Pay per event**), and **Last run status** to filter by how the last run ended. **My Actors** (under **Development**) lists only your own Actors, in creation date order; pick **Last run started** there to sort by last run instead. Clicking a column header overrides either order.

**Start** (in the page header and at the bottom of **Input**) runs exactly what the input form shows, with the run options shown under it. Both buttons do the same thing.

- If the form shows one of your [saved inputs](#saved-inputs) unchanged, the run is recorded against it: it appears in that saved input's runs and counts as its last use.
- If you changed the form, or picked an example, **Defaults** or a recent run, the run starts with the form's input as it is. A message then offers **Save as…** or **Update “name”** so you can keep the input.

The run page opens next. An Actor marked **Under Maintenance** asks you to confirm with **Run anyway**; an unapproved **Full Access** Actor asks for **Approve & Run** (see [Permissions](https://scrape.ly/docs/store.md#permissions)).

### Input: form or JSON

The **Input** tab's **Form** / **JSON** toggle switches between a form generated from the input schema and the raw JSON object (handy for API calls; it reports **Invalid JSON.** or **Input must be a JSON object.** until valid).

Under the form, **API & MCP** shows how to run the same input from code: snippets for **HTTP / cURL**, **JavaScript**, **Python** and the **CLI** built from the input in the form, the **MCP** setup for AI agents, and the Actor's **OpenAPI** definition. **Use my API token** puts your API key into the snippets. The section is closed until you open it and stays the way you left it. On your own Actors it is under **Source** → **Input**.

When a build's input schema differs from the one an input was saved with, the form fits the input to it: fields the schema no longer has are hidden and not sent, and new fields get their default or prefill.

### Saved inputs

The **Input** dropdown above the form fills the form from one of these, in this order:

| Section | What it holds |
| --- | --- |
| **Your saved inputs** | Inputs you saved for this Actor. Your default (marked **Default**) comes first, then the most recently used |
| **Examples** | Ready-made inputs from the Actor's developer: published example tasks and [presets](https://scrape.ly/docs/publishing.md#input-presets). They can't be overwritten. An example marked **May be unreliable** failed its recent automatic tests, so it may return fewer results or none |
| **Defaults** | The input schema's default and prefill values |
| **Recent runs** | The inputs of your last three runs of this Actor |

The page opens with your default saved input, or the first example, or **Defaults**. Picking an entry only fills the form; nothing runs until you press **Start**. If you changed the form, you're asked before your changes are replaced.

| Action | Does |
| --- | --- |
| **Save “name”** | Overwrites the selected saved input with the form and its run options. Shown when you changed one of your own saved inputs |
| **Save as new…** | Saves the form as a new saved input under a name you choose, optionally as your default |
| **Rename…**, **Duplicate**, **Delete…** | Manage the selected saved input. **Delete…** lists the automations that still use it |
| **Set as default** | The default opens first on this Actor's page |
| **Copy link** | A link that opens this Actor's page with that entry selected |
| **Automate this** | Opens a new [automation](https://scrape.ly/docs/automations.md) that runs the selected saved input. If the form has unsaved changes, or shows an example, you save it as a new saved input first |

Each saved input in the dropdown shows its run count, when it was last used, and how many automations use it. **Manage all saved inputs** at the bottom of the dropdown opens **Actors** → **Saved inputs**, the list of saved inputs across all your Actors, searched for this Actor.

A saved input is a [task](https://scrape.ly/docs/api-tasks.md) in the API, so the same saved inputs work with `POST /v2/actor-tasks/{taskId}/runs`, the clients' `task()` calls and the CLI. The **API & MCP** section under the input form (closed until you open it) shows snippets **By reference** (they run the saved input as stored) or with the input **Inline**. Secret fields are stored but shown masked.

> [!WARNING]
> The dashboard checks the form before running, for example that every URL in a request list is valid. The API does not validate input against the schema and does not fill in defaults: the JSON you send is exactly what the Actor receives. Include every field the Actor needs.

### Run options

Under the form, **Run options** (collapsed) shows memory, timeout and maximum cost:

| Option | What it does |
| --- | --- |
| **Memory** | 128 MB to 16 GB. CPU scales with memory (4 GB gets one CPU core), and so does the compute cost per minute. Your account's maximum per run still applies. |
| **Timeout** | Seconds before the run is stopped. **No timeout** leaves it unset, so the Actor's default timeout applies, or 3600 seconds if the Actor has none. |
| **Maximum cost per run** | A USD cap on the run's total charges (platform usage plus the Actor's fee). **No maximum limit** removes the cap. |

Run options are saved with each saved input; changing them counts as an unsaved change. Examples and **Defaults** start from the Actor's default options. On someone else's Actor there is no build option: your runs always use the build the publisher picked (the [User Build](https://scrape.ly/docs/publishing.md#user-build)).

## Run from the API

Authenticate with `Authorization: Bearer <token>`. Create a token under **Settings** → **API & Integrations**.

Refer to an Actor by its ID or by `username~name` (for example `jane~google-maps-scraper`). `~name` alone refers to one of your own Actors.

```
POST /v2/actors/{actorId}/runs
POST /v2/actors/{actorId}/run-sync
POST /v2/actors/{actorId}/run-sync-get-dataset-items
```

In all three, the request body is the Actor's input as a JSON object. Run options go in the query string.

### Start a run (asynchronous)

`POST /v2/actors/{actorId}/runs` starts the run and returns immediately with `201 Created` and the run object, including `id`, `status`, `defaultDatasetId`, and `defaultKeyValueStoreId`. The `Location` header points at `/v2/actor-runs/{runId}`.

Add `waitForFinish=<seconds>` (up to 60) to make the request wait for the run to finish before it returns. The wait stops at 45 seconds. If the run is still going when the wait ends, you get the run in its current state.

If your code retries failed requests, send an `Idempotency-Key` header with the start request. A retry with the same key returns the run that already started instead of starting a second one. See [Idempotent run starts](https://scrape.ly/docs/api-actors.md#idempotent-run-starts).

> [!NOTE]
> If the platform has no free capacity right now, the run is queued: you get `202 Accepted` with the run in `READY` status and a `meta` object with `queuePosition`, `estimatedWaitSecs`, and `reason`. The run starts automatically when capacity frees up.

### Run synchronously

The synchronous endpoints start a run, wait for it, and return the results in the same response:

- `run-sync` returns the run's `OUTPUT` record from its default key-value store, or `{}` if the Actor didn't write one. Pass `outputRecordKey` to return a different record.
- `run-sync-get-dataset-items` returns the run's dataset items. It accepts all the [dataset item parameters](https://scrape.ly/docs/results.md#download-with-the-api), such as `format`, `fields`, `clean`, and `limit`.

Both wait at most 45 seconds and cap the run's timeout at 300 seconds. If the run hasn't finished after 45 seconds, you get `408 run-timeout-exceeded` with the run ID in the message. The run keeps going, so wait for it as described below instead of calling the endpoint again. If it finishes with any status other than `SUCCEEDED`, you get `400 run-failed`. Use the asynchronous endpoint for longer jobs.

Both synchronous endpoints also accept `GET`, which runs the Actor with an empty input.

### Wait for a run and check its status

`GET /v2/actor-runs/{runId}?waitForFinish=60` returns the run as soon as it finishes, or after 45 seconds with the run in its current state. Repeat it until `status` is terminal; the official clients' `call()` and `waitForFinish()` do this for you. For a run started elsewhere, `GET /v2/actors/{actorId}/runs/last?waitForFinish=60` waits the same way for your latest run of the Actor. Filter it with `status`, for example `status=SUCCEEDED`.

```api-test
{ "method": "GET", "path": "/v2/actors/{actorId}/runs/last?status=SUCCEEDED" }
```

```api-test
{ "method": "GET", "path": "/v2/actor-runs/{runId}" }
```

### Query parameters

| Parameter | Values | Default |
| --- | --- | --- |
| `build` | Build tag or number. Ignored on someone else's Actor, which always runs the [User Build](https://scrape.ly/docs/publishing.md#user-build). | The Actor's default build, else `latest` |
| `memory` | Megabytes; a power of two (128, 256, 512, 1024, …) within your limits | The Actor's default, else 2048 |
| `timeout` | Seconds; a positive integer | The Actor's default, else 3600 |
| `maxTotalChargeUsd` | USD, a non-negative decimal | The Actor's minimum, if set; otherwise no cap |
| `maxItems` | Integer | The Actor's default, if any |
| `restartOnError` | `true`/`1` or `false`/`0` | The Actor's default, else `false` |
| `waitForFinish` | 0–60 seconds; the wait stops at 45 | 0 |
| `webhooks` | Base64-encoded JSON array of ad-hoc webhooks | None |
| `forcePermissionLevel` | `LIMITED_PERMISSIONS` to run a Full Access Actor with limited access | The Actor's level |

Notes on the less obvious options:

- **`maxTotalChargeUsd`** caps the run's total charges: compute, storage, data transfer, proxy, [Scrape.ly browser](https://scrape.ly/docs/browser.md) credits, [LLM gateway](https://scrape.ly/docs/llm-gateway.md#spending-cap-and-balance) requests, and the Actor's fee. When the cap is reached, the run is stopped and the Actor fee is limited to what fits under the cap.
- **`maxItems`** is passed to the Actor as `ACTOR_MAX_PAID_DATASET_ITEMS`. Actors that charge per result item are expected to stop after that many results, and the platform never bills more result-item events than `maxItems`. Other events still count, so use `maxTotalChargeUsd` for a hard limit on the whole run.
- **`restartOnError`**: if the run fails, Scrape.ly starts one new run with the same input and options. The new run has its own ID and storages.
- **`webhooks`**: see [Ad-hoc webhooks](https://scrape.ly/docs/integrations.md#ad-hoc-webhooks).

Invalid values return `400`: `invalid-timeout`, `invalid-memory`, or `invalid-max-total-charge`.

## Run from code

The clients wrap the same endpoints. Install with `npm install scrapely-client` or `pip install scrapely-client-python`.

> [!WARNING]
> Always pass `https://api.scrape.ly` as the base URL. Both clients default to another host.

```js [JavaScript]
import { ScrapelyClient } from 'scrapely-client';

const client = new ScrapelyClient({
    token: process.env.SCRAPELY_TOKEN,
    baseUrl: 'https://api.scrape.ly',
});

// Start the run and wait for it to finish
const run = await client.actor('jane~google-maps-scraper').call(
    { searchTerms: ['coffee'], maxResults: 50 },
    { memory: 1024, timeout: 600, maxTotalChargeUsd: 2 },
);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python [Python]
import os
from scrapely_client import ScrapelyClient

client = ScrapelyClient(os.environ['SCRAPELY_TOKEN'], api_url='https://api.scrape.ly')

run = client.actor('jane~google-maps-scraper').call(
    run_input={'searchTerms': ['coffee'], 'maxResults': 50},
    memory_mbytes=1024,
)
items = client.dataset(run.default_dataset_id).list_items().items
```

Use `.start(input, options)` instead of `.call()` to return as soon as the run is created. See the [JavaScript client](https://scrape.ly/docs/client-js.md) and [Python client](https://scrape.ly/docs/client-python.md) guides.

## Run from the CLI

The [CLI](https://scrape.ly/docs/cli.md) can run any Actor you have access to, including Store Actors:

```bash
npm install -g scrapely-cli
scrapely login

# Run and wait, streaming the log; print the dataset at the end
scrapely call jane~google-maps-scraper \
  --input '{"searchTerms": ["coffee"], "maxResults": 50}' \
  --memory 1024 --timeout 600 --output-dataset

# Start and return immediately
scrapely actors start jane~google-maps-scraper --input-file input.json
```

`call` and `actors start` accept `--input`, `--input-file` (or `-` for stdin), `--build` (your own Actors only), `--memory`, `--timeout`, and `--json`. They don't expose `maxTotalChargeUsd`; use the API or a client if you need a cost cap. `scrapely run` is different: it runs the Actor project in your current directory.

To follow runs from the CLI, use `scrapely runs ls`, `runs info <runId>`, `runs log <runId>`, `runs wait <runId>`, `runs abort <runId>`, and `runs resurrect <runId>`. See [CLI runs & builds](https://scrape.ly/docs/cli-runs-builds.md).

## Monitor runs

**Runs** in the side menu lists all your runs. Filter it by status or origin, or search. Each Actor page also has a **Runs** tab with just that Actor's runs.

A run moves through these statuses:

| Status | Meaning |
| --- | --- |
| `READY` | Created, waiting for a worker |
| `RUNNING` | The Actor is running |
| `SUCCEEDED` | Finished successfully |
| `FAILED` | The Actor exited with an error |
| `ABORTING` / `ABORTED` | Being stopped, or stopped, at your request |
| `TIMING-OUT` / `TIMED-OUT` | Being stopped, or stopped, because the timeout was reached |

A resurrected run goes back to `RUNNING`. `RESURRECTED` only appears as the `ACTOR.RUN.RESURRECTED` webhook event.

The run page shows the status, live CPU and memory, and these tabs: **Log**, **Input**, **Output** (when the Actor defines one), **Storage** (**Dataset**, **Key-value store**, **Request queue**), and **Automations** (the deliveries of automations that reacted to this run). **Show details** reveals the run ID, origin, start and finish times, build, memory, timeout, maximum cost, and the **Cost**; click the cost for a breakdown.

From the API:

```
GET /v2/actor-runs
GET /v2/actors/{actorId}/runs
GET /v2/actor-runs/{runId}
GET /v2/actor-runs/{runId}/log
GET /v2/actor-runs/{runId}/input
```

The run lists accept `status` (comma-separated), `startedAfter`, `startedBefore`, `offset`, `limit`, and `desc`. `GET /v2/actor-runs` also accepts `origin` and `search`. The log is returned as plain text.

```api-test
{ "method": "GET", "path": "/v2/actor-runs?status=SUCCEEDED&limit=5&desc=true" }
```

## Abort, rerun, and resurrect

| Dashboard (run page) | API | Does |
| --- | --- | --- |
| **Abort** | `POST /v2/actor-runs/{runId}/abort` | Stops a running run |
| **Actions** → **Rerun** | Start a new run | New run with the same input and options |
| **Actions** → **Resurrect** | `POST /v2/actor-runs/{runId}/resurrect` | Restarts a finished run |
| **Actions** → **Delete** | `DELETE /v2/actor-runs/{runId}` | Removes the run |

- **Abort** with `gracefully=true` first tells the Actor to save its state and stop, instead of killing it right away. A run that hasn't started yet is aborted at once and its reserved credits are released.
- **Resurrect** works on `SUCCEEDED`, `FAILED`, `ABORTED`, and `TIMED-OUT` runs. The run keeps its ID and storages, so a crawler can continue where it stopped. You can override `build` (on your own Actors only), `timeout`, `memory`, `maxItems`, `maxTotalChargeUsd`, and `restartOnError`. A resurrected run is billed again and checked against your balance and limits like a new run.

## Limits and errors

Your account limits are under **Billing** → **Limits** and in `GET /v2/users/me/limits`. The ones that affect runs:

| Limit | Caps |
| --- | --- |
| Concurrent runs | Runs active at once |
| Total memory | Combined memory of all your active runs |
| Memory per run | Minimum and maximum for one run (128 MB and 8 GB by default) |
| API rate limit | Requests per minute to the API |

| Status | Error type | Cause and fix |
| --- | --- | --- |
| `400` | `invalid-memory` | Memory is not a power of two, or is outside your per-run limits. |
| `400` | `limit-exceeded` | This run would take your active runs over your total memory limit. Wait for runs to finish or use less memory. |
| `400` | `no-successful-build` | Your Actor has no successful build to run. |
| `400` | `actor-not-available` | Someone else's Actor can't be run right now. |
| `400` | `unsupported-pricing` | The Actor uses a pricing model Scrape.ly can't run. |
| `402` | `payment-required` | Your balance can't cover the run's estimated cost (timeout × memory × compute price, plus the Actor's event prices). Top up, or lower `timeout` or `memory`. |
| `403` | `forbidden` | The Actor is private and isn't yours. |
| `403` | `full-permission-actor-not-approved` | Approve the Actor in the dashboard or with `POST /v2/actors/{actorId}/approve-permissions`, then start the run again. |
| `404` | `actor-not-found` | Check the Actor ID or `username~name`. |
| `429` | `concurrency-limit-exceeded` | You're at your concurrent-runs limit. Retry when a run finishes. |
| `429` | `run-capacity-check-busy` | Another run request from your account is being checked (synchronous endpoints). Retry shortly. |

## What's next

- [Results & exports](https://scrape.ly/docs/results.md)
- [Integrations](https://scrape.ly/docs/integrations.md): tasks, schedules, and webhooks
- [Runs API reference](https://scrape.ly/docs/api-runs.md) and [Actors API reference](https://scrape.ly/docs/api-actors.md#start-a-run)

---

- Previous: [Find Actors in the Store](https://scrape.ly/docs/store.md)
- Next: [AI chat](https://scrape.ly/docs/ai-chat.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
