Actor Programming Interface
An Apify Actor's programming interface is the contract every Actor honors: it reads a single JSON input (the INPUT record in the default key-value store), processes it inside a Docker container, and writes results to three run-scoped storages. 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 element | What it is | How you access it |
|---|---|---|
| Input | One JSON object, shape declared by an input schema | Actor.getInput() / Actor.get_input() |
| Dataset | Append-only table of JSON output records | Actor.pushData(record) |
| Key-value store | Named blobs: INPUT, OUTPUT, screenshots, checkpoints | Actor.setValue(key, body) / Actor.getValue(key) |
| Request queue | URL frontier for crawls | Actor.openRequestQueue() |
| Environment variables | Run context (ACTOR_*) plus credentials (APIFY_*) | process.env (read for you by the SDK) |
| Run status | Outcome derived from how the process exits | Actor.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) orActor.get_input()(Python). It's stored asINPUTin 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
inputfield in.actor/actor.json, either inline or as a path to a JSON file. A standalone.actor/INPUT_SCHEMA.jsonstill works but is the deprecated form.
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:
| Variable | What it holds |
|---|---|
ACTOR_RUN_ID | ID of the current run |
ACTOR_DEFAULT_DATASET_ID | ID of the run's default dataset |
ACTOR_DEFAULT_KEY_VALUE_STORE_ID | ID of the run's default key-value store |
ACTOR_DEFAULT_REQUEST_QUEUE_ID | ID of the run's default request queue |
ACTOR_INPUT_KEY | Key in the default key-value store that holds the input (defaults to INPUT) |
ACTOR_MEMORY_MBYTES | Memory limit allocated to the run |
APIFY_TOKEN | API token scoped to the run, used for API and Actor.call |
APIFY_PROXY_PASSWORD | Password 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 saveuseState/ 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, andActor.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.
| Storage | Typical content | Primary SDK calls |
|---|---|---|
| Dataset | Append-only JSON records (one row per item) | Actor.pushData(record) / Actor.open_dataset() |
| Key-value store | Named blobs: INPUT, OUTPUT, HTML snapshots, screenshots, checkpoints | Actor.setValue(key, body, { contentType }), Actor.getValue(key) |
| Request queue | Frontier of URLs + metadata for a crawl | Actor.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, statusSUCCEEDED, optional status message shown in Console and webhook payloads.Actor.fail([message]): terminates with statusFAILEDand the message stored on the run.- Uncaught exception or non-zero exit: status
FAILED. - Platform-initiated abort (user, timeout, memory): status
ABORTEDorTIMED-OUT; may be preceded by anabortingevent 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}`);
}
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. A standalone `.actor/INPUT_SCHEMA.json` file still works but is the deprecated form. 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.



