Skip to content
API

Actor runs

List, inspect, abort, metamorph, resurrect, charge, and read logs and storages of runs.

Inspect, update, abort, metamorph, reboot, resurrect, charge, and read logs for Actor runs. All paths are relative to https://api.scrape.ly.

To start a run, use POST /v2/actors/{actorId}/runs or run a task. The JSON body is the input object at the top level.

Statuses: READY, RUNNING, SUCCEEDED, FAILED, ABORTING, ABORTED, TIMING-OUT, TIMED-OUT. Terminal statuses are SUCCEEDED, FAILED, ABORTED, and TIMED-OUT.

Run statuses use a hyphen: TIMED-OUT. Webhook event names use an underscore: ACTOR.RUN.TIMED_OUT.

List runs

GET/v2/actor-runs

Lists your runs across all Actors. Accepts a user API key, an OAuth token, or a full-permission scoped token. Limited scoped tokens get 403 insufficient-permissions.

Query paramDescription
statusOne value or a comma-separated list, for example RUNNING,SUCCEEDED. Use TIMED-OUT.
startedAfter / startedBeforeISO 8601 timestamps.
originHow the run was started: API, CLI, WEB, DEVELOPMENT, or SCHEDULER.
searchMatches the run ID or the Actor name.
sortBystartedAt (default), finishedAt, status, duration, or usage.
limit / offset / descPagination. Default and maximum limit is 1000. desc defaults to false.

To list the runs of one Actor, use GET /v2/actors/{actorId}/runs. It takes the same status, startedAfter, startedBefore, sortBy, and pagination parameters. For a task, use GET /v2/actor-tasks/{taskId}/runs.

.bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/actor-runs?status=RUNNING,SUCCEEDED&limit=5&desc=1"
GET/v2/actor-runs?limit=5&desc=1

Get run

GET/v2/actor-runs/{runId}

You can only read your own runs. A run's scoped token can read its own run and the child runs it started. If the Actor defines an output schema, the response also includes a computed output map of URLs. On someone else's Actor, templates that reference build fields (such as {{run.buildId}}) resolve to empty strings.

Build info is owner only. On runs of an Actor you don't own, buildId, buildNumber, and options.build are left out, here and everywhere else runs are returned (run lists, task lastRun, webhooks, and the dashboard's live updates).

Pass waitForFinish (seconds, up to 60) to wait for the run to reach a terminal status. The response comes as soon as the run finishes, or after 45 seconds with the run in its current state (see Waiting for runs and builds). To wait longer, repeat the request until status is terminal. Without waitForFinish, the endpoint returns immediately.

curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt
GET/v2/actor-runs/{runId}

usageUsd breaks the run's cost down by service and usageTotalUsd is the total, both in US dollars. usageTotalNd is the same total in nanodollars ($1 = 1_000_000_000), an exact integer for accounting. Run lists and search return usageTotalUsd and usageTotalNd too.

Read tabular results from defaultDatasetId (dataset items). Named output such as OUTPUT is a record in defaultKeyValueStoreId. You can also reach both through the run storage shortcuts. There is no /output run endpoint.

Get run input

GET/v2/actor-runs/{runId}/input

Returns the INPUT record from the run's default key-value store as raw JSON, not wrapped in data. If the run has no input, the body is null.

Secret input fields come back encrypted (ENCRYPTED_VALUE:...), including for your API key. Only the run's own token gets them decrypted, so Actor.getInput() inside the run sees plain values.

.bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/input
GET/v2/actor-runs/{runId}/input

Run storages

GET/v2/actor-runs/{runId}/dataset
GET/v2/actor-runs/{runId}/dataset/items
GET/v2/actor-runs/{runId}/key-value-store
GET/v2/actor-runs/{runId}/key-value-store/keys
GET/v2/actor-runs/{runId}/key-value-store/records/{recordKey}
GET/v2/actor-runs/{runId}/request-queue
GET/v2/actor-runs/{runId}/request-queue/head
GET/v2/actor-runs/{runId}/request-queue/requests

Any path under /v2/actor-runs/{runId}/dataset, /key-value-store, or /request-queue is forwarded to the run's default storage. It behaves exactly like the matching dataset, key-value store, or request queue endpoint, with every method and parameter, so you don't need to look up the storage ID first.

Only the run owner, or the run's own scoped token, can use these paths. An unknown run returns 404 run-not-found. A run without that default storage returns 404 record-not-found.

.bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/dataset/items?format=csv&limit=100"
GET/v2/actor-runs/{runId}/dataset/items?limit=10
GET/v2/actor-runs/{runId}/key-value-store/records/OUTPUT

Update run

PUT/v2/actor-runs/{runId}
Body fieldTypeDescription
statusMessagestringMessage shown on the run.
isStatusMessageTerminalbooleanWhether the status message is final.
generalAccessstringFOLLOW_USER_SETTING, ANYONE_WITH_ID_CAN_READ, or RESTRICTED.
idstringIf present, must match {runId}.

An id that doesn't match, or an unknown generalAccess value, returns 400 invalid-request. Returns 200 with the updated run under data.

.bash
curl -X PUT https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"statusMessage": "Crawling page 12/40"}'

Delete run

DELETE/v2/actor-runs/{runId}

Deletes a run that is READY or has finished. A run that is still RUNNING, ABORTING, or TIMING-OUT returns 400 invalid-state, so abort it first. Returns 204 No Content.

Deleting a run removes the run record. Its default storages are not deleted right away; they expire with your storage retention period.

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

Abort run

POST/v2/actor-runs/{runId}/abort

Stops a run. The run moves to ABORTING and then ABORTED. Pass gracefully=1 (or true) to give the Actor time to shut down cleanly instead of stopping the container at once. A READY run that hasn't started yet goes straight to ABORTED. Aborting a run that already finished, or is already ABORTING, changes nothing and returns the run as it is.

Returns 200 OK with the run under data:

.json
{
  "data": {
    "id": "a7XyZ8kLmN2pQ4rSt",
    "status": "ABORTING"
  }
}
.bash
curl -X POST -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/abort?gracefully=1"

Metamorph run

POST/v2/actor-runs/{runId}/metamorph

Turns a RUNNING run into a run of a different Actor, keeping the same run ID and storages. A run can metamorph at most 10 times.

ParameterWhereDescription
targetActorIdbody or queryRequired. Target Actor ID or username~name. actorId is accepted as an alias.
inputbodyNew input for the target Actor (not runInput).
buildbody or queryTarget build tag or number. Only for a target Actor you own; on someone else's Actor it's ignored and the User Build is used.
.bash
curl -X POST https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/metamorph \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "targetActorId": "otheruser~other-actor",
    "input": { "url": "https://example.com" },
    "build": "latest"
  }'

The previous INPUT is stored as INPUT-METAMORPH-N in the default key-value store.

Returns 200 OK with the run under data, including a metamorphs array. Each item has createdAt, actorId, buildId (only for Actors you own), and inputKey. If the target Actor has no matching build, the run's buildId and buildNumber are cleared after the switch. The new container gets the target build's ACTOR_BUILD_ID, ACTOR_BUILD_NUMBER, and ACTOR_BUILD_TAGS, not the original Actor's. The container switches to the target Actor after the response, so actId in this response still shows the original Actor. Read the run again to see the new actId.

.json
{
  "data": {
    "id": "a7XyZ8kLmN2pQ4rSt",
    "actId": "eLN5rTB8sYTBWdfzx",
    "status": "RUNNING",
    "metamorphs": [
      {
        "createdAt": "2026-08-14T15:00:00.000Z",
        "actorId": "targetActorId123",
        "buildId": "bUiLdIdExAmPlE123",
        "inputKey": "INPUT-METAMORPH-1"
      }
    ]
  }
}
StatusTypeMeaning
400invalid-stateThe run is not RUNNING.
400limit-exceededThe run already metamorphed 10 times.
403insufficient-permissionsA limited-permission run tried to metamorph into a full-permission Actor.
403full-permission-actor-not-approvedThe target Actor needs full permissions and you haven't approved it. See Permission approvals.
404actor-not-foundThe target Actor does not exist.

The wrong-state status code differs by endpoint: metamorph and resurrect return 400, while charge returns 405.

Reboot run

POST/v2/actor-runs/{runId}/reboot

Restarts the run's container and keeps its storages. Allowed while the run is READY or RUNNING; any other status returns 400 invalid-run-status. Returns 202:

.json
{ "data": { "accepted": true, "runId": "a7XyZ8kLmN2pQ4rSt" } }
.bash
curl -X POST -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/reboot

Resurrect run

POST/v2/actor-runs/{runId}/resurrect

Restarts a finished run in place, with the same run ID and storages. The run must be SUCCEEDED, FAILED, ABORTED, or TIMED-OUT. Returns 200 with the run under data and a Location header. The run goes back to RUNNING, and an ACTOR.RUN.RESURRECTED webhook event fires.

Optional query parameters (defaults come from the original run): build, timeout, memory, maxItems, maxTotalChargeUsd, restartOnError. build is owner only; on someone else's Actor the run keeps its original build, or runs the User Build if that build was deleted.

StatusTypeMeaning
400invalid-stateThe run has not finished.
400invalid-memory / limit-exceededThe memory is invalid or would exceed your total memory limit.
402payment-requiredNot enough credit to start the run.
429concurrency-limit-exceeded / run-capacity-check-busyYou are at your concurrent run limit, or another run request is being evaluated.
.bash
curl -X POST -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/resurrect?memory=4096"

Charge events

POST/v2/actor-runs/{runId}/charge

Charges pay-per-event events against a RUNNING run you own. The SDK charging helpers and scrapely actor charge call this endpoint.

Body fieldTypeDescription
eventNamestringRequired. An event name from the Actor's pricing. 1–128 bytes, starts with a letter or digit, then letters, digits, ., _, :, or -.
countintegerNumber of events, from 1 to 1000000. Default 1.

The dataset-item event (scrapely-default-dataset-item) is charged automatically when items are pushed and cannot be charged through this endpoint.

Send an Idempotency-Key header (up to 128 characters of letters, digits, ., _, :, or -) whenever the charge is in a code path that can retry. Without it, a retry after a network error charges twice. A replay with the same key and payload returns the original result with duplicate: true. Keys are remembered for at least 35 days after the run ends, and a replay works even after the run has finished. If the charge was voided, the replay returns acceptedCount: 0.

Response (201, not wrapped in data). operationId is your Idempotency-Key, or a generated ID if you didn't send one:

.json
{
  "operationId": "charge-1",
  "eventName": "result-item",
  "acceptedCount": 10,
  "requestedCount": 10,
  "duplicate": false
}

If the charge would go over the run's maxTotalChargeUsd, only part of it may be accepted: compare acceptedCount with requestedCount.

StatusTypeMeaning
201—Charge recorded, or replayed for a repeated Idempotency-Key.
400invalid-inputBad eventName, count, or Idempotency-Key. error.code says which: event-name-invalid, event-name-too-long, event-name-server-derived, count-invalid, count-too-large, idempotency-key-invalid, or idempotency-key-too-long.
400unknown-eventThe Actor's pricing has no event with that name.
402charge-limit-exceededThe run already reached maxTotalChargeUsd. Next to error, the body includes acceptedCount: 0 and requestedCount.
403forbiddenNot your run.
404run-not-foundThe run does not exist.
405invalid-stateThe run is not RUNNING (and the request is not a replay).
409conflictThe Idempotency-Key was already used with a different event or count.
409ledger-sealedThe run has finished and its charges were finalized.
503ledger-unavailableCharging is temporarily unavailable. Retry with the same Idempotency-Key.
.bash
curl -X POST https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/charge \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: charge-1" \
  -d '{"eventName": "result-item", "count": 10}'

Void a charge

POST/v2/actor-runs/{runId}/charge/void

Reverses a charge whose items were never stored, so a result is only billed if it was saved. The SDKs call this when a push fails after its charge was accepted. No body. The Idempotency-Key header is required and is the key of the charge to void.

Only the run's own token can void its charges; a user API token gets 403 forbidden. Voiding works until the run is settled.

Response (200, not wrapped in data). status is voided, or already-voided for a repeated void. voidedCount is the number of events reversed:

.json
{ "status": "voided", "voidedCount": 10 }
StatusTypeMeaning
200—Charge voided, or already voided.
400invalid-inputThe Idempotency-Key is missing, invalid, or too long. error.code says which: idempotency-key-required, idempotency-key-invalid, or idempotency-key-too-long.
403forbiddenNot your run, or not the run's own token.
404run-not-foundThe run does not exist.
404charge-not-foundNo charge with this Idempotency-Key was applied to the run.
409ledger-sealedThe run has been settled; its charges can no longer be voided.
503ledger-unavailableThe void could not be confirmed. Retry with the same Idempotency-Key.
.bash
curl -X POST https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/charge/void \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Idempotency-Key: charge-1"

Get run log

GET/v2/actor-runs/{runId}/log

Returns the log as plain text (Content-Type: text/plain), not a JSON envelope. There is no /log/stream endpoint.

On runs of an Actor you don't own, Scrape.ly's own log lines name no build: image names (which carry the build number) are replaced with [actor image]. The same applies to older runs whose log is read as the log record of the run's key-value store.

.bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/actor-runs/a7XyZ8kLmN2pQ4rSt/log
GET/v2/actor-runs/{runId}/log

For the last run of an Actor or task, see GET /v2/actors/{actorId}/runs/last/log and GET /v2/actor-tasks/{taskId}/runs/last/log.

Get log by ID

GET/v2/logs/{logId}

Returns the log of a run or a build by its ID, as plain text. The ID is checked as a run first, then as a build. Owner only: someone else's run returns 403 forbidden, and an unknown build or one you can't read returns 404 record-not-found.

.bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/logs/a7XyZ8kLmN2pQ4rSt