API overview
Base URL, authentication, token types, errors, rate limits, size limits, and API clients.
The scrape.ly API is a REST API. Every public endpoint in these docs is versioned under /v2.
Base URL
https://api.scrape.lyMost requests and responses are JSON (UTF-8). The exceptions are noted on each page: dataset items, key-value store records, Actor pictures, and run and build logs.
/v2/users/meAuthentication
Create a token in the dashboard under Settings → API & Integrations. Send it on every request in the Authorization header:
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/users/meThe /v2 API does not read tokens from the query string. A ?token= parameter is ignored, so the request is treated as unauthenticated. The one exception is the CI build webhook URL from GET /v2/actors/{actorId}/ci/webhook-url. That URL carries a per-Actor CI secret, not your API key, and it is only accepted by POST /v2/actors/{actorId}/builds.
Never commit tokens to source control. Resetting your API key invalidates the old value immediately.
You can create several API keys and limit each one with scopes, resource restrictions, an IP allowlist, and spending caps. See API keys & permissions.
A missing token returns 401 with error.type token-not-provided. An unknown, expired, or revoked token returns 401 with token-not-valid.
When authentication is required
- Required to list or change anything you own: Actors, runs, tasks, storages, schedules, and webhooks.
- Required for every
/v2/users/meendpoint. - Optional for public Actor metadata (
GET /v2/actors/{actorId}), public Actor pictures, public example tasks, and the Store listing.
Token types
| Token | Prefix | Use |
|---|---|---|
| User API key | scrapely_ | Full account access from the dashboard, the CLI, or your backend. |
| Additional API key | scrapely_sk_ | Full access or scoped, created under Settings → API keys. See API keys & permissions. |
| OAuth access token | oat_ | Issued through the OAuth flow under /v2/oauth/*, for example when you connect an MCP client to the hosted MCP server. It acts for your account like an API key, but it cannot view or reset API keys. |
| Scoped run token | st_ | Issued to a running Actor. Its permissions follow the run's permission level. |
Scoped run tokens have limits:
FULL_PERMISSIONS: the run can call most of the API on your behalf, including starting other Actors. Account endpoints (/v2/users/me/*, API keys) still need a user API key.LIMITED_PERMISSIONS: the run can use datasets, key-value stores, and request queues, read Actors, start other Actors and tasks (always with limited permissions), and act on its own run and the child runs it started (read, update, abort, metamorph, reboot, resurrect, charge). Every other endpoint returns403 insufficient-permissions.
Listing all your runs (GET /v2/actor-runs) and creating Actors (POST /v2/actors) reject limited scoped tokens. They accept a user API key, an OAuth token, or a full-permission scoped token.
Response envelope
Most JSON endpoints wrap the payload in data:
{
"data": {
"id": "eLN5rTB8sYTBWdfzx",
"name": "my-actor"
}
}List endpoints use:
{
"data": {
"total": 2560,
"offset": 0,
"limit": 1000,
"count": 1000,
"desc": false,
"items": []
}
}Not wrapped in data: dataset items, key-value store records, run logs, build logs, run and task input JSON, synchronous run output records, Actor pictures, and OpenAPI documents.
Successful deletes usually return 204 No Content with an empty body. Creates usually return 201 with a Location header.
Pagination
List endpoints accept:
| Parameter | Description |
|---|---|
limit | Maximum number of items to return. Capped at 1000. 0 or no value uses the endpoint default. |
offset | Number of items to skip from the start. |
desc | desc=1 or desc=true returns newest first, where supported. |
A negative limit or offset returns 400 invalid-input. Most defaults are 1000; the Store listing and per-Actor task list default to 20. Key-value store key listings paginate with limit and exclusiveStartKey instead. See Key-value stores.
Errors
Errors use standard HTTP status codes. The body holds an error object with type and message, and sometimes data with extra details:
{
"error": {
"type": "record-not-found",
"message": "Store was not found."
}
}| Status | Type | Meaning |
|---|---|---|
| 400 | invalid-request / invalid-input / invalid-value | Malformed body or parameter. |
| 400 | invalid-json | The request body is not valid JSON. |
| 400 | invalid-memory / invalid-timeout | Memory is not a power of two within your limits, or the timeout is not a positive integer. |
| 400 | limit-exceeded | The run or build would exceed your total memory limit. |
| 400 | unsupported-pricing | The Actor uses a pricing model that can no longer be run. |
| 401 | token-not-provided | No Authorization: Bearer header. |
| 401 | token-not-valid | The token is unknown, expired, or revoked. |
| 401 | token-expired | An API key has passed its expiry date. |
| 402 | api-key-daily-cap-reached | The API key's daily charge limit is used up, so the run can't start. |
| 402 | insufficient-balance | A balance-gated action (such as a storage write or build) needs a positive balance. |
| 402 | payment-required | A run cannot reserve enough credits. |
| 402 | insufficient-funds | Your effective balance is used up, for a standby request. |
| 403 | forbidden / insufficient-permissions | Authenticated but not allowed. For a scoped API key, the key's scopes don't cover the request. |
| 403 | api-key-ip-not-allowed | The request came from an address outside the API key's IP allowlist. |
| 403 | full-permission-actor-not-approved | The Actor needs full permissions and you have not approved it yet. error.data.approvalUrl links to the approval page. See Permission approvals. |
| 404 | *-not-found | The resource does not exist (for example actor-not-found, run-not-found). |
| 404 | not-found | No route matches the method and path. |
| 408 | run-timeout-exceeded / request-timeout | A synchronous run did not finish within the request wait limit. The run keeps going. |
| 409 | task-name-exists / version-exists | Conflicts with an existing name or version. |
| 409 | idempotency-key-in-progress | A run 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). |
| 413 | request-too-large | The body is over the size limit. |
| 415 | unsupported-content-encoding | Unsupported Content-Encoding. |
| 429 | rate-limit-exceeded | Too many requests. |
| 429 | concurrency-limit-exceeded / run-capacity-check-busy | You hit your concurrent run limit, or another run request from your account is being evaluated right now. |
| 503 | rate-limit-unavailable | The rate limiter is temporarily unavailable. Retry after one second. |
| 5xx | internal-error | Unexpected server error. |
Retry 429 and 503 with exponential backoff: start near 500 ms, double each time, and add jitter. The API clients do this for you.
Response headers
Rate-limited routes send these headers on both successful and rejected responses:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Maximum requests in the current window. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix time in seconds when the window resets. |
Retry-After | Seconds to wait. Sent only with 429 and 503 rate-limit-unavailable. |
Creation endpoints usually return Location with the new resource URL. Dataset-item responses, including synchronous dataset-item runs, also send pagination in X-Apify-Pagination-Total, X-Apify-Pagination-Offset, X-Apify-Pagination-Count, X-Apify-Pagination-Limit, and X-Apify-Pagination-Desc. The header names are historical.
The API allows cross-origin requests (CORS) from any origin, so you can call it from a browser. Keep in mind that a token used in browser code is visible to anyone who loads the page.
Rate limits
Limits use a sliding one-minute window.
| Scope | Default limit |
|---|---|
General /v2 API with a valid token | 1000 requests / minute per account |
General /v2 API without a valid token | 1000 requests / minute per IP address |
| Datasets, key-value stores, and request queues | 5000 requests / minute per account, plus a ceiling of 5000 per IP address |
/v2/api-keys | 10 requests / minute per IP address |
Hosted MCP server (/mcp) | 1000 requests / minute per IP address |
LLM gateway (/v2/llm/api/v1), with a run token | 600 requests / minute and 20 running at once, per account. Not counted toward the general /v2 limit |
GET /health | 60 requests / minute per IP address |
Calls an Actor makes to update or restart its own run (PUT /v2/actor-runs/{runId}, reboot, metamorph) count against the storage limit rather than the general one.
When you go over a limit, the API responds with 429 rate-limit-exceeded and the headers above. Your account's limits can be raised. Contact support if you need more.
Request size limits
| Body type | Limit |
|---|---|
application/json | 10 MB |
application/octet-stream | 100 MB |
text/* | 100 MB |
The LLM gateway accepts JSON bodies up to 50 MB, for inline images and files.
A body over the limit is rejected with 413 request-too-large before it reaches a route handler. Split large dataset writes into several POST /v2/datasets/{datasetId}/items calls instead of one huge array, and send binary key-value records as application/octet-stream.
Request bodies may be compressed with the identity, gzip, deflate, or br content encodings. A compressed body may be at most 10 MB before decompression, and the decompressed body must still fit the limit above. Any other Content-Encoding returns 415 unsupported-content-encoding.
Referring to resources
- Resource ID: an opaque ID. Actors and runs use 17-character IDs; some resources use UUIDs.
- Named ID:
username~resourcename, for examplejanedoe~my-actor. You need the right permissions on the resource. Task endpoints also accept~tasknamefor one of your own tasks.
Most Actor paths use /v2/actors. These paths also exist under /v2/acts: GET /v2/acts/{actorId}, GET /v2/acts/{actorId}/picture, GET /v2/acts/{actorId}/webhooks, and the public example-task endpoints GET /v2/acts/{actorId}/examples and GET /v2/acts/{actorId}/examples/{slug}.
Content-Type
JSON request bodies must send Content-Type: application/json:
curl -X POST https://api.scrape.ly/v2/actors/janedoe~my-actor/runs \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'The run-start body is the input object itself, not { "input": ... } or { "runInput": ... }.
Run and build statuses
Runs: READY, RUNNING, SUCCEEDED, FAILED, ABORTING, ABORTED, TIMING-OUT, TIMED-OUT.
Builds: READY, RUNNING, SUCCEEDED, FAILED, TIMED-OUT, ABORTED.
A resurrected run goes back to RUNNING. RESURRECTED only appears as the ACTOR.RUN.RESURRECTED webhook event.
Run statuses use a hyphen: TIMED-OUT. Webhook event names use an underscore: ACTOR.RUN.TIMED_OUT (see Webhooks).
Waiting for runs and builds
waitForFinish and the synchronous run endpoints hold the request open while the run or build works. One request is held for at most 45 seconds. This is the server's API_MAX_REQUEST_WAIT_SECS setting, which keeps every response ahead of the 50-second limit of the proxy in front of the API.
waitForFinishaccepts up to 60, but the wait stops at 45 seconds. You then get the run or build in its current state, so checkstatusbefore you read results.- A synchronous run (
run-sync,run-sync-get-dataset-items) that is still going after 45 seconds returns408. The run keeps going, and the error message includes its run ID. - To wait longer, repeat GET /v2/actor-runs/{runId}?waitForFinish=60 until
statusis terminal. Each request returns as soon as the run finishes, or after 45 seconds.call()andwaitForFinish()in the official clients already work this way. - For long jobs, prefer webhooks to waiting.
Don't retry a run start just because it timed out on the network. The run may already exist. Send an Idempotency-Key so a retry returns the same run.
Typical workflow
- Start a run with POST /v2/actors/{actorId}/runs.
- Poll GET /v2/actor-runs/{runId} with
waitForFinishuntilstatusis terminal, or use a synchronous run for jobs that finish within 45 seconds. - Read tabular results from
defaultDatasetIdwith Get dataset items. - Read named records such as
OUTPUTfromdefaultKeyValueStoreIdwith Get record.
For a task-oriented walkthrough, see Run Actors and Get results.
API clients
You don't have to call the REST API by hand. These official tools wrap it:
- JavaScript / TypeScript:
scrapely-client, a typed client for the whole API with retries, pagination helpers, and run waiting. It works in Node.js and the browser. - Python:
scrapely-client-python, the same client for Python, with sync and async interfaces. - MCP: the hosted MCP server lets AI agents search the Store, run Actors, and read results.
- CLI:
scrapelyhandles login, deploys, runs, and storage from the terminal.scrapely apisends a raw authenticated request to any endpoint.
The Actor SDKs (Node.js, Python) are for writing code that runs inside an Actor. To call the API from your own app, use an API client.