Skip to main content

The Actor Interface: Input, Storage, and Exiting Cleanly

Call process.exit() instead of Actor.exit() and the run still finishes with status SUCCEEDED, because the platform reads the process exit code, not how you got there. What you lose is whatever was still in flight when the process died: buffered dataset writes, persisted state, the status message Console would have shown. The run looks clean. Nobody notices until someone asks why the dataset is short.

That gap between "exited" and "exited cleanly" is what the programming interface exists to close. It's the contract every Actor honors: read a single JSON input (the INPUT record in the default key-value store), process it inside a Docker container, and write results to three run-scoped storages, then exit through the SDK so the platform knows the run is actually done. The lifecycle is Input → Processing → Output → Exit, and each stage maps to exact SDK calls.

The whole interface reduces to a handful of elements you read or write through the SDK:

Interface elementWhat it isHow you access it
InputOne JSON object, shape declared by an input schemaActor.getInput() / Actor.get_input()
DatasetAppend-only table of JSON output recordsActor.pushData(record)
Key-value storeNamed blobs: INPUT, OUTPUT, screenshots, checkpointsActor.setValue(key, body) / Actor.getValue(key)
Request queueURL frontier for crawlsActor.openRequestQueue()
Environment variablesRun context (ACTOR_*) plus credentials (APIFY_*)process.env (read for you by the SDK)
Run statusOutcome derived from how the process exitsActor.exit() / Actor.fail()

For a wider view of how these pieces fit the platform, see the Actor programming model overview and the Apify storage guide. To put it into practice, follow build your first Actor. Actors can also serve responses over HTTP through Standby mode.

1. Input​

An Actor receives a single JSON object at startup. Two things describe it:

  • Runtime input: the JSON object your code reads with Actor.getInput() (JS) or Actor.get_input() (Python). It's stored as INPUT in the run's default key-value store.
  • Input schema: a JSON Schema definition the Console uses to render a form, validate types, apply defaults, and mark secret fields (stored encrypted, decrypted only at run time). Set it via the input field in .actor/actor.json, either inline or as a path to a JSON file. If that field is omitted, Apify falls back to a standalone .actor/INPUT_SCHEMA.json (or INPUT_SCHEMA.json), which still works but is the legacy location.
import { Actor } from 'apify';

await Actor.init();
const input = (await Actor.getInput()) ?? {};
const { startUrls = [], maxPages = 100, apiToken } = input; // apiToken may be a secret field

Fields marked "isSecret": true in the schema (available on textfield, textarea, hidden, and json editors) are encrypted at rest and in the Console UI; they arrive decrypted in getInput().

2. Processing​

Your code runs inside a Docker container that Apify boots with a set of environment variables. The current programming interface exposes run context under the ACTOR_* prefix, while credentials and proxy settings keep the older APIFY_* prefix. The SDK reads these for you, so you rarely touch them directly, but knowing the names helps when you debug or shell into a container:

VariableWhat it holds
ACTOR_RUN_IDID of the current run
ACTOR_DEFAULT_DATASET_IDID of the run's default dataset
ACTOR_DEFAULT_KEY_VALUE_STORE_IDID of the run's default key-value store
ACTOR_DEFAULT_REQUEST_QUEUE_IDID of the run's default request queue
ACTOR_INPUT_KEYKey in the default key-value store that holds the input (defaults to INPUT)
ACTOR_MEMORY_MBYTESMemory limit allocated to the run
APIFY_TOKENAPI token scoped to the run, used for API and Actor.call
APIFY_PROXY_PASSWORDPassword for Apify Proxy

Between Actor.init() and Actor.exit() you also have access to:

  • Crawlee crawlers and ProxyConfiguration for request handling and rotation.
  • Platform events via Actor.on(eventName, handler):
    • migrating: the worker is about to move your run; persist state immediately.
    • aborting: a user aborted the run with "graceful abort"; flush work.
    • persistState: periodic (by default every 60 s) signal to save useState / session pool state.
    • systemInfo: memory and CPU telemetry for self-throttling.
  • Outgoing calls: Actor.call(actorId, input), Actor.callTask(taskId, input), Actor.start(actorId, input) to orchestrate other Actors, and Actor.addWebhook({ eventTypes, requestUrl, payloadTemplate }) to register run-scoped webhooks.
Actor.on('migrating', async () => {
await Actor.setValue('CHECKPOINT', { processedAt: Date.now() });
});

The Python SDK exposes the same surface (Actor.on, Actor.call, Actor.add_webhook) inside an async with Actor: block.

3. Output​

An Actor has three storages, all scoped to the run by default and addressable by name for cross-run sharing.

StorageTypical contentPrimary SDK calls
DatasetAppend-only JSON records (one row per item)Actor.pushData(record) / Actor.open_dataset()
Key-value storeNamed blobs: INPUT, OUTPUT, HTML snapshots, screenshots, checkpointsActor.setValue(key, body, { contentType }), Actor.getValue(key)
Request queueFrontier of URLs + metadata for a crawlActor.openRequestQueue(), then addRequests / handled implicitly by Crawlee

pushData accepts a single object or an array; it enforces JSON-serializability and attaches the record to the dataset named in ACTOR_DEFAULT_DATASET_ID. setValue accepts strings, Buffers, or JSON. Pass contentType for anything other than JSON.

4. Exit and run status​

Run status is derived from how the process ends:

  • Actor.exit([message]): graceful termination, status SUCCEEDED, optional status message shown in Console and webhook payloads.
  • Actor.fail([message]): terminates with status FAILED and the message stored on the run.
  • Uncaught exception or non-zero exit: status FAILED.
  • Platform-initiated abort (user, timeout, memory): status ABORTED or TIMED-OUT; may be preceded by an aborting event for graceful cleanup.

Always prefer Actor.exit / Actor.fail over raw process.exit: they flush buffered dataset writes, persist final state, and attach the status message that external webhooks consume.

try {
await run();
await Actor.exit('Scrape complete: 1,204 items.');
} catch (err) {
await Actor.fail(`Unrecoverable error: ${err.message}`);
}
Frequently Asked Questions

Input is a single JSON object stored as the `INPUT` key in the run's default key-value store; its shape is declared in `.actor/INPUT_SCHEMA.json` (JSON Schema). Output uses three storages: datasets for append-only JSON records (`Actor.pushData`), key-value stores for named blobs like screenshots or checkpoints (`Actor.setValue`), and request queues for crawl frontiers (`Actor.openRequestQueue`).

Wrap your main logic in try/catch and call `Actor.fail(message)` on unrecoverable errors. This sets the run status to FAILED with a message surfaced in Console and webhooks. Uncaught exceptions also produce FAILED but without a custom message. Listen for the `aborting` and `migrating` events via `Actor.on(...)` to flush state before the platform stops the container.

Datasets (ordered JSON records, `Actor.pushData`), key-value stores (named blobs addressed by key, `Actor.setValue` / `Actor.getValue`), and request queues (URL frontiers for crawlers, `Actor.openRequestQueue`). Each run gets default instances of all three, and you can open named storages by passing a name to `Actor.openDataset(name)` / `Actor.openKeyValueStore(name)` to share data across runs.

Run context is exposed under the `ACTOR_*` prefix: `ACTOR_RUN_ID`, `ACTOR_DEFAULT_DATASET_ID`, `ACTOR_DEFAULT_KEY_VALUE_STORE_ID`, `ACTOR_DEFAULT_REQUEST_QUEUE_ID`, `ACTOR_INPUT_KEY` (the key holding the input, default `INPUT`), and `ACTOR_MEMORY_MBYTES`. Credentials keep the older `APIFY_*` prefix, such as `APIFY_TOKEN` and `APIFY_PROXY_PASSWORD`. The SDK reads these automatically, so most code uses `Actor.getInput()` and `Actor.pushData()` instead of touching them directly.

Set the `input` field in `.actor/actor.json`, either as an inline JSON Schema object or a path to a JSON file. If that field is omitted, Apify falls back to a standalone `.actor/INPUT_SCHEMA.json` file, which still works but is the legacy location. The schema renders the Console input form, validates types, applies defaults, and marks secret fields with `"isSecret": true` so they are encrypted at rest and decrypted only inside the run.

Input → Processing → Output → Exit. Between `Actor.init()` and `Actor.exit()`, the SDK emits events you can subscribe to with `Actor.on`: `migrating` (persist state before the run moves), `aborting` (graceful shutdown requested), `persistState` (periodic save signal, default every 60 seconds), and `systemInfo` (CPU/memory telemetry). Exit status comes from `Actor.exit()` / `Actor.fail()` or the process exit code.