API keys & permissions
Create many API keys, limit them with scopes, resources, IPs, and spending caps, and revoke them.
Your account can have many API keys. Give each app, server, CI pipeline, or AI agent its own key, limit what it can do with scopes, and revoke it on its own without touching the others. Manage keys in the dashboard under Settings → API keys, or over the API with the key management endpoints.
Every key is sent the same way, in the Authorization header:
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/users/meThe API does not read keys from a ?token= query parameter. The only places that take a token in the URL are the CI build webhook, which uses its own per-Actor secret, and the hosted MCP server at /mcp, which passes the key through to the API.
What a key has
| Field | Description |
|---|---|
| Name | Required, up to 100 characters. Shown in the key list and in notifications. |
| Description | Optional, up to 500 characters. Note where the key is used. |
| Created | When the key was created. |
| Last used | The time and IP address of the key's most recent request. |
| Expires | Never, or a date. After it, requests with the key get 401 token-expired. |
| Access | Full access or a list of scopes, plus optional resource restrictions and limits. |
| Status | active, expired, or revoked. |
The secret is shown once, when you create the key. Scrape.ly stores only a hash of it, so it can't be shown again. Afterwards the dashboard and the API show a masked prefix such as scrapely_sk_…ab12. Copy the key into your secret store right away; if you lose it, regenerate it.
- Revoke stops a key immediately. A revoked key stays in the list, marked as revoked, and its requests get
401 token-not-valid. - Regenerate creates a new key with the same name, description, expiry, and permissions, and revokes the old one at the same time. Nothing is rotated silently: you get the new secret in the response and have to deploy it. A revoked key can't be regenerated; create a new one instead.
The Default key
Every account has a Default key: the account's original API key, in the format scrapely_ followed by 48 hex characters. It is listed first, with the ID default.
- It has full access and never expires. The dashboard uses it, and it keeps working exactly as before.
- Only the Default key can create, edit, regenerate, and revoke API keys. In practice that means the dashboard.
- It can't be edited or revoked. You can rotate it in the dashboard, or with
POST /v2/api-keys/reset(see Users API). Rotating it doesn't affect your additional keys.
Additional keys look like scrapely_sk_ + 16 hex characters + _ + 48 hex characters, for example scrapely_sk_3f9a0c1d2e4b5a67_….
Full access or scoped
When you create a key, choose one of two access modes:
| Mode | What the key can do |
|---|---|
| Full access | Everything the Default key can do, except managing API keys. |
| Scoped | Only what its scopes cover. Everything else is refused with 403 insufficient-permissions. |
Scoped keys are deny-by-default: an endpoint that no scope covers is refused, even when it only reads data.
The dashboard has two presets to start from:
- Read only: every
:readscope. - Read & write: every read and write scope.
- Automations:
automations:write,runs:read,datasets:read, andtasks:read(withactors:run), the run permission Actor's own permission level, and no Actor limit. Enough for a service or agent that creates and runs automations.
You can then add or remove single scopes.
Scopes
A write scope includes its read scope. actors:manage includes actors:build, and actors:run and actors:build include actors:read.
| Scope | Allows |
|---|---|
actors:read | Read Actors, versions, builds, source files, and OpenAPI documents. |
actors:run | Start runs of Actors and tasks, including synchronous runs (run-sync, run-sync-get-dataset-items) and Standby URLs. |
actors:build | Start and abort builds. |
actors:manage | Create, update, and delete Actors and versions, environment variables, deployment keys, and the CI webhook. Sensitive: a key with this scope can change the code your other keys run. |
runs:read | List and get runs, logs, run input, and a run's default dataset, key-value store, and request queue through /v2/actor-runs/{runId}/dataset, /key-value-store, and /request-queue. Also the last-run endpoints (…/runs/last). |
runs:write | Abort, reboot, update, and delete runs, and write to a run's default storages. Resurrect and metamorph also need actors:run and the run permission actor-default; with an Actor restriction, the metamorph target must be one of the listed Actors. |
tasks:read | List and get tasks and their input. Tasks are the dashboard's saved inputs (the key form calls this area Saved inputs (tasks)), so this also covers GET /v2/actors/{actorId}/saved-inputs. |
tasks:write | Create, update, and delete tasks (saved inputs). |
datasets:read | List datasets, get them, and read their items. |
datasets:write | Create, update, and delete datasets, and push items. |
keyValueStores:read | List key-value stores, get them, list keys, and read records. |
keyValueStores:write | Create, update, and delete key-value stores and records. |
requestQueues:read | List request queues, get them, and read requests and the queue head. |
requestQueues:write | Create, update, and delete request queues, add and update requests, and lock them. |
schedules:read | List and get schedules and their logs. |
schedules:write | Create, update, and delete schedules. Also needs actors:run and the run permission actor-default, because scheduled runs start later with your account's own permissions, not the key's. Refused, except for deletes, on keys with a spending cap. |
automations:read | List and get automations, their run history, and single runs. |
automations:write | Create, update, delete, duplicate, and run automations, AI step included. Also needs actors:run and the run permission actor-default, and is refused for keys limited to specific Actors, because automation runs start with your account's own permissions, not the key's. Refused, except for deletes, on keys with a spending cap: those runs and the AI step, which is billed to your account like the AI chat, would run later without the key's caps. |
webhooks:read | List and get webhooks, webhook dispatches, and the integrations list. |
webhooks:write | Create, update, delete, and test webhooks. Also needs actors:run, runs:read, and the run permission actor-default, because a webhook's integration token can start and read runs. Refused for keys with an Actor or storage restriction or a spending cap. |
billing:read | Usage, limits, plan, and stats: GET /v2/users/me/usage/*, /v2/users/me/limits, /v2/users/me/plan, and /v2/users/me/stats. |
Endpoints outside the scopes
| Who can call it | Endpoints |
|---|---|
| Any valid key | GET /v2/users/me (your identity; this is what scrapely login checks) and public Store and catalog reads. |
| Full-access keys and the Default key | Profile, notification settings, dashboard notifications, messages, monetization and payouts, earnings, approving full-permission Actors, bookmarks, writing issues and reviews, and connecting Slack or GitHub. |
| Default key only | /v2/api-keys*, GET /v2/scrapely, the AI chat, and the dashboard live feed. |
| Run tokens only | The LLM gateway and the solver. No API key works there. |
Example requests by scope
The examples use $SCRAPELY_TOKEN for the key. Replace the IDs with your own.
actors:read: get an Actor.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/actors/{actorId}actors:run: start a run. The body is the Actor input.
curl -X POST https://api.scrape.ly/v2/actors/{actorId}/runs \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'actors:build: build version 0.1.
curl -X POST -H "Authorization: Bearer $SCRAPELY_TOKEN" \
"https://api.scrape.ly/v2/actors/{actorId}/builds?version=0.1&tag=latest"actors:manage: update an Actor.
curl -X PUT https://api.scrape.ly/v2/actors/{actorId} \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "My scraper"}'runs:read: read a run's log, and the items of its default dataset.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/actor-runs/{runId}/log
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/actor-runs/{runId}/dataset/itemsruns:write: abort a run.
curl -X POST -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/actor-runs/{runId}/aborttasks:read: list your tasks.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/actor-taskstasks:write: create a task.
curl -X POST https://api.scrape.ly/v2/actor-tasks \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"actId": "{actorId}", "name": "daily-shop", "input": {"url": "https://example.com"}}'datasets:read: read dataset items.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/datasets/{datasetId}/itemsdatasets:write: push items.
curl -X POST https://api.scrape.ly/v2/datasets/{datasetId}/items \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '[{"title": "First item"}]'keyValueStores:read: read a record.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/key-value-stores/{storeId}/records/OUTPUTkeyValueStores:write: write a record.
curl -X PUT https://api.scrape.ly/v2/key-value-stores/{storeId}/records/CONFIG \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"maxPages": 50}'requestQueues:read: read the queue head.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/request-queues/{queueId}/headrequestQueues:write: add a request.
curl -X POST https://api.scrape.ly/v2/request-queues/{queueId}/requests \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/page-2", "uniqueKey": "https://example.com/page-2"}'schedules:read: list schedules.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/schedulesschedules:write (with actors:run and run permission actor-default): create a schedule.
curl -X POST https://api.scrape.ly/v2/schedules \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "daily-scrape",
"cronExpression": "0 6 * * *",
"actions": [{ "type": "RUN_ACTOR_TASK", "actorTaskId": "{taskId}" }]
}'automations:read: list automations.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/automationsautomations:write (with actors:run and run permission actor-default): run an automation now.
curl -X POST -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/automations/{automationId}/runswebhooks:read: list webhooks.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/webhookswebhooks:write (with actors:run and runs:read): create a webhook.
curl -X POST https://api.scrape.ly/v2/webhooks \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"eventTypes": ["ACTOR.RUN.SUCCEEDED"],
"requestUrl": "https://example.com/hooks/scrapely",
"condition": { "actorId": "{actorId}" }
}'billing:read: this month's usage.
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
https://api.scrape.ly/v2/users/me/usage/monthlyA refused request
A key with only datasets:read that tries to push items:
curl -X POST https://api.scrape.ly/v2/datasets/{datasetId}/items \
-H "Authorization: Bearer $SCRAPELY_TOKEN" \
-H "Content-Type: application/json" \
-d '[{"title": "First item"}]'The status is 403. Nothing is written.
Resource restrictions
A scoped key can also be limited to specific resources.
Specific Actors
With a list of Actors, the key can only read, run, and build those Actors, and see their runs, builds, logs, and tasks.
- Account-wide lists are refused, for example
GET /v2/actorsandGET /v2/actor-runs. Use the per-Actor endpoints instead, such asGET /v2/actors/{actorId}/runs. - The key can't create Actors, schedules, or webhooks.
You can add your own Actors and public Store Actors, by ID or by username/actor-name.
Specific storages
With a list of storage IDs (datasets, key-value stores, and request queues), the key can only use those storages.
- Listing storages and creating new ones are refused.
- A run's default storages are reachable through
/v2/actor-runs/{runId}/dataset,/v2/actor-runs/{runId}/key-value-store, and/v2/actor-runs/{runId}/request-queuewhen they are on the list, or when the key is also limited to specific Actors and may read that run.
Run permission
This setting decides how much access a run gets when a scoped key starts it.
| Value | What the run gets |
|---|---|
limited (default, recommended) | The run is forced to LIMITED_PERMISSIONS. Its run token can only use the run's own default storages, storages passed in its input, storages it creates, and the child runs it starts. The key can't approve full-permission Actors. |
actor-default | The run gets the permission level the Actor declares, which may be FULL_PERMISSIONS. Use it for a service that should only start one trusted Actor, ideally together with an Actor restriction. |
With limited, Standby URLs are refused (a Standby run uses the Actor's own permissions, as on Apify), and so are resurrect and metamorph.
Attaching ad-hoc webhooks to a run start (the webhooks body field or query parameter) needs the same scopes as creating a webhook: webhooks:write and runs:read. Keys limited to specific Actors can't attach them.
Full-access keys behave like the Default key: runs get the Actor's declared permission level. See Security for what each level allows.
Optional limits
None of these are set by default.
| Limit | Effect |
|---|---|
| IP allowlist | IP addresses or CIDR ranges, IPv4 or IPv6, for example 203.0.113.7 or 2001:db8::/32. Requests from any other address get 403 api-key-ip-not-allowed. |
| Max charge per run | The default and the ceiling of maxTotalChargeUsd for every run the key starts. A run asking for more is capped. |
| Max charge per day | The total the key's runs may charge per UTC day. Each new run is capped to what's left of the day; a run that is still going holds its whole cap until it finishes and is settled, so parallel starts share one budget. Once it's used up, run starts return 402 api-key-daily-cap-reached until the next UTC day. |
| Rate limit per minute | Requests per minute for this key. Over it, the API returns 429 rate-limit-exceeded with Retry-After. Your account rate limits still apply. |
Errors
| Status | Type | Meaning |
|---|---|---|
| 401 | token-not-valid | The key is unknown or revoked. |
| 401 | token-expired | The key's expiry date has passed. |
| 403 | insufficient-permissions | The key's scopes or resource restrictions don't cover this request. |
| 403 | api-key-ip-not-allowed | The request came from an address outside the key's IP allowlist. |
| 402 | api-key-daily-cap-reached | The key's daily charge limit is used up, so the run can't start. |
| 429 | rate-limit-exceeded | The key's or the account's rate limit was hit. Wait for Retry-After seconds. |
Manage keys over the API
These endpoints accept only the Default key. Any other key, a run token, or an OAuth token gets 403 insufficient-permissions. They are limited to 10 requests per minute per IP address.
/v2/api-keys/v2/api-keys/v2/api-keys/scopes/v2/api-keys/{keyId}/v2/api-keys/{keyId}/v2/api-keys/{keyId}/v2/api-keys/{keyId}/v2/api-keys/{keyId}/regenerate| Endpoint | Description |
|---|---|
GET /v2/api-keys | Lists every key, the Default key first (ID default), with masked prefixes. Revoked keys are included. |
POST /v2/api-keys | Creates a key. Returns 201; the response includes key once. |
GET /v2/api-keys/scopes | The scope catalog: scopes, the read-only preset (readOnly), what each write scope implies, and the requirements of automations:write, schedules:write, and webhooks:write (alsoRequires, runPermission, allActors). The dashboard uses it to warn about a key that would be refused. |
GET /v2/api-keys/{keyId} | One key. |
PUT / PATCH /v2/api-keys/{keyId} | Changes a key's name, description, expiry, or permissions. The secret stays the same. The Default key can't be edited. |
DELETE /v2/api-keys/{keyId} | Revokes a key immediately. Returns 204. The Default key can't be revoked. |
POST /v2/api-keys/{keyId}/regenerate | Creates a new key with the same settings and revokes the old one. Returns 201; the response includes key once and replaces with the old key's ID. |
Request body fields for create and update:
| Field | Type | Description |
|---|---|---|
name | string | Required on create. Up to 100 characters. |
description | string or null | Up to 500 characters. |
expiresAt | string or null | ISO 8601 date and time, or null for no expiry. |
fullAccess | boolean | true for full access. Scopes and restrictions are then ignored. |
scopes | string[] | Scopes for a scoped key. Implied scopes are added for you. |
actorIds | string[] | Limit the key to these Actors (IDs or username/actor-name). |
storageIds | string[] | Limit the key to these dataset, key-value store, and request queue IDs. |
ipAllowlist | string[] | IP addresses or CIDR ranges. |
runPermission | string | limited (default) or actor-default. |
maxChargePerRunUsd | number or null | Default and ceiling of maxTotalChargeUsd for runs the key starts. |
maxChargePerDayUsd | number or null | Total charges of the key's runs per UTC day. |
rateLimitPerMin | number or null | Requests per minute for this key. |
Create a key for a CI pipeline that deploys one Actor:
curl -X POST https://api.scrape.ly/v2/api-keys \
-H "Authorization: Bearer $SCRAPELY_DEFAULT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Actions deploy",
"description": "Builds janedoe/my-actor on push",
"expiresAt": "2027-06-30T00:00:00.000Z",
"fullAccess": false,
"scopes": ["actors:build", "actors:read"],
"actorIds": ["janedoe/my-actor"]
}'Revoke it:
curl -X DELETE -H "Authorization: Bearer $SCRAPELY_DEFAULT_KEY" \
https://api.scrape.ly/v2/api-keys/3f9a0c1d2e4b5a67CLI, SDKs, and MCP
CLI. scrapely login works with any key: the Default key, a full-access key, or a scoped key. It only checks the key with GET /v2/users/me. Each command then succeeds or fails according to the key's scopes. See CLI authentication.
scrapely login --token "$SCRAPELY_TOKEN"API clients and SDKs. The JavaScript and Python clients send the key as a Bearer token, so a scoped key works there the same way.
MCP. The hosted MCP server acts with the key you give it. A scoped key limits what an AI agent can do: a read-only key can't start paid runs.
Recommended keys
Use a separate, scoped key for everything that isn't you in the dashboard:
| Use | Scopes |
|---|---|
CI that deploys an Actor (scrapely push) | actors:build + actors:read, restricted to that Actor |
| A backend that triggers runs and reads results | actors:run + runs:read, restricted to the Actors it uses |
| A dashboard or BI tool that reads results | datasets:read (or the Read only preset), restricted to the storages it needs |
| An MCP client or AI agent | Read only, or actors:run + runs:read with a daily charge limit |
| A service or agent that sets up automations | The Automations preset, without spending caps (a capped key can't create, change, or run automations). Cap each step with Maximum cost per run instead |
| A third-party integration | Only the scopes it asks for, with an expiry date |
Give each key a name you'll recognize, set an expiry where you can, and revoke keys you no longer use. Use Last used to find keys that have gone quiet.
What's next
- API overview: base URL, errors, and rate limits.
- Security: run tokens, secrets, and Actor permissions.
- CLI authentication: logging in and using keys in CI.
- Account & billing: plans, usage, and limits.