Actors
Create, run, version, build, and publish Actors, plus CI and Store endpoints.
Create and manage Actors, versions, builds, and runs. All paths are relative to https://api.scrape.ly.
Actor IDs may be the opaque ID or the named ID username~actor-name. Public Actors can be read without a token. Private Actors need the owner's token. Builds and versions are owner only, also for public Actors.
Most Actor paths use /v2/actors. A few also answer under /v2/acts: GET /v2/acts/{actorId}, GET /v2/acts/{actorId}/picture, and GET /v2/acts/{actorId}/webhooks. The example-task endpoints under /v2/acts/{actorId}/examples are covered in Actor tasks.
List Actors
/v2/actorsRequires authentication. With my=1, returns Actors you own. Otherwise returns Actors you created or have run.
| Query param | Type | Description |
|---|---|---|
my | boolean | 1 / true: only Actors you own. Default is created-or-used. |
sortBy | string | createdAt (default), stats.lastRunStartedAt, name, or totalRuns. Other values fall back to createdAt. |
search | string | Filter by Actor name. |
status | string | Filter by the status of your last run of that Actor (for example SUCCEEDED). |
pricingModel | string | PAY_PER_EVENT or FREE. Any other model returns 400 invalid-pricing. |
hasSuccessfulBuild | boolean | When listing created-or-used (my unset), only Actors with a successful build. Applies to your own Actors only; Actors you've used are always included. |
limit / offset / desc | number / boolean | Pagination. Default limit is 1000. |
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/actors?my=1&sortBy=totalRuns&desc=1&limit=20"/v2/actors?my=1&limit=5Create Actor
/v2/actorsAccepts a user API key, an OAuth token, or a full-permission scoped token. Limited scoped tokens get 403 insufficient-permissions. Counts against your plan's Actor limit. Returns 201 and a Location header.
| Body field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique Actor name (max 40 characters). |
title | string | No | Display title. |
description | string | No | Short description (max 500 characters). |
seoTitle / seoDescription | string | No | SEO metadata for the public page. |
isPublic | boolean | No | Whether the Actor is listed in the Store. |
versions | array | No | Initial versions (see Versions). |
defaultRunOptions | object | No | Default build, memoryMbytes, timeoutSecs, maxItems, restartOnError, forcePermissionLevel, minimalMaxTotalChargeUsd. |
exampleRunInput | object | No | Example input (body, contentType). |
pricingInfos | array | No | Pricing configuration. |
actorStandby | object | No | Standby settings. |
categories | string[] | No | Store categories. |
iconWebp | string | No | Icon as base64 WebP or a data:image/...;base64, URL (PNG and JPEG are converted to WebP). Responses never include the icon itself; they return pictureUrl. Sending the current pictureUrl back leaves the icon unchanged. |
hideSourceFiles | boolean | No | Hide source files, the Dockerfile and build logs from non-owners. Defaults to true. Hiding is always allowed, also while the Actor is public. Setting false on a hidden Actor needs confirmPublicSourceFiles. |
confirmPublicSourceFiles | string | With hideSourceFiles: false | The Actor name, to confirm that you want to make the source public. Not stored. |
| Status | Type | Meaning |
|---|---|---|
| 400 | invalid-input | Name over 40 characters, description over 500, or defaultRunOptions.timeoutSecs out of range. |
| 400 | profanity-detected | The name, title, or description contains blocked language. |
| 400 | source-visibility-confirmation-required | hideSourceFiles: false was sent without confirmPublicSourceFiles set to the Actor name. |
| 403 | limit-reached | You already have as many Actors as your plan allows. |
curl -X POST https://api.scrape.ly/v2/actors \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-actor",
"title": "My Actor",
"isPublic": false,
"versions": [{
"versionNumber": "0.1",
"buildTag": "latest",
"sourceType": "SOURCE_FILES",
"sourceFiles": [
{ "name": "main.js", "format": "TEXT", "content": "import { Actor } from \"scrapely\";" }
]
}]
}'Get Actor
/v2/actors/{actorId}/v2/acts/{actorId}Public Actors are readable without a token. Private Actors need the owner. Authenticated callers get userStats for their own use of the Actor. Scoped run tokens with limited permissions get the non-owner view, also for the runner's own Actors.
Usage numbers are owner only too: for non-owners and anonymous callers, the only count in stats is totalUsers30Days (monthly active users: every distinct user with a run in the last 30 days); totalRuns, totalUsers, totalUsers7Days, totalUsers90Days, totalMetamorphs and totalBookmarks are null, and your own run count is in userStats. Every caller, the owner included, also gets stats.successRate: the percentage of the Actor's finished runs (succeeded, failed or timed out; aborted and unfinished runs are left out) that succeeded, and stats.successRateWindow, which says which runs it covers. Every run counts, whoever started it. The window is the runs started since the Actor's last successful build ("currentBuild"); editing the description, settings or price does not reset it, a new build does. If the current build has fewer than 10 finished runs, the rate covers the last 30 days across builds ("last30Days"). If that also has fewer than 10, both fields are null ("Not enough runs yet"). No run counts are published with it.
Build info is owner only, also for public Actors. Non-owners don't get versions, taggedBuilds, defaultNonOwnerBuild, defaultRunOptions.build, stats.totalBuilds, hasSuccessfulBuild, or standby config. Their defaultBuildActorDefinition comes from the User Build and holds only what's needed to run the Actor: actorSpecification, name, title, description, readme, input/inputSchema, output, storages, the memory limits, usesStandbyMode, webServerSchema, and webServerMcpPath. Build details such as version, buildTag, changelog, dockerfile, and environmentVariables are left out. Source files are left out when hideSourceFiles is set; when they're shown, CHANGELOG.md is still left out.
The input schema lives on the build's Actor definition (defaultBuildActorDefinition.input or .inputSchema), not on a separate schema endpoint. Owners get the default build's definition; everyone else gets the User Build's.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/actors/janedoe~my-actor/v2/actors/janedoe~my-actorGet Actor icon and picture
/v2/actors/{actorId}/picture.webp?v={hash}/v2/actors/{actorId}/icon/v2/actors/{actorId}/picture/v2/acts/{actorId}/pictureNo authentication. API objects never embed the icon. Actors, Store items, bookmarks and search results carry pictureUrl, and runs and tasks carry actorPictureUrl (null when the Actor has no icon). Use the URL directly in an <img> tag.
pictureUrlis/picture.webp?v={hash}, where the hash identifies the icon's content. It works for any Actor whose icon has that hash, and returns the raw image withCache-Control: public, max-age=31536000, immutableandETag: "{hash}". A changed icon gets a new URL./iconand/picturewithoutvwork for public Actors only; anything else returns404 actor-not-found./iconreturns JSON ({ "data": { "iconWebp": "..." } }, with the icon as a base64 string)./picturereturns the raw image bytes, cached for 5 minutes.- An Actor without an icon returns
404 picture-not-found.
curl -o icon.webp https://api.scrape.ly/v2/actors/janedoe~my-actor/picture/v2/actors/janedoe~my-actor/iconUpdate Actor
/v2/actors/{actorId}Owner only. Same fields as create (all optional), plus:
| Body field | Type | Description |
|---|---|---|
actorPermissionLevel | string | LIMITED_PERMISSIONS or FULL_PERMISSIONS. Cannot change after the Actor is public. |
taggedBuilds | object | Map of tag → { "buildId": "..." }; null removes the tag. The target must be a SUCCEEDED build of this Actor, else 404 not-found (no such build) or 400 invalid-input. Responses return { "buildId", "buildNumber", "finishedAt" } per tag. Owner only. |
defaultNonOwnerBuild | string | null | The User Build: the build tag or number every other user runs. Must be a SUCCEEDED build, else 400 invalid-input. A version number such as 0.1 is saved as the build number it resolves to, so later builds of that version don't reach users. Empty or null clears it, so users run defaultRunOptions.build. Can change any time, also after the Actor is public. |
isDeprecated / isUnderMaintenance | boolean | Lifecycle flags. |
userMaintenanceBadge / userDeprecatedBadge | boolean | Display-only badges. |
allowsAgenticUsers | boolean | Whether AI agents may find the Actor in the agent Store format and run it with agent payments (x402). Defaults to true. Non-boolean values return 400 invalid-input. Returned on GET /v2/actors/{actorId}. |
Publishing (isPublic: true) requires a name, a description, a successful build, an input schema on the User Build (the default build when none is set), and a Scrape.ly base image (Node.js 20, 22, 24, or 26, or Python 3.11 or newer). A request that doesn't meet these returns 400 publish-validation.
To make the source of a hidden Actor public, send the Actor name as confirmation. You can hide it again at any time with { "hideSourceFiles": true }.
curl -X PUT https://api.scrape.ly/v2/actors/janedoe~my-actor \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "hideSourceFiles": false, "confirmPublicSourceFiles": "my-actor" }'Delete Actor
/v2/actors/{actorId}Owner only. Returns 204 No Content.
curl -X DELETE https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx \
-H "Authorization: Bearer $SCRAPELY_TOKEN"Saved input and run options (removed)
GET|PUT /v2/actors/{actorId}/input
GET|PUT /v2/actors/{actorId}/run-optionsThese endpoints were removed and answer 410 endpoint-removed. Your saved input values and run options for an Actor are saved inputs now: list them with GET /v2/actors/{actorId}/saved-inputs (your default saved input has isDefault: true) and read or change one with the task endpoints.
Start a run
/v2/actors/{actorId}/runsStarts a run and returns 201 with the run object, or 202 if the run is queued. Both set Location to /v2/actor-runs/{runId}. The JSON body is the input object at the top level. If the body isn't a JSON object, the run starts without input.
Any authenticated user can run a public Actor; private Actors are owner-only. Full-permission Actors may need approval.
| Query param | Type | Description |
|---|---|---|
build | string | A build ID, tag, version (such as 0.1), or build number. A tag or version picks the newest matching build; builds started in the same second go to the higher build number. Owner only: for other users build is ignored and the run uses the User Build. Default: the Actor's defaultRunOptions.build, or latest. |
memory | number | Memory in MB. A power of two within your account limits. Default: the Actor's default, or 2048. |
timeout | number | Timeout in seconds. Default: the Actor's default, or 3600. |
maxItems | number | Max dataset items. |
maxTotalChargeUsd | number | Max pay-per-event charge in USD. Default: the Actor's minimalMaxTotalChargeUsd, if set. |
restartOnError | boolean | Restart the container on error. |
waitForFinish | number | Seconds to wait for a terminal status, from 0 to 60. The wait stops at 45 seconds (see Waiting for runs and builds). If the run is still going, you get the current run object. Ignored when the run is queued. |
idempotencyKey | string | Same as the Idempotency-Key header. See Idempotent run starts. |
forcePermissionLevel | string | LIMITED_PERMISSIONS or FULL_PERMISSIONS. |
webhooks | string | Base64-encoded JSON array of ad-hoc webhook definitions. |
You can also put a webhooks array in the JSON body. Ad-hoc webhooks are bound to this run, count toward your webhook limit while it is active, and are deleted after they fire. See Ad-hoc webhooks.
A queued 202 response keeps the run under data and adds queue details:
{
"data": { "id": "a7XyZ8kLmN2pQ4rSt", "status": "READY" },
"meta": {
"queuePosition": 3,
"estimatedWaitSecs": 45,
"reason": "concurrency"
}
}| Status | Type | Meaning |
|---|---|---|
| 400 | invalid-input | The input doesn't match the Actor's input schema. See Input validation. |
| 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 | invalid-memory / invalid-timeout | Memory is not a power of two within your limits, or the timeout is not a positive integer. |
| 400 | invalid-max-total-charge | maxTotalChargeUsd is not a valid amount. |
| 400 | limit-exceeded | The run would exceed your total memory limit. |
| 400 | unsupported-pricing | The Actor uses a pricing model that can no longer be run. |
| 402 | payment-required | Not enough credit to reserve for the run. |
| 403 | forbidden | Private Actor you don't own. |
| 403 | full-permission-actor-not-approved | See Permission approvals. |
| 409 | idempotency-key-in-progress | A start with the same Idempotency-Key is still in progress. Retry shortly. |
| 422 | idempotency-key-reused | The Idempotency-Key was already used for a different request (another Actor, task, or build target). |
| 429 | concurrency-limit-exceeded / run-capacity-check-busy | You're at your concurrent run limit, or another run request from your account is being evaluated. Retry shortly. |
curl -X POST "https://api.scrape.ly/v2/actors/janedoe~my-actor/runs?memory=2048&timeout=3600" \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "maxResults": 50}'The returned run's options include build (only on runs of your own Actor), timeoutSecs, memoryMbytes, diskMbytes (twice the memory), maxItems, maxTotalChargeUsd, and restartOnError, the same as Get run returns.
Secret input fields are encrypted before the run is stored. See Secret input fields.
To inspect, abort, or fetch logs for the returned id, see Actor runs.
Input validation
Before a run starts, its input is checked against the input schema of the build it runs. Schema defaults are filled in first. Input that doesn't match returns 400 invalid-input, and no run is created or billed. The same check applies to synchronous runs, task runs and scheduled runs. A rejected scheduled run is recorded in the schedule log.
{
"error": {
"type": "invalid-input",
"message": "Input is not valid: Field input.maxItems must be >= 1",
"data": {
"errors": [{ "field": "input.maxItems", "message": "must be >= 1" }]
}
}
}The check covers required fields, types, enum, minimum/maximum, minLength/maxLength, pattern, and array minItems/maxItems and items. Input keys that the schema doesn't list are allowed, unless the schema sets "additionalProperties": false. An optional field may be null. Input that isn't a JSON object, and Actors without an input schema, are not checked.
To check input without starting a run, use Validate input.
Validate input
/v2/actors/{actorId}/validate-inputValidates the JSON body against the input schema, using the same rules as a run start, and starts nothing. It returns 200 { "valid": true }, or 400 invalid-input with the field errors shown above. Use the build query parameter to pick the build (owner only, like on Start a run). Other users always validate against the User Build. An unknown build returns 404 record-not-found.
curl -X POST "https://api.scrape.ly/v2/actors/janedoe~my-actor/validate-input" \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "maxResults": 50}'The clients call this endpoint from client.actor(id).validateInput(input) (JavaScript) and client.actor(id).validate_input(input) (Python).
Idempotent run starts
A start request can time out on the network after the run was created. Retrying it then starts a second run, which is billed too. To make retries safe, send an Idempotency-Key header (or the idempotencyKey query parameter) with a value you generate once per intended run:
curl -X POST "https://api.scrape.ly/v2/actors/janedoe~my-actor/runs" \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Idempotency-Key: nightly-crawl-2026-08-14" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'- The key is 1 to 255 printable ASCII characters, without spaces. Anything else returns
400 invalid-input. - Keys are scoped to your account and remembered for 24 hours after the run starts.
- A repeated request with the same key returns
201with the original run instead of starting a new one.waitForFinishworks on the repeat too. The body and query parameters of the repeat are ignored. - If the first request is still starting the run, the repeat gets
409 idempotency-key-in-progress. Retry after a moment. - Reusing a key for a different request (another Actor, task, or build target) returns
422 idempotency-key-reused. - If the first request fails before a run exists (a validation error, a limit, or no balance), the key is released, so a retry with the same key can start the run.
Without the header, every request starts a new run, so parallel starts of the same Actor and input all run.
The header is also read on POST /v2/actor-tasks/{taskId}/runs and POST /v2/actors/{actorId}/builds:
- A task-run replay returns the original run at once and ignores
waitForFinish. - A build replay returns
201with{ "data": build }, withoutmetaand without waiting. - On GitHub
pushdeliveries the key is ignored.
The JavaScript client retries 5xx responses on its own. To send the header from it, use the requestInterceptors option.
Run Actor synchronously
/v2/actors/{actorId}/run-sync/v2/actors/{actorId}/run-syncStarts a run and waits up to 45 seconds for it to finish (see Waiting for runs and builds). On success, returns 200 OK with the OUTPUT key-value record as raw JSON, not the { "data": ... } envelope and not the run object. If the record is missing, the body is {}.
POST: the body is the input object, as for Start a run.GET: starts a run without input.outputRecordKeyselects a different key-value store record (defaultOUTPUT).- Takes the same query parameters as Start a run except
waitForFinish.maxTotalChargeUsdhas no default here. - The run's own timeout is capped at 300 seconds. Without
timeout, it's the Actor's default timeout or 300, whichever is lower.
| Status | Type | Meaning |
|---|---|---|
| 200 | — | The run succeeded. The body is the output record. |
| 400 | run-failed | The run ended in a status other than SUCCEEDED. |
| 408 | run-timeout-exceeded | The run did not finish within 45 seconds. It keeps going; the message includes its run ID. |
The same start errors as Start a run also apply.
On a 408, don't call run-sync again: that starts another run. Take the run ID from the message and wait with GET /v2/actor-runs/{runId}?waitForFinish=60, then read its OUTPUT record or dataset. For jobs that regularly take longer than 45 seconds, start the run asynchronously instead.
curl -X POST https://api.scrape.ly/v2/actors/janedoe~my-actor/run-sync \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'Get dataset items synchronously
/v2/actors/{actorId}/run-sync-get-dataset-items/v2/actors/{actorId}/run-sync-get-dataset-itemsSame wait rules as run-sync (max 45 seconds, then 408). On success, returns 200 OK with the run's dataset items (a JSON array by default, not wrapped in data).
It accepts every parameter of Get dataset items, such as format, fields, omit, offset, limit, and desc, so ?format=csv returns CSV. Responses include the X-Apify-Pagination-Offset, X-Apify-Pagination-Limit, X-Apify-Pagination-Count, X-Apify-Pagination-Total, and X-Apify-Pagination-Desc headers.
GET starts a run without input; POST sends the input object as the body.
curl -X POST "https://api.scrape.ly/v2/actors/janedoe~my-actor/run-sync-get-dataset-items?format=csv" \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'List Actor runs
/v2/actors/{actorId}/runsYour runs of this Actor. Works for public Actors you don't own too, but only lists your own runs. Pagination (default limit 1000) plus:
| Query param | Description |
|---|---|
status | One status or a comma-separated list, for example RUNNING,SUCCEEDED. Use TIMED-OUT (hyphen). |
startedAfter / startedBefore | ISO 8601 timestamps. |
sortBy | startedAt (default), finishedAt, status, duration, or usage. |
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/runs?status=SUCCEEDED&limit=10&desc=1"/v2/actors/{actorId}/runs?limit=5&desc=1To list all your runs across Actors, use GET /v2/actor-runs.
Run statistics
/v2/actors/{actorId}/runs/statsSummarizes your last 100 runs of the Actor in a date window. Works for public Actors and your own private ones.
| Query param | Description |
|---|---|
startDate | Start of the window (default: 30 days ago). |
endDate | End of the window (default: now). |
{
"data": {
"totalRuns": 42,
"statusCounts": { "RUNNING": 1, "SUCCEEDED": 38, "FAILED": 2, "ABORTED": 1, "TIMED-OUT": 0 },
"dailyStatuses": [],
"metrics": {
"results": [],
"duration": [],
"datasetFields": [],
"kvStoreSize": [],
"requestsHandled": []
},
"summary": {
"totalResults": 12840,
"totalDuration": 5400,
"maxCpuUsage": 98.5,
"maxMemoryUsage": 812,
"totalDatasetItems": 12840,
"totalRequests": 13010,
"totalComputeUnits": 1.25,
"durationPerResult": 0.42
},
"dateRange": { "start": "2026-07-15T00:00:00.000Z", "end": "2026-08-14T15:00:00.000Z" }
}
}Each metrics series is a list of { runId, startedAt, value } points.
/v2/actors/{actorId}/runs/statsLast run
/v2/actors/{actorId}/runs/lastThe most recent run you started on this Actor. Optional status (for example SUCCEEDED) and waitForFinish (up to 60, the wait stops at 45 seconds). If no run matches, returns 404 run-not-found.
Last-run storage shortcuts
These find the last run and read its default storages, so you can get results without looking up a run ID first. It's the simplest way to consume a scheduled Actor's output: point a report at the shortcut and it always reads the newest data.
Add ?status=SUCCEEDED to skip failed or aborted runs. Without it, the shortcut uses the most recent run whatever its outcome, which may have an empty or partial dataset.
| Endpoint | Description |
|---|---|
GET /v2/actors/{actorId}/runs/last/log | Plain-text log. |
GET /v2/actors/{actorId}/runs/last/dataset | Default dataset. |
GET /v2/actors/{actorId}/runs/last/dataset/items | Dataset items. Takes every Get dataset items parameter, including format. |
GET /v2/actors/{actorId}/runs/last/key-value-store | Default key-value store. |
GET /v2/actors/{actorId}/runs/last/key-value-store/keys | List keys (limit, default 1000, and exclusiveStartKey). |
GET /v2/actors/{actorId}/runs/last/key-value-store/records/{recordKey} | Get a record (use OUTPUT for named output). Missing records return 404 record-not-found. |
GET /v2/actors/{actorId}/runs/last/request-queue | Default request queue. |
GET /v2/actors/{actorId}/runs/last/request-queue/requests | List requests (limit, offset). |
GET /v2/actors/{actorId}/runs/last/request-queue/head | Queue head (limit, default 100). |
The storage objects these shortcuts return are summaries, and their item and request counts are not filled in. For real counts, read the storage by ID: defaultDatasetId and the others are on the run object.
Read the named output of the last successful run:
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/actors/janedoe~my-actor/runs/last/key-value-store/records/OUTPUT?status=SUCCEEDED"Fetch the newest results as CSV:
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/actors/janedoe~my-actor/runs/last/dataset/items?status=SUCCEEDED&format=csv" \
-o latest.csvHandle the 404 in a report or dashboard rather than assuming data always exists.
/v2/actors/janedoe~my-actor/runs/last/v2/actors/janedoe~my-actor/runs/last/dataset/items?status=SUCCEEDED&limit=10List tasks for an Actor
/v2/actors/{actorId}/tasksReturns your saved tasks for this Actor, also for public Actors you don't own. Default limit is 20, newest first. See Actor tasks.
/v2/actors/{actorId}/tasksList Actor webhooks
/v2/actors/{actorId}/webhooks/v2/acts/{actorId}/webhooksYour webhooks whose condition targets this Actor. Owner only; limited scoped tokens are rejected. Paginated with limit (default and max 1000), offset, and desc (default true). To create or edit webhooks, see Webhooks.
/v2/actors/{actorId}/webhooksVersions
A version holds the source of your Actor at a major.minor number, for example 0.1. Builds compile a version into a runnable image, so the usual deployment sequence is: create or update a version, then start a build from it.
Versions are owner only, also for public Actors: only the owner can read, create, update, or delete them, and other users get 403.
Source types:
| Source type | Where the code comes from |
|---|---|
SOURCE_FILES | Files supplied inline in the request |
GIT_REPO | A Git repository URL, cloned at build time |
TARBALL | A tarball URL, downloaded at build time |
GITHUB_GIST | A GitHub gist |
| Endpoint | Description |
|---|---|
GET /v2/actors/{actorId}/versions | List versions. |
GET /v2/actors/{actorId}/versions/{versionNumber} | Get one version, including the contents of its sourceFiles. Owner only. |
POST /v2/actors/{actorId}/versions | Push a version: 201 + Location. If the version number already exists, it is replaced, including its environment variables. |
PUT /v2/actors/{actorId}/versions/{versionNumber} | Update the fields you send. A file sent without content keeps its stored content. |
DELETE /v2/actors/{actorId}/versions/{versionNumber} | Delete: 204. |
POST /v2/actors/{actorId}/versions/fork | Copy a version into a new number. An existing target number returns 409 version-exists. |
GET /v2/actors/{actorId}/versions/{versionNumber}/source-files | The version including file contents. |
PATCH /v2/actors/{actorId}/versions/{versionNumber}/source-files | Owner only. Delta update: { "updatedFiles": [...], "deletedFiles": [...] }. Returns { "data": { "uploaded": n, "deleted": n } }. |
The versions list returns { "data": { "total": number, "items": [...] } }. It returns every version and does not accept offset or limit.
Push a version with inline source files:
curl -X POST https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/versions \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"versionNumber": "0.1",
"buildTag": "latest",
"sourceType": "SOURCE_FILES",
"sourceFiles": [
{ "name": "main.js", "format": "TEXT", "content": "import { Actor } from \"scrapely\";" },
{ "name": "scrapely.json", "format": "TEXT", "content": "{ \"actorSpecification\": 1 }" }
]
}'Each entry in sourceFiles needs name, format, and content. format is TEXT, BASE64 (for binary files), or ZIP (a base64 zip archive that the server unpacks into individual files).
Size limits for uploaded source:
- The JSON request body can be at most 10 MB.
- All
ZIPentries of one request together may unpack to at most 10,000 files and 48 MB. Past that, the request returns413 payload-too-large. Use a Git repository or a tarball URL for larger sources. - A tarball URL can be up to 500 MB.
- The source a version stores, whatever its type, can hold at most 10,000 files, 128 MB in total, and 64 MB per file; a larger one returns
payload-too-large. Keep large data out of the Actor's folder: download it in the Dockerfile or at run time.
A read-modify-write is safe: GET a version, change a field such as envVars, and PUT the object back, and the source stays as it was. PUT replaces the whole file list when you send sourceFiles, so leave out the files you want to delete. If every file in the list arrives with an empty content, the stored contents are kept; to empty a single file, send it next to files that have content.
Point a version at a Git repository instead:
curl -X POST https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/versions \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"versionNumber": "0.2",
"buildTag": "latest",
"sourceType": "GIT_REPO",
"gitRepoUrl": "https://github.com/janedoe/my-actor#main"
}'For a private repository, add a deployment key.
If your GitHub integration is connected, linking a version to a GitHub repository also adds a push webhook to that repository, so each push to the linked branch rebuilds the version. This applies to POST /v2/actors, PUT /v2/actors/{actorId} with versions, POST .../versions, and a PUT .../versions/{versionNumber} that changes sourceType, gitRepoUrl, or gitBranch. Requests made with scoped tokens don't register webhooks. Registration is best effort: the version is saved even if GitHub refuses the webhook. See CI webhooks.
Once a version has many files, replacing all of them on every deploy is wasteful. PATCH .../source-files applies a delta instead:
curl -X PATCH https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/versions/0.1/source-files \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"updatedFiles": [
{ "name": "main.js", "format": "TEXT", "content": "// updated" }
],
"deletedFiles": ["old-helper.js"]
}'Fork body:
| Field | Type | Required | Description |
|---|---|---|---|
newVersionNumber | string | Yes | major.minor, for example "1.0". |
sourceVersionNumber | string | No | Version to copy (defaults to the first version). |
copyEnvVars | boolean | No | Copy non-secret env vars (default false). |
/v2/actors/{actorId}/versionsEnvironment variables
Environment variables belong to a version, not to the Actor, so a variable added to 0.1 is not visible to 0.2. They are injected into runs, and into builds when applyToBuild is set.
| Body field | Type | Description |
|---|---|---|
name | string | Required. Variable name. |
value | string | Required. Variable value. |
isSecret | boolean | Encrypt at rest and hide the value in API responses. |
applyToBuild | boolean | Also inject during the Docker build, not just at run time. |
Once isSecret is set, the value is never returned again: responses show "value": "". Store the plaintext in your own secret manager if you need to read it back.
Any signed-in user can list the environment variables of a public Actor and read the non-secret values. Mark every credential as secret.
Set applyToBuild only for values the build itself needs, such as a private registry token. A runtime credential does not belong in the build, where it can end up in an image layer.
| Endpoint | Description |
|---|---|
GET /v2/actors/{actorId}/versions/{versionNumber}/env-vars | List. |
POST /v2/actors/{actorId}/versions/{versionNumber}/env-vars | Create or overwrite a variable by name: 201. Body: { "name", "value", "isSecret", "applyToBuild" }. |
GET /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName} | Get one, or 404 env-var-not-found. |
PUT /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName} | Update. |
DELETE /v2/actors/{actorId}/versions/{versionNumber}/env-vars/{envVarName} | Delete: 204. |
curl -X POST https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/versions/0.1/env-vars \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "MY_TOKEN", "value": "secret-value", "isSecret": true }'/v2/actors/{actorId}/versions/0.1/env-varsBuilds
A build turns a version into a runnable image. Builds are addressed two ways: under an Actor (/v2/actors/{actorId}/builds) to list and start them, and under /v2/actor-builds/{buildId} to work with one build.
Builds, build logs, and build OpenAPI documents are owner only, also for public Actors. Everyone else gets 403 on the /v2/actors/{actorId}/builds… routes and 404 build-not-found on the /v2/actor-builds/{buildId}… routes, the same as for a build that doesn't exist. Other users run the User Build and read its schemas from Get Actor.
| Endpoint | Description |
|---|---|
GET /v2/actors/{actorId}/builds | List builds for an Actor. Owner only. Default limit 1000. Oldest first by default; desc=1 returns newest first. Builds started in the same second are ordered by build number. |
GET /v2/actors/{actorId}/builds/preflight | Dockerfile checks before building. Query version required. Owner only. |
POST /v2/actors/{actorId}/builds | Start a build. Owner only; needs a positive balance. |
GET /v2/actors/{actorId}/builds/default | The build tagged latest, else the newest succeeded build, else the newest build of any status. Doesn't follow defaultRunOptions.build. Owner only. Optional waitForFinish (up to 60, the wait stops at 45 seconds). 404 build-not-found only if the Actor has no builds. |
GET /v2/actors/{actorId}/builds/{buildId}/openapi.json | OpenAPI document for a build. buildId may be default. Owner only. |
GET /v2/actor-builds | List your builds across Actors. Default limit 1000. Oldest first by default; desc=1 for newest first. |
GET /v2/actor-builds/{buildId} | Get a build. Owner only. Optional waitForFinish (up to 60, the wait stops at 45 seconds). |
GET /v2/actor-builds/{buildId}/openapi.json | OpenAPI document for a build. Owner only. |
GET /v2/actor-builds/{buildId}/log | Build log as plain text. Owner only. |
POST /v2/actor-builds/{buildId}/abort | Abort a running build. Owner only. Returns the build under data. |
DELETE /v2/actor-builds/{buildId} | Delete a build: 204. Owner only. The current User Build can't be deleted (400 invalid-input); set a different one first. |
Start a build
| Query param | Type | Required | Description |
|---|---|---|---|
version | string | Yes | Version number to build. |
tag | string | No | Build tag (defaults to the version's buildTag, or latest). |
waitForFinish | number | No | Seconds to wait, up to 60. The wait stops at 45 seconds. Ignored when the build is queued. |
idempotencyKey | string | No | Same as the Idempotency-Key header. A repeat with the same key returns 201 with the original build, without meta and without waiting. See Idempotent run starts. |
syncGitFromRemote | boolean | No | Pull the linked Git repo before building. The build then reads actor.json, the input schema, and the other schemas from the pulled commit, so they always match the code it builds. |
Optional JSON body: { "options": { "memoryMbytes", "timeoutSecs", "noCache" } }.
memoryMbytesdefaults to 4096 MB, or your account's build memory limit if that is lower. It must be a power of two, within your limit, and small enough to fit on a single worker.timeoutSecsdefaults to your account's maximum build timeout and cannot exceed it.
An immediately dispatched build returns 201; a queued build returns 202. Both include meta.warnings, meta.hasWarnings, and meta.guidance from the Dockerfile preflight. A queued response also includes meta.queuePosition, meta.estimatedWaitSecs, meta.timeoutAt, and meta.reason.
{
"data": { "id": "bUiLdIdExAmPlE123", "status": "READY" },
"meta": {
"warnings": [],
"hasWarnings": false,
"guidance": "Review the Dockerfile guidance before rebuilding.",
"queuePosition": 2,
"estimatedWaitSecs": 30,
"timeoutAt": 1786725000000,
"reason": "No server has sufficient capacity. Requested: 2048MB"
}
}The Location header points to /v2/actors/{actorId}/builds/{buildId}, which has no GET handler. Read the build from GET /v2/actor-builds/{buildId} instead, using data.id.
| Status | Type | Meaning |
|---|---|---|
| 400 | invalid-input | version is missing or unknown, or the memory or timeout is invalid. |
| 400 | limit-exceeded | Running builds and runs would exceed your total memory limit. |
| 402 | insufficient-balance | Your balance is not positive. |
| 403 | forbidden | You don't own the Actor. |
| 409 | idempotency-key-in-progress | A build with the same Idempotency-Key is still being started. Retry shortly. |
| 422 | idempotency-key-reused | The Idempotency-Key was already used for a different request (another Actor, task, or build target). |
| 429 | concurrent-builds-limit-exceeded | You are at your concurrent build limit. |
Requests that carry an X-GitHub-Event header other than push are acknowledged with 200 and { "data": { "ignored": true, "event": "..." } }, and no build starts.
A push event only builds when it matches the version. Otherwise the response is 200 with { "data": { "ignored": true, "event": "push", "reason": "...", "ref": "refs/heads/..." } }:
reason | Meaning |
|---|---|
repo-not-linked | The push came from a different repository than the version's gitRepoUrl. Renamed repositories still match. |
ref-not-linked | The push updated a branch or tag other than the version's branch (the #branch in gitRepoUrl, or gitBranch, or else the repository's default branch). |
ref-deleted | The push deleted the branch. |
source-not-git | The version isn't built from a Git repository. |
duplicate-push | GitHub delivered the same push again. buildId is the build the first delivery started. |
Requests without an X-GitHub-Event header, such as a curl from GitHub Actions, aren't filtered. See CI webhooks.
Start a build and capture the ID:
BUILD_ID=$(curl -sS -X POST \
"https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/builds?version=0.1&tag=latest" \
-H "Authorization: Bearer $SCRAPELY_TOKEN" | jq -r '.data.id')Because the build may be queued rather than dispatched, do not assume 201. Treat 202 as success and poll GET /v2/actor-builds/{buildId}?waitForFinish=60 until the status is terminal. Each wait stops at 45 seconds; a longer build still finishes, the response just returns first.
Run GET /v2/actors/{actorId}/builds/preflight?version=0.1 first to catch Dockerfile problems before spending build minutes.
Files under /tmp don't survive the build's layer cache. To keep rebuilds working, the step that first copies into /tmp (COPY or ADD) and every step after it in that stage always rebuild, so those steps never come from the cache. A RUN step that writes into /tmp for a later RUN isn't covered: the later step can fail on a cached rebuild. Copy build inputs somewhere else, such as /usr/src/app, and do the whole /tmp job inside one RUN.
/v2/actors/{actorId}/builds?limit=5&desc=1/v2/actors/{actorId}/builds/default/v2/actor-builds/{buildId}Get build log
/v2/actor-builds/{buildId}/logReturns the build log as plain text (text/plain; charset=utf-8). The stored log keeps the newest 1 MB of output; older lines are dropped with a note. Build logs are kept for 3 days after the build finishes.
A build that prints more than 64 MB of output is stopped and fails with [Scrapely Build] Error: the build printed more than 64 MB of output and was stopped. Make noisy RUN steps quieter, for example npm install --loglevel=warn or pip install -q.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/actor-builds/$BUILD_ID/log"/v2/actor-builds/{buildId}/logOpenAPI document
/v2/actors/{actorId}/openapi.jsonThe OpenAPI document for the build the caller runs: the default build for the owner, the User Build for everyone else. Public Actors need no token; private Actors need the owner. Non-owners get info.version 1.0.0 instead of the build's version. If the Actor has no build yet, the owner gets 404 build-not-found and everyone else 404 actor-not-available.
/v2/actors/{actorId}/openapi.jsonThe openapi.json endpoints return a minimal OpenAPI 3.0 document with the Actor's title, description, and version. The request and response schemas are generic objects, not the Actor's input schema, so the document is not enough to generate a typed client. Read the input schema from Get Actor instead.
Build statuses: READY, RUNNING, SUCCEEDED, FAILED, TIMED-OUT, ABORTED. A run can only start from a SUCCEEDED build.
Browser patch
/v2/actors/{actorId}/browser-patch/v2/actors/{actorId}/browser-patchThe per-Actor switch for the Scrape.ly browser, shown in the dashboard as Settings → Scrapely Browser → Use Scrapely browser. Owner only.
| Item | Value |
|---|---|
| Response | { "data": { "browserPatchEnabled": false } } |
PUT body | { "browserPatchEnabled": true }. A non-boolean returns 400 invalid-input |
| Default | false: builds keep their browser binaries and the Actor launches its own browsers |
| Takes effect | On the next build. Each build stores the value as browserPatched, and runs follow their build. Legacy builds with browserPatched: null count as true |
- On: the build removes Playwright, pyppeteer, and Puppeteer browser downloads and redirects every launch to the Scrape.ly browser. Your own proxy URLs are ignored for browser traffic.
- Off: runs get
SCRAPELY_BROWSER_PATCH=0.CDP_URLis still set for Actors that connect over CDP themselves. - Your stored source is never modified. The setting is independent of the account-level Transform actors setting.
curl -X PUT -H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"browserPatchEnabled": true}' \
"https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/browser-patch"/v2/actors/{actorId}/browser-patchCI webhooks
Trigger a build automatically when you push to Git. With the GitHub integration connected, linking a version to a GitHub repository registers the webhook for you (see Versions), so you only need these endpoints to set it up by hand, to repair an older webhook, or to call the build from GitHub Actions. Both endpoints require a user API key and work only for Actors you own. You can pass the Actor ID or username~actor-name; the webhook is always keyed by the Actor ID.
Get the webhook URL
/v2/actors/{actorId}/ci/webhook-urlReturns a ready-to-POST build URL plus setup instructions for GitHub webhooks and GitHub Actions. The URL carries a per-Actor CI secret, which only works for starting builds of this Actor, so you don't have to put your API key in CI. It builds with tag=latest, waitForFinish=0, and syncGitFromRemote=true. Treat it as a secret.
Optional query version: must match an existing version. If you leave it out, a GIT_REPO version is preferred, otherwise the first version.
| Response field | Description |
|---|---|
webhookUrl | The build URL to call with POST. |
actorId | The Actor ID. |
defaultVersion | The version the URL builds (0.0 if the Actor has no versions). |
buildTag | The tag the build gets. |
instructions | Setup steps for GitHub. |
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/ci/webhook-url?version=0.1"/v2/actors/{actorId}/ci/webhook-urlRegister the GitHub webhook
/v2/actors/{actorId}/ci/webhookRegisters the build webhook on the version's GitHub repository, or repairs the webhook that is already there. The Actor needs a GIT_REPO version on GitHub, and your account must have the GitHub integration connected. The body is optional:
| Body field | Type | Description |
|---|---|---|
version | string | Optional. The version to build. Defaults to the first GIT_REPO version on GitHub. |
repoFullName | string | Optional. owner/repo. Defaults to the version's repository and must match it if you send it. |
A webhook that already points at this Actor and version is reused. If it's disabled, isn't sent as JSON, skips TLS verification, or carries an old token (such as your API key), it is updated in place, and updated is true.
Returns 201 with { "data": { "webhookUrl", "repoFullName", "version", "hookId", "alreadyExisted", "updated" } }.
curl -X POST -H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" -d '{}' \
"https://api.scrape.ly/v2/actors/eLN5rTB8sYTBWdfzx/ci/webhook"| Status | Type | Meaning |
|---|---|---|
| 400 | invalid-input | The Actor has no GIT_REPO version, version isn't one, the version isn't on GitHub, or repoFullName doesn't match its repository. |
| 400 | github-not-connected | Connect GitHub in the dashboard first. |
| 422 | webhook-registration-failed | GitHub refused the webhook. When error.data.manualSetupRequired is true, add the URL from ci/webhook-url in the repository settings yourself. |
Deployment keys
/v2/actors/{actorId}/versions/{versionNumber}/deployment-keys/v2/actors/{actorId}/versions/{versionNumber}/deployment-keys/v2/actors/{actorId}/versions/{versionNumber}/deployment-keys/{keyId}A deployment key is an SSH key pair that lets Scrape.ly clone a private Git repository when it builds a GIT_REPO version. Scrape.ly keeps the private key; you add the returned publicKey to the repository as a read-only deploy key (on GitHub: Settings → Deploy keys).
Owner only, and requires a user API key.
POSTcreates a key and returns201with{ "data": { "id", "publicKey", "title", "createdAt" } }. Optional body:{ "title": "CI" }.GETlists the version's keys.DELETEreturns204, or404 key-not-found.
/v2/actors/{actorId}/versions/0.1/deployment-keysStore listing
/v2/store/actorsThe public catalog of published, non-deprecated Actors. Authentication is optional.
| Query param | Type | Description |
|---|---|---|
search | string | Full-text search. |
category | string | Store category. |
username | string | Only Actors published by this user. |
sortBy | string | popularity (default), newest, rating, users, updated, or name. relevance and lastUpdate are accepted as aliases for popularity and updated. |
desc | boolean | Reverse the sort order. |
pricingModel | string | free for free Actors, or PAY_PER_EVENT. |
excludeKnown | boolean | Leave out Actors you created or already ran. Needs a token. |
includeUnrunnableActors | boolean | Accepted for compatibility; it has no effect. |
limit / offset | number | Pagination. Default limit is 20, max 100. |
curl "https://api.scrape.ly/v2/store/actors?limit=20&sortBy=popularity"/v2/store/actors?limit=5Reviews and issues: Store, reviews & issues.
Permission approvals
An Actor with actorPermissionLevel FULL_PERMISSIONS can act on your account, so you must approve it before you run it, unless you own it or skip approvals. Until then, run requests fail with 403 full-permission-actor-not-approved, and error.data.approvalUrl links to the approval page.
You can also:
- approve it with
POST /v2/actors/{actorId}/approve-permissions(below), or - pass
forcePermissionLevel=LIMITED_PERMISSIONSto run the Actor with limited permissions, which needs no approval.
A run request can't approve an Actor by itself: a permissionApproved query parameter is ignored.
These endpoints require a user API key:
| Endpoint | Description |
|---|---|
POST /v2/actors/{actorId}/approve-permissions | Approve: 201 if new, 200 if already approved. { "data": { "approved": true } }. |
DELETE /v2/actors/{actorId}/approve-permissions | Revoke: 204. |
GET /v2/users/me/permission-approval-skip | { "data": { "skipActorPermissionApprovals": true } }. |
PUT /v2/users/me/permission-approval-skip | Body: { "skip": true }. |
/v2/users/me/permission-approval-skipBookmarks and insights
Bookmark a public Actor with POST / DELETE / GET /v2/actors/{actorId}/bookmark, and list yours with GET /v2/users/me/bookmarks. Details: Search, alerts & utilities.
Owner monetization and acquisition: Earnings. Debugging stats: Search, alerts & utilities.
Actors with standby enabled accept HTTP requests at /v2/{username}/{actorName}/{path}. See Standby mode.