---
title: "Actor tasks · Scrape.ly Docs"
description: "Saved inputs and run options, synchronous task runs, and published example tasks."
url: https://scrape.ly/docs/api-tasks
markdown: https://scrape.ly/docs/api-tasks.md
---

# Actor tasks

> Saved inputs and run options, synchronous task runs, and published example tasks.

A task is a saved configuration for an Actor: a name, an input object, and run options. The dashboard calls tasks **saved inputs** (see [Saved inputs](https://scrape.ly/docs/run-actors.md#saved-inputs)); they are the same objects. All paths are relative to `https://api.scrape.ly`. Authentication is required, except for the [public example-task](#public-example-tasks) endpoints. You can only access your own tasks.

Task responses use `actId` for the Actor ID (the create body also accepts `actorId`).

`{taskId}` can be the task ID, `username~task-name`, or `~task-name` for one of your own tasks.

## List tasks

```
GET /v2/actor-tasks
```

| Query param | Description |
| --- | --- |
| `search` | Matches the task name or the Actor name (case-insensitive). |
| `sortBy` | `createdAt` (default), `modifiedAt`, or `name`. |
| `limit` / `offset` / `desc` | [Pagination](https://scrape.ly/docs/api-overview.md#pagination). Default `limit` is `1000`; `desc` defaults to `false`. |

Each item includes the Actor name and icon, `totalRuns`, and `lastRun`.

```bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/actor-tasks?limit=20&desc=1"
```

```api-test
{ "method": "GET", "path": "/v2/actor-tasks?limit=5" }
```

## List tasks for an Actor

```
GET /v2/actors/{actorId}/tasks
```

Returns **your** tasks for that Actor, even if you don't own the Actor (it must be public or yours). Default `limit` is `20`; `desc` defaults to newest first.

```bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/tasks"
```

```api-test
{ "method": "GET", "path": "/v2/actors/{actorId}/tasks" }
```

## Saved inputs for an Actor

```
GET /v2/actors/{actorId}/saved-inputs
GET /v2/acts/{actorId}/input-presets
```

`saved-inputs` returns what the dashboard's **Input** dropdown shows, in one call: your tasks for the Actor (default first, then most recently used, each with `totalRuns`, `lastRun` and the `automations` that use it), the Actor's examples with their input (secret fields masked), and your last three runs of the Actor. `input-presets` is public and returns only the examples.

`examples` lists the published example tasks first (`id` `e:<slug>`, `source: "example"`), then the [presets](https://scrape.ly/docs/publishing.md#input-presets) the owner marked in the Actor's scenarios (`id` `s:<scenario name>`, `source: "scenario"`, with `slug`, `order`, and `reliability`). `reliability` is `ok`, `degraded` (the latest automatic test cycle confirmed a failure; the dashboard shows **May be unreliable**), or `untested`. Hidden presets, and entries with the same input as an earlier one, are left out. Use the `id` in a link: `?input=s:<scenario name>` on the Actor's page selects that entry.

```json
{
  "data": {
    "savedInputs": [
      { "id": "tAsKiDeXaMpLe123", "name": "maps-dentists-austin", "title": "Dentists in Austin", "isDefault": true,
        "lastUsedAt": "2026-08-20T09:00:00.000Z", "modifiedAt": "2026-08-14T15:00:00.000Z", "totalRuns": 12,
        "lastRun": { "id": "rUnIdExAmPlE1234", "status": "SUCCEEDED", "startedAt": "2026-08-20T09:00:00.000Z" },
        "automations": [] }
    ],
    "examples": [
      { "id": "e:restaurants-berlin", "source": "example", "title": "Restaurants in Berlin, 20 places", "description": "…", "input": { "searchTerms": ["restaurant"] }, "reliability": null },
      { "id": "s:search-dentists", "source": "scenario", "slug": "search-dentists", "title": "Dentists in Austin, 10 places", "description": null, "input": { "searchTerms": ["dentist"], "location": "Austin" }, "order": 1, "reliability": "ok" }
    ],
    "recentRuns": [ { "id": "rUnIdExAmPlE1234", "status": "SUCCEEDED", "startedAt": "2026-08-20T09:00:00.000Z", "taskId": "tAsKiDeXaMpLe123" } ]
  }
}
```

```api-test
{ "method": "GET", "path": "/v2/actors/{actorId}/saved-inputs" }
```

## Create task

```
POST /v2/actor-tasks
```

The Actor must be public or yours, and must have at least one successful build. Task names are unique per user and at most 30 characters. Input is limited to 9 MB. Returns `201` and a `Location` header.

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `actId` | string | Yes | Actor ID or `username~name`. `actorId` is accepted as an alias. |
| `name` | string | Yes, unless `title` is set | Task name (max 30 characters, unique per user). Without it, a name is generated from the Actor name and `title` (`-2`, `-3`… on a clash). |
| `title` | string | No | Display title (the name shown in the dashboard). |
| `input` | object | No | Saved input. An empty object is filled from the schema defaults and prefills of the build you run (the default build on your own Actor, the [User Build](https://scrape.ly/docs/publishing.md#user-build) on someone else's). |
| `options` | object | No | `build`, `timeoutSecs`, `memoryMbytes` (a power of two), `maxItems`, `maxTotalChargeUsd`, `restartOnError`. `build` only applies to your own Actors; others run the [User Build](https://scrape.ly/docs/publishing.md#user-build), and `build` is dropped from the task on create and update. |
| `seoTitle` / `seoDescription` | string | No | Text for the task's public page if you [publish it](#publish-a-task). |
| `isDefault` | boolean | No | Makes this your default saved input for the Actor (the one the dashboard opens first). Clears the flag on your other tasks for the same Actor. |

```bash
curl -X POST https://api.scrape.ly/v2/actor-tasks \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "actId": "eLN5rTB8sYTBWdfzx",
    "name": "daily-scrape",
    "title": "Daily scrape",
    "input": { "url": "https://example.com" },
    "options": { "memoryMbytes": 2048, "timeoutSecs": 3600 }
  }'
```

| Status | Type | Meaning |
| --- | --- | --- |
| 400 | `invalid-input` | `actId` missing, both `name` and `title` missing, name over 30 characters, or `options.memoryMbytes` not a power of two. |
| 400 | `actor-not-built` | Your Actor has no successful build yet. |
| 400 | `actor-not-available` | Someone else's Actor can't be run right now. |
| 400 | `input-too-large` | The input is over 9 MB. |
| 403 | `forbidden` | Private Actor you don't own. |
| 403 | `limit-reached` | You have as many tasks as your plan allows. |
| 404 | `actor-not-found` | Unknown Actor. |
| 409 | `task-name-exists` | You already have a task with this name. |

## Get task

```
GET /v2/actor-tasks/{taskId}
```

```json
{
  "data": {
    "id": "tAsKiDeXaMpLe123",
    "userId": "uSeRiDeXaMpLe123",
    "actId": "eLN5rTB8sYTBWdfzx",
    "name": "daily-scrape",
    "title": "Daily scrape",
    "input": { "url": "https://example.com" },
    "options": { "memoryMbytes": 2048, "timeoutSecs": 3600 },
    "createdAt": "2026-08-14T15:00:00.000Z",
    "modifiedAt": "2026-08-14T15:00:00.000Z",
    "stats": { "totalRuns": 12 },
    "isPublic": false,
    "publicSlug": null,
    "seoTitle": null,
    "seoDescription": null,
    "useCase": null,
    "publishedAt": null,
    "isDefault": true,
    "lastUsedAt": "2026-08-20T09:00:00Z"
  }
}
```

`isDefault` and `lastUsedAt` (when a run of the task last started, or `null`) are Scrape.ly additions; list items carry them too, plus `title`.

```api-test
{ "method": "GET", "path": "/v2/actor-tasks/{taskId}" }
```

## Update task

```
PUT /v2/actor-tasks/{taskId}
```

Send any of `name`, `title`, `input`, `options`, `seoTitle`, `seoDescription`, and `isDefault`. The same name and memory rules as create apply.

While a task is [published](#publish-a-task), its input cannot change: an input edit returns `409 task-published-input-immutable`. Unpublish the task, edit it, then publish it again.

```bash
curl -X PUT https://api.scrape.ly/v2/actor-tasks/tAsKiDeXaMpLe123 \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Nightly scrape", "input": { "url": "https://example.com/v2" }}'
```

## Delete task

```
DELETE /v2/actor-tasks/{taskId}
```

Returns `204 No Content`.

```bash
curl -X DELETE https://api.scrape.ly/v2/actor-tasks/tAsKiDeXaMpLe123 \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

## Task input

```
GET /v2/actor-tasks/{taskId}/input
PUT /v2/actor-tasks/{taskId}/input
```

`GET` returns the saved input as **raw JSON**, not wrapped in `data` (`{}` if there is none). `PUT` replaces it; the body is the input object (9 MB limit, else `400 input-too-large`). The response is the stored input object.

```bash
curl -X PUT https://api.scrape.ly/v2/actor-tasks/tAsKiDeXaMpLe123/input \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "maxResults": 100}'
```

```api-test
{ "method": "GET", "path": "/v2/actor-tasks/{taskId}/input" }
```

## List task runs

```
GET /v2/actor-tasks/{taskId}/runs
```

| Query param | Description |
| --- | --- |
| `status` | Filter by a single run status (`TIMED-OUT` with a hyphen). |
| `limit` / `offset` / `desc` | Pagination. Default `limit` is `1000`; `desc` defaults to newest first. |

Each run item also carries `taskName`.

```api-test
{ "method": "GET", "path": "/v2/actor-tasks/{taskId}/runs?limit=5" }
```

## Run task

```
POST /v2/actor-tasks/{taskId}/runs
```

Starts a run with the task's saved input. A non-empty JSON body is **merged over** the saved input, field by field at the top level (send input fields, not `{ "input": ... }`). A `webhooks` key in the body is read as ad-hoc webhooks and is not added to the input.

Returns `201` with the run object and a `Location` header.

| Query param | Description |
| --- | --- |
| `build` | A build ID, tag, version (such as `0.1`), or build number (overrides the task options). A tag or version picks the newest matching build; builds started in the same second go to the higher build number. Ignored on someone else's Actor, which always runs the [User Build](https://scrape.ly/docs/publishing.md#user-build). |
| `memory` | Memory in MB, a power of two. |
| `timeout` | Timeout in seconds. |
| `maxItems` | Max dataset items. |
| `maxTotalChargeUsd` | Max pay-per-event charge. |
| `restartOnError` | Restart on error. |
| `waitForFinish` | Seconds to wait for a terminal status (up to 60; the wait stops at **45 seconds**, see [Waiting for runs and builds](https://scrape.ly/docs/api-overview.md#waiting-for-runs-and-builds)). |
| `idempotencyKey` | Same as the `Idempotency-Key` header. |
| `forcePermissionLevel` | `LIMITED_PERMISSIONS` or `FULL_PERMISSIONS`. An invalid value returns `400 invalid-force-permission-level`. |
| `webhooks` | Base64-encoded ad-hoc webhook array. |

Unset options fall back to the task's `options`, then the Actor's defaults, then `latest`, 2048 MB, and 3600 seconds.

```bash
curl -X POST "https://api.scrape.ly/v2/actor-tasks/tAsKiDeXaMpLe123/runs?waitForFinish=30" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"maxResults": 20}'
```

Starting a task run can fail with the same errors as [Start a run](https://scrape.ly/docs/api-actors.md#start-a-run), including `429 concurrency-limit-exceeded` and `403 full-permission-actor-not-approved` (see [Permission approvals](https://scrape.ly/docs/api-actors.md#permission-approvals)).

To make retries safe, send an `Idempotency-Key` header (or the `idempotencyKey` query parameter). A repeat with the same key returns `201` with the original run at once and ignores `waitForFinish`. While the first request is still starting the run, a repeat gets `409 idempotency-key-in-progress`; a key already used for a different request returns `422 idempotency-key-reused`. See [Idempotent run starts](https://scrape.ly/docs/api-actors.md#idempotent-run-starts).

## Run task synchronously

```
POST /v2/actor-tasks/{taskId}/run-sync
GET  /v2/actor-tasks/{taskId}/run-sync
```

Starts a task run and waits for it to finish. On success, returns the **`OUTPUT` key-value record as raw JSON** with status `201`, not the run object and not a `data` envelope.

- `POST` merges the body over the saved task input. `GET` uses the saved input only.
- `outputRecordKey` selects a different key-value store record (default `OUTPUT`).
- Query parameters: `build` (owner only, see above), `memory`, `timeout`, `maxItems`, `forcePermissionLevel`, and `webhooks` (base64 only; a body `webhooks` key is not read here). `maxTotalChargeUsd` and `restartOnError` come from the task's options.
- `timeout` sets the run's timeout. The request waits for `timeout` seconds or **45 seconds**, whichever is shorter. Without `timeout`, the run keeps the task's timeout and may continue after the request gives up.

| Status | Type | Meaning |
| --- | --- | --- |
| 201 | — | The run succeeded. The body is the output record. |
| 400 | `run-failed` | The run ended in a status other than `SUCCEEDED`. |
| 404 | `record-not-found` | The run succeeded but the output record doesn't exist. |
| 408 | `request-timeout` | The run didn't finish in time. It keeps going; the message includes the run ID so you can keep polling it. |

These status codes differ from the Actor endpoints, which return `200`, `{}` for a missing record, and `408 run-timeout-exceeded`.

On a `408`, wait for the run with [GET /v2/actor-runs/{runId}?waitForFinish=60](https://scrape.ly/docs/api-runs.md#get-run) instead of calling `run-sync` again, which starts another run.

```bash
curl -X POST https://api.scrape.ly/v2/actor-tasks/tAsKiDeXaMpLe123/run-sync \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
```

### Get dataset items synchronously

```
POST /v2/actor-tasks/{taskId}/run-sync-get-dataset-items
GET  /v2/actor-tasks/{taskId}/run-sync-get-dataset-items
```

Same wait rules as `run-sync`. On success, returns the run's **dataset items** with status `201`. It accepts every [Get dataset items](https://scrape.ly/docs/api-datasets.md) parameter (`format`, `fields`, `offset`, `limit`, `desc`, and so on) and sends the `X-Apify-Pagination-*` headers.

## Last task run

```
GET /v2/actor-tasks/{taskId}/runs/last
```

The most recent run of the task. Optional `status` and `waitForFinish` (up to 60; the wait stops at 45 seconds). If there's no matching run, returns `404 run-not-found`.

### Last-run storage shortcuts

Every shortcut accepts `status` and `waitForFinish`.

| Endpoint | Description |
| --- | --- |
| `GET /v2/actor-tasks/{taskId}/runs/last/log` | Plain-text log. |
| `GET /v2/actor-tasks/{taskId}/runs/last/dataset` | Default dataset. |
| `GET /v2/actor-tasks/{taskId}/runs/last/dataset/items` | Dataset items. Takes every [Get dataset items](https://scrape.ly/docs/api-datasets.md) parameter. |
| `GET /v2/actor-tasks/{taskId}/runs/last/key-value-store` | Default key-value store. |
| `GET /v2/actor-tasks/{taskId}/runs/last/key-value-store/keys` | List keys (`limit`, default `1000`, and `exclusiveStartKey`). |
| `GET /v2/actor-tasks/{taskId}/runs/last/key-value-store/records/{recordKey}` | Get a record (`OUTPUT` for named output), or `404 record-not-found`. |
| `GET /v2/actor-tasks/{taskId}/runs/last/request-queue` | Default request queue. |
| `GET /v2/actor-tasks/{taskId}/runs/last/request-queue/requests` | List requests (`limit`, default `1000`, and `offset`). |
| `GET /v2/actor-tasks/{taskId}/runs/last/request-queue/head` | Queue head (`limit`, default `100`). |

```bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/actor-tasks/tAsKiDeXaMpLe123/runs/last/key-value-store/records/OUTPUT?status=SUCCEEDED"
```

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

Run objects, abort, resurrect, and logs for a known `runId` are documented under [Actor runs](https://scrape.ly/docs/api-runs.md).

## Publish a task

```
POST /v2/actor-tasks/{taskId}/publish
POST /v2/actor-tasks/{taskId}/unpublish
```

Publishing turns a task into a public **example** for its Actor. The example gets its own page in the Store, under the Actor's Examples tab, showing the task's input. Values of input fields the Actor's schema marks as secret are masked on that page.

You can publish a task when:

- you own both the task and the Actor,
- the Actor is public, not deprecated, and has a successful build,
- the task input is a non-empty object, and
- the [User Build](https://scrape.ly/docs/publishing.md#user-build) has a valid input schema (the default build when none is set).

The Actor can have up to 50 published tasks.

Optional publish body fields:

| Field | Type | Description |
| --- | --- | --- |
| `slug` | string | URL slug for the example page. Normalized to lowercase letters, digits, and hyphens; must keep at least one letter or digit. Defaults to the slug from the last publish, or one based on the task title or name. |
| `seoTitle` | string | Page title (max 255 characters). |
| `seoDescription` | string | Page description (max 5000 characters). |
| `useCase` | string | Short use-case label (max 100 characters). |

Both endpoints return the task under `data`. Unpublishing keeps the SEO fields, so you can publish again later. If you change the slug, the old slug redirects to the new one.

| Status | Type | Meaning |
| --- | --- | --- |
| 400 | `invalid-input` | A field is too long, or the slug has no letters or digits. |
| 400 | `actor-not-published` / `actor-deprecated` / `actor-not-built` | The Actor doesn't meet the requirements. |
| 400 | `task-input-incomplete` | The task input is empty. |
| 400 | `input-schema-missing` / `input-schema-invalid` | The User Build (or the default build when none is set) has no usable input schema. |
| 403 | `forbidden` | You don't own the task or the Actor. |
| 403 | `publish-limit-reached` | The Actor already has 50 published tasks. |

## Duplicate a task

```
POST /v2/actor-tasks/{taskId}/duplicate
```

Copies a task into your account, with its input and options. You can duplicate your own tasks and other users' published examples. When you copy someone else's example, secret input fields are left out of the copy. The new task gets a free name based on the original.

Returns `201` with the new task and a `Location` header. If you are at your task limit, returns `403 limit-reached`.

## Task webhooks

```
GET /v2/actor-tasks/{taskId}/webhooks
```

Lists your webhooks whose condition names this task. See [List webhooks of a task](https://scrape.ly/docs/api-webhooks.md#list-webhooks-of-a-task). To find the automations that use a saved input, call `GET /v2/automations?savedInputId={taskId}` (see [Automations API](https://scrape.ly/docs/api-automations.md#list-automations)).

## Public example tasks

```
GET /v2/acts/{actorId}/examples
GET /v2/acts/{actorId}/examples/{slug}
GET /v2/acts/{actorId}/examples/{slug}.md
```

Read the published examples of a public Actor. No authentication. `{actorId}` must be the Actor ID here, not `username~name`.

- The list returns `{ "data": { "total", "count", "items": [...] } }`, newest first. Each item has `id`, `name`, `title`, `publicSlug`, `seoTitle`, `seoDescription`, `useCase`, `publishedAt`, `actId`, `actName`, `actUsername`, and `actTitle`.
- A single example returns the same fields plus the masked `input` and `duplicateUrl`, the [duplicate](#duplicate-a-task) endpoint for that task.
- The `.md` variant returns the example as Markdown, which is handy for AI agents.

| Status | Type | Meaning |
| --- | --- | --- |
| 301 | — | The slug changed. Follow the redirect to the current slug. |
| 404 | `example-not-found` | No such example, or the Actor is not public. |
| 410 | `example-gone` | The example was unpublished. |

```api-test
{ "method": "GET", "path": "/v2/acts/{actorId}/examples" }
```

---

- Previous: [Actor runs](https://scrape.ly/docs/api-runs.md)
- Next: [Datasets](https://scrape.ly/docs/api-datasets.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
