---
title: "Environment · Scrape.ly Docs"
description: "Injected variables, configuration, the Scrape.ly browser, and the API client."
url: https://scrape.ly/docs/sdk-node-environment
markdown: https://scrape.ly/docs/sdk-node-environment.md
---

# Environment

> Injected variables, configuration, the Scrape.ly browser, and the API client.

A platform run describes itself through environment variables. This page lists what the platform injects, how the SDK exposes it, how to tune the SDK configuration, how browser crawlers reach the remote browser, and how to get an API client.

## Environment and run metadata

### `Actor.getEnv()`

`getEnv()` reads the environment and returns an object (TypeScript type `ApifyEnv`, a compatibility name). Missing values are `null`. It's available as `Actor.getEnv()` and on instances.

| Property | Source |
| --- | --- |
| `actorId` | `ACTOR_ID` |
| `actorRunId` | `ACTOR_RUN_ID` |
| `actorTaskId` | `ACTOR_TASK_ID` |
| `actorFullName` | `ACTOR_FULL_NAME` (`owner/actor-name`) |
| `actorBuildId`, `actorBuildNumber`, `actorBuildTags` | `ACTOR_BUILD_ID`, `ACTOR_BUILD_NUMBER`, `ACTOR_BUILD_TAGS` (raw strings) |
| `actorEventsWsUrl` | `ACTOR_EVENTS_WS_URL`, then `ACTOR_EVENTS_WEBSOCKET_URL` |
| `actorMaxPaidDatasetItems` | `ACTOR_MAX_PAID_DATASET_ITEMS` as a number |
| `actorPermissionLevel` | `ACTOR_PERMISSION_LEVEL` |
| `apiBaseUrl` | `APIFY_API_BASE_URL`, then `SCRAPELY_API_URL` |
| `apiPublicBaseUrl` | `APIFY_API_PUBLIC_BASE_URL` |
| `token` | `APIFY_TOKEN`, then `SCRAPELY_TOKEN` |
| `isAtHome` | `APIFY_IS_AT_HOME`, then `SCRAPELY_IS_AT_HOME` (string, `'1'` on the platform) |
| `userId` | `APIFY_USER_ID` |
| `userIsPaying` | `APIFY_USER_IS_PAYING` (`'1'` or `'0'`) |
| `userPlan` | `SCRAPELY_USER_PLAN` (`'paid'` or `'free'`) |
| `defaultDatasetId`, `defaultKeyValueStoreId`, `defaultRequestQueueId` | `ACTOR_DEFAULT_*_ID` |
| `inputKey` | `ACTOR_INPUT_KEY` |
| `containerPort`, `containerUrl` | `ACTOR_WEB_SERVER_PORT` (number), `ACTOR_WEB_SERVER_URL` |
| `metaOrigin` | `APIFY_META_ORIGIN`, then `ACTOR_META_ORIGIN` (`'STANDBY'` for standby runs) |
| `memoryMbytes` | `ACTOR_MEMORY_MBYTES` as a number |
| `startedAt`, `timeoutAt` | `ACTOR_STARTED_AT`, `ACTOR_TIMEOUT_AT` as `Date` |
| `maxTotalChargeUsd` | `ACTOR_MAX_TOTAL_CHARGE_USD` as a number |
| `restartOnError` | `ACTOR_RESTART_ON_ERROR` (`'1'` or `'0'`) |
| `proxyHostname`, `proxyPort`, `proxyPassword`, `proxyStatusUrl` | `APIFY_PROXY_*` |
| `headless`, `logLevel`, `logFormat`, `localStorageDir`, `chromeExecutablePath` | `APIFY_HEADLESS`, `APIFY_LOG_LEVEL`, `APIFY_LOG_FORMAT`, `CRAWLEE_STORAGE_DIR` or `APIFY_LOCAL_STORAGE_DIR`, `APIFY_CHROME_EXECUTABLE_PATH` |

The object also has compatibility fields that Scrape.ly doesn't set (`dedicatedCpus`, `fact`, `workflowKey`, `systemInfoIntervalMillis`, `disableOutdatedWarning`, `sdkLatestVersion`, `inputSecretsPrivateKeyFile`, `inputSecretsPrivateKeyPassphrase`).

### Variables the platform injects

These are set on every platform run container. Don't set them yourself.

**Identity and storage**

| Variable | Purpose |
| --- | --- |
| `ACTOR_ID` / `ACTOR_FULL_NAME` | Current Actor ID and `owner/actor-name` |
| `ACTOR_RUN_ID` | Current run ID |
| `ACTOR_TASK_ID` | Task that started the run, if any |
| `ACTOR_BUILD_ID` / `ACTOR_BUILD_NUMBER` / `ACTOR_BUILD_TAGS` | Build this run executes |
| `ACTOR_DEFAULT_DATASET_ID` / `ACTOR_DEFAULT_KEY_VALUE_STORE_ID` / `ACTOR_DEFAULT_REQUEST_QUEUE_ID` | Default storages |
| `ACTOR_STORAGES_JSON` | Storage aliases for this run |
| `ACTOR_INPUT` | Run input as base64-encoded JSON (also stored in the `INPUT` record) |
| `ACTOR_INPUT_KEY` | `INPUT` |
| `ACTOR_METAMORPH_COUNT` | Number of metamorphs so far (after a metamorph) |

**Authentication and API**

| Variable | Purpose |
| --- | --- |
| `SCRAPELY_TOKEN` (also `APIFY_TOKEN`) | Token scoped to this run |
| `SCRAPELY_API_URL` (also `APIFY_API_BASE_URL`) | API URL the container uses (internal address) |
| `APIFY_API_PUBLIC_BASE_URL` | Public API URL, for links you hand to others |
| `SCRAPELY_IS_AT_HOME` (also `APIFY_IS_AT_HOME`) | `1` on a platform run |
| `ACTOR_PERMISSION_LEVEL` | `LIMITED_PERMISSIONS` (default) or `FULL_PERMISSIONS` |
| `APIFY_USER_ID` | User who started the run |
| `APIFY_USER_IS_PAYING` / `SCRAPELY_USER_PLAN` | `1`/`paid` or `0`/`free` |
| `SCRAPELY_LLM_BASE_URL` | [LLM gateway](https://scrape.ly/docs/llm-gateway.md) base URL (OpenRouter-compatible, ends in `/v2/llm/api/v1`). Use `SCRAPELY_TOKEN` as the API key |

**Runtime**

| Variable | Purpose |
| --- | --- |
| `CDP_URL` | [Scrape.ly browser](https://scrape.ly/docs/browser.md) endpoint. Set on every run; used for launches only when the Actor's browser setting is on |
| `ACTOR_EVENTS_WEBSOCKET_URL` | Platform events (`persistState`, `migrating`, `aborting`, `systemInfo`, `cpuInfo`) |
| `ACTOR_WEB_SERVER_PORT` / `ACTOR_WEB_SERVER_URL` | Port your HTTP server listens on (`4321`) and the URL the platform proxies to it |
| `ACTOR_META_ORIGIN` (also `APIFY_META_ORIGIN`) | `STANDBY` for standby runs |
| `ACTOR_STANDBY_URL` / `ACTOR_STANDBY_IDLE_TIMEOUT_SECS` | Standby runs only |
| `ACTOR_STARTED_AT` / `ACTOR_TIMEOUT_AT` | ISO timestamps |
| `ACTOR_MEMORY_MBYTES` / `CRAWLEE_MEMORY_MBYTES` | Memory allocation |
| `ACTOR_RESTART_ON_ERROR` | `1` if the run restarts once after failing |
| `APIFY_HEADLESS` | `1` |
| `SCRAPELY_PROXY_*` (also `APIFY_PROXY_*`) | Platform proxy credentials; see [Proxy configuration](https://scrape.ly/docs/sdk-node-proxy-events.md#proxy-configuration) |
| `SCRAPELY_BROWSER_PATCH` | `0` when the build has **Use Scrapely browser** off (the default), so browsers launch in the container |

**Pricing**

| Variable | Purpose |
| --- | --- |
| `ACTOR_MAX_TOTAL_CHARGE_USD` | Run spending cap |
| `ACTOR_MAX_PAID_DATASET_ITEMS` | Result cap (`maxItems`) |
| `APIFY_ACTOR_PRICING_INFO` (also `ACTOR_PRICING_INFO`) | Pricing as JSON |
| `APIFY_CHARGED_ACTOR_EVENT_COUNTS` (also `ACTOR_CHARGED_EVENT_COUNTS`) | Events already charged in this run |

Environment variables defined in your Actor's version settings or `environmentVariables` in the Actor config are added too. Secret inputs are decrypted on the host before the container starts; the platform doesn't provide the `APIFY_INPUT_SECRETS_PRIVATE_KEY_*` variables.

### Convenience properties

These are static getters on `Actor`:

| Getter | Type | Source |
| --- | --- | --- |
| `Actor.fullName` | `string \| undefined` | `ACTOR_FULL_NAME` |
| `Actor.taskId` | `string \| undefined` | `ACTOR_TASK_ID` |
| `Actor.buildId` | `string \| undefined` | `ACTOR_BUILD_ID` |
| `Actor.buildNumber` | `string \| undefined` | `ACTOR_BUILD_NUMBER` |
| `Actor.buildTags` | `string[]` | `ACTOR_BUILD_TAGS`, split on commas |
| `Actor.startedAt` | `string \| undefined` | `ACTOR_STARTED_AT` |
| `Actor.timeoutAt` | `string \| undefined` | `ACTOR_TIMEOUT_AT` |
| `Actor.token` | `string \| undefined` | Configured token |
| `Actor.userId` | `string \| undefined` | `APIFY_USER_ID` |
| `Actor.isPaying` | `boolean` | `APIFY_USER_IS_PAYING` |
| `Actor.apiPublicBaseUrl` | `string \| undefined` | `APIFY_API_PUBLIC_BASE_URL` |

```ts
const msLeft = Actor.getRemainingTime();
if (msLeft !== undefined && msLeft < 30_000) {
    await Actor.exit('Stopping before the timeout');
}
const custom = Actor.getenv('MY_SETTING');
```

`Actor.getRemainingTime()` returns milliseconds until `ACTOR_TIMEOUT_AT` (never below `0`), or `undefined` when there's no timeout. `Actor.getenv(key)` returns `process.env[key]`. `Actor.isAtHome()` is `true` on a platform run.

### Web server and standby metadata

There are no dedicated getters for the web server. Read the values from the configuration:

```ts
const port = Actor.config.get('containerPort');   // ACTOR_WEB_SERVER_PORT, default 4321
const url = Actor.config.get('containerUrl');     // ACTOR_WEB_SERVER_URL
const standbyUrl = Actor.config.get('standbyUrl'); // ACTOR_STANDBY_URL
const isStandby = Actor.isStandby();               // metaOrigin === 'STANDBY'
```

The SDK doesn't start an HTTP server; your code listens on `containerPort`. See [Standby mode](https://scrape.ly/docs/sdk-node-standby.md).

## Configuration

`Configuration` extends Crawlee's configuration with Scrape.ly fields. `Actor.init()` builds it from the environment; most Actors never touch it.

```ts
import { Actor } from 'scrapely';

const token = Actor.config.get('token');
Actor.config.set('persistStateIntervalMillis', 30_000);
```

To set values before the SDK reads them, pass `configuration` to `Actor.init()` or `Actor.main()` (a matching environment variable still takes precedence):

```ts
await Actor.main(async () => { /* ... */ }, {
    configuration: { persistStateIntervalMillis: 30_000 },
});
```

| Member | Purpose |
| --- | --- |
| `get(key, defaultValue?)` | Read a value |
| `set(key, value)` | Override a value at runtime |
| `getStorageClient()` / `useStorageClient(client)` | Read or replace the storage client |
| `getEventManager()` / `useEventManager(events)` | Read or replace the event manager |
| `Configuration.getGlobalConfig()` | The process-wide instance |
| `Configuration.resetGlobalState()` | Reset the process-wide instance |

Scrape.ly fields and their environment variables:

| Field | Environment | Default |
| --- | --- | --- |
| `token` | `SCRAPELY_TOKEN`, `APIFY_TOKEN` | — |
| `apiBaseUrl` | `SCRAPELY_API_URL`, `APIFY_API_BASE_URL` | Non-production URL. Set to `https://api.scrape.ly` |
| `apiPublicBaseUrl` | `APIFY_API_PUBLIC_BASE_URL` | Non-production URL. Set to `https://api.scrape.ly` |
| `isAtHome` | `SCRAPELY_IS_AT_HOME`, `APIFY_IS_AT_HOME` | — |
| `actorId`, `actorRunId`, `actorTaskId` | `ACTOR_ID`, `ACTOR_RUN_ID`, `ACTOR_TASK_ID` | — |
| `actorEventsWsUrl` | `ACTOR_EVENTS_WEBSOCKET_URL`, `APIFY_ACTOR_EVENTS_WS_URL` | — |
| `containerPort`, `containerUrl` | `ACTOR_WEB_SERVER_PORT`, `ACTOR_WEB_SERVER_URL` | `4321`, `http://localhost:4321` |
| `standbyUrl` | `ACTOR_STANDBY_URL` | — |
| `standbyPort` | `ACTOR_STANDBY_PORT` (deprecated; use `containerPort`) | `4321` |
| `metaOrigin` | `ACTOR_META_ORIGIN`, `APIFY_META_ORIGIN` | — |
| `inputKey` | `ACTOR_INPUT_KEY` | `INPUT` |
| `persistStateIntervalMillis` | `CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS` | `60000` |
| `maxTotalChargeUsd` | `ACTOR_MAX_TOTAL_CHARGE_USD` | — |
| `testPayPerEvent`, `useChargingLogDataset` | `ACTOR_TEST_PAY_PER_EVENT`, `ACTOR_USE_CHARGING_LOG_DATASET` | `false` |
| `actorStoragesJson` | `ACTOR_STORAGES_JSON` | — |
| `actorPermissionLevel` | `ACTOR_PERMISSION_LEVEL` | — |
| `userId`, `userIsPaying` | `APIFY_USER_ID`, `APIFY_USER_IS_PAYING` | — |

An environment variable that is set wins over a value passed in `configuration` or set with `set()`; defaults apply last. When several variables map to one field, they carry the same value on the platform. Crawlers use the global configuration by default; pass `Actor.config` as the second constructor argument only if you created a separate `Actor` instance:

```ts
const crawler = new CheerioCrawler({ /* ... */ }, Actor.config);
```

## Browsers and remote CDP

Where a browser runs depends on the Actor's **Use Scrapely browser** setting, which is **off by default** and applied per build. See [Scrape.ly Browser](https://scrape.ly/docs/browser.md).

| Situation | What happens |
| --- | --- |
| Setting off (`SCRAPELY_BROWSER_PATCH=0`) | Normal launch in the container, from the browsers in your image |
| Setting on, on the platform | Playwright calls `chromium.connectOverCDP(CDP_URL)`; Puppeteer calls `connect({ browserWSEndpoint })` |
| Setting on, Firefox, WebKit, or Camoufox launcher | Redirected to the Scrape.ly browser (Chromium) |
| Setting on, no CDP URL | Throws `cdpUrl is required. Local browser launch is disabled — all runs must use the remote browser.` |
| Local run, no CDP URL | Normal local launch |

Importing the SDK patches Crawlee's browser pool and the Playwright and Puppeteer launchers. The `scrapely/actor-node*` images also preload the patch through `NODE_OPTIONS`, so Actors that don't import the SDK are covered.

`CDP_URL` is set on every run, whatever the setting. `setCdpUrl(url)` overrides it and `setCdpUrl(undefined)` clears the override. Call it before the first browser launches, for example to point a local run at a CDP endpoint you host:

```ts
import { Actor, PlaywrightCrawler, setCdpUrl } from 'scrapely';

setCdpUrl('wss://browser.example.com/devtools?token=...');

const crawler = new PlaywrightCrawler({
    async requestHandler({ page }) {
        await Actor.pushData({ title: await page.title() });
    },
});
```

The Scrape.ly browser has its own exit IP and ignores your proxy URLs. Choose its location with [`createBrowserConfiguration()`](https://scrape.ly/docs/sdk-node-proxy-events.md#remote-browser-location).

## Instance API and API client

### Instance API

The class can be instantiated for code that expects an Actor object:

```ts
const actor = new Actor();
await actor.init();
const input = await actor.getInputOrThrow<{ url: string }>();
await actor.pushData({ url: input.url });
await actor.exit({ exit: false });
```

`new Actor(options?)` takes `ConfigurationOptions`. Without options it uses the global configuration. The first instance created becomes the default instance that the static methods use; `Actor.getDefaultInstance()` returns it.

Instances have the lifecycle, input, storage, status, proxy, browser, state, reboot, metamorph, abort, webhook, call, charging, `getEnv()`, `isAtHome()`, and `isStandby()` methods, plus `actor.config` and `actor.apifyClient`. These are static only: the run metadata getters, `Actor.events`, `Actor.input`, `Actor.getenv()`, `Actor.getRemainingTime()`, and `Actor.utils`.

### API client

`Actor.apifyClient` and `Actor.newClient()` return an `ApifyClient` from `scrapely-client`, the Scrape.ly JavaScript API client. The name is kept so existing code works; it's the same class as `ScrapelyClient`. See [JavaScript client](https://scrape.ly/docs/client-js.md) for its full API.

```ts
const client = Actor.newClient();
const actorInfo = await client.actor('owner~name').get();

const other = Actor.newClient({ token: process.env.OTHER_TOKEN });
```

| Call | Client |
| --- | --- |
| `Actor.apifyClient` / `Actor.newClient()` | Shared client with the run's token and `SCRAPELY_API_URL` |
| `Actor.newClient({ token, baseUrl })` | New client with that token and/or base URL |

The run's client uses the internal API URL for requests and `APIFY_API_PUBLIC_BASE_URL` for public links such as `getRecordPublicUrl()`. On pay-per-event Actors it also charges for writes to the default dataset (see [Default dataset item event](https://scrape.ly/docs/sdk-node-actors.md#default-dataset-item-event)).

`ApifyClient`, `ApifyClientOptions`, and `ActorRun` are re-exported from the SDK root.

---

- Previous: [Actors & webhooks](https://scrape.ly/docs/sdk-node-actors.md)
- Next: [Standby mode](https://scrape.ly/docs/sdk-node-standby.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
