Skip to content
Build Actors

Security

API and run tokens, secrets, Actor permissions, data isolation, and webhook safety.

This page covers how Scrape.ly protects your account and data, and what you need to do to keep credentials safe: API tokens, secret environment variables, secret input fields, Actor permissions, data isolation, and webhooks.

API tokens

Your account API key gives full access to your account. Treat it like a password.

  • Send it in the Authorization: Bearer <token> header. The API ignores a ?token= query parameter on every route except the Actor's CI build webhook, which uses its own per-Actor secret, not your API key.
  • Never commit it to Git. Read it from an environment variable (SCRAPELY_TOKEN, or APIFY_TOKEN) or store it with scrapely login.
  • If it leaks, rotate it under Settings → API & Integrations in the dashboard, or call POST /v2/api-keys/reset. The old key stops working immediately and you get a notification.
  • For CI, servers, MCP clients, and integrations, create separate keys under Settings → API keys and give each only the scopes it needs, with an expiry, resource restrictions, an IP allowlist, or spending caps. See API keys & permissions.
GET/v2/api-keys
POST/v2/api-keys/reset

Both routes accept only the permanent account API key. Run tokens, OAuth access tokens, and CI tokens get 403 insufficient-permissions.

Run tokens

Code inside a run never sees your account API key. Each run gets a short-lived scoped token in SCRAPELY_TOKEN, with the st_ prefix. It expires after one hour without use, and the platform keeps refreshing it while the run is active. What the token can reach depends on the Actor's permission level.

No scoped token can call account endpoints: profile, email, billing, usage, API keys, and notification settings.

The run token is also the API key for the LLM gateway, which accepts nothing else: your account API key gets 403 llm-gateway-runs-only, and the token stops working there when the run finishes.

Secret environment variables

Mark an Actor version's environment variable as secret for third-party API keys, proxy passwords, and other credentials.

  • The value is encrypted at rest with AES-256-GCM.
  • API reads return an empty string in place of the value.
  • The value is decrypted only when it's injected into a build or run container.

In scrapely.json or .actor/actor.json, an environmentVariables value that starts with @ refers to a secret by name, for example "API_KEY": "@myApiKey". Add the secret locally with scrapely secrets add myApiKey <value>. The CLI keeps local secrets in ~/.scrapely/secrets.json. See CLI webhooks and secrets.

Secret input fields

Set "isSecret": true on a field in your input schema, usually a string, to mark it as sensitive:

.json
{
    "apiKey": {
        "title": "API key",
        "type": "string",
        "isSecret": true
    }
}

What this does:

  • The dashboard renders the field as a password input with a Secret badge.
  • Saved input (GET /v2/actors/:actorId/input) returns *** instead of the value.
  • When a run is created, from the API, the dashboard, a task, or a schedule, the platform encrypts the field's value before storing anything. It uses your account's key pair: RSA-OAEP wraps a fresh AES-256-GCM key for each value. The run record, the job queue, and the run's INPUT record only hold the encrypted form, ENCRYPTED_VALUE:<key>:<data>. The secret fields are the ones declared by the input schema of the build the run uses.
  • The input passed to a metamorph target is encrypted the same way, using the target build's input schema.
  • Each encrypted value is bound to the one run, and the one Actor, it was encrypted for. It opens only for that run.
  • Encrypted values (ENCRYPTED_VALUE:...) in a new run's input are accepted when you send them with your dashboard session or an API key, as a Rerun does, or when a schedule or a restart on error starts the run. They are re-encrypted for the new run. A run token, an OAuth or integration token, or an x402 payment can pass an encrypted value only as it is stored in the saved task being started; otherwise the run start returns 400 sealed-input-not-allowed. A value encrypted for another Actor, or one that comes only from the input schema's defaults, is always refused. Send the secret itself instead.
  • When the run starts, the platform decrypts the values on the worker host and passes the plaintext to the container in ACTOR_INPUT. The private key never enters the container. A secret that can't be decrypted fails the run.

Your Actor reads the field as plain text from Actor.getInput() / Actor.get_input(). It needs no keys or extra environment variables.

Who gets the decrypted value:

ReaderINPUT record and GET /v2/actor-runs/:runId/input
The run itself, with its own run tokenDecrypted
Your API key, OAuth tokens, other runs (including a parent run)Encrypted (ENCRYPTED_VALUE:...)

Rotating your key pair makes values encrypted with the old key unreadable, so a run created before the rotation can't be resurrected with its secrets.

What isn't encrypted yet:

  • Secret fields inside array items are not detected, so their values are stored as sent. Only top-level fields and fields nested in object properties are encrypted.
  • Saved task input and saved Actor input are stored as sent. Saved Actor input reads return *** for secret fields.
  • Inside the container, ACTOR_INPUT holds the plaintext, so any code in the run can read it.
  • Runs created before encryption was introduced keep their plaintext input.

Actor permissions

Every Actor declares a permission level. That level controls what its run token can do.

LevelWhat the run can do
LIMITED_PERMISSIONS (default)Read and write its own default storages, storages it creates, and storages passed in through resourcePicker input fields. It can also read its own run and Actor, start other Actors and tasks (always as limited), and abort, metamorph, reboot, resurrect, or charge its own run. Every other route returns 403 insufficient-permissions.
FULL_PERMISSIONSUse the API on your behalf, for example to read any of your storages, list runs, or manage webhooks. Account endpoints stay off limits.

To give a limited Actor access to an existing storage, add an input field with "editor": "resourcePicker" and "resourceType" set to dataset, keyValueStore, or requestQueue. The value must be the storage ID, or an array of IDs.

Approving full-permission Actors

You can run your own Actors at any level. For someone else's FULL_PERMISSIONS Actor, the first run returns 403 full-permission-actor-not-approved with an approval URL. Approve it once from the dashboard, or with POST /v2/actors/:actorId/approve-permissions. A run request can't approve an Actor by itself: a permissionApproved query parameter is ignored.

POST/v2/actors/:actorId/approve-permissions
DELETE/v2/actors/:actorId/approve-permissions
GET/v2/users/me/permission-approval-skip
PUT/v2/users/me/permission-approval-skip

DELETE revokes an approval. The permission-approval-skip setting turns off approval prompts for your account.

When you start a run, forcePermissionLevel=LIMITED_PERMISSIONS runs any Actor with limited permissions. A limited run that starts another Actor always starts it as limited.

What run tokens can't do

A run token of either level acts inside your account, but some actions always need you:

  • Saved tasks. A run token can start your saved tasks, but not with more than you saved. It can change the input, waitForFinish, and the Idempotency-Key. A longer timeout is cut to the task's timeout. A larger memory, maxItems, or maxTotalChargeUsd than the task's, turning on restartOnError, another build, ad-hoc webhooks, or FULL_PERMISSIONS on an Actor whose default is limited returns 403 task-override-not-allowed. API keys aren't affected.
  • Approvals. Approving a full-permission Actor needs your dashboard session or an API key.
  • Reviews and issues. Writing reviews, issues, and comments needs your dashboard session or an API key.
  • Encrypted secret input. A run token can't pass another run's encrypted secret on to a new run. See Secret input fields.

Once an Actor is published, its owner can't change its permission level. See Publish and monetize.

Data isolation

  • Datasets, key-value stores, request queues, runs, and builds belong to one account. Other accounts get 404 or 403.
  • A private Actor can only be run, read, or built by its owner.
  • A public Actor's listing, README, and input schema are public. Its source files are hidden by default.
  • Run containers are ephemeral. Their filesystem is discarded when the run ends, so persist what you need in storage.
  • The managed proxy password is the run ID and only works from inside that run.

Webhook security

The API checks webhook URLs when you create or update a webhook, and again before every delivery:

  • Only http:// and https:// URLs are accepted. Use HTTPS so payloads are encrypted in transit.
  • Localhost, private networks, cloud metadata addresses, and internal hostnames are rejected.
  • URLs that point back at Scrape.ly are only allowed for the public Actor and task run endpoints.

Each delivery carries an X-Scrapely-Webhook-Dispatch-Id header. To check that a request really came from your webhook, add a secret of your own: either in the URL, or as a header through the webhook's headers template. Check it in your receiver.

Delivery attempts are logged as dispatches. See the Webhooks API.

GET/v2/webhooks/:webhookId/dispatches

What's next