The Actor programming model
The Apify Actor programming model defines how Actors read inputs (JSON), access storage (datasets and key-value stores), use proxies, and report status. It is Docker containers plus a standardized I/O interface to the Apify platform.
The Actor programming model is how Apify runs serverless automation: each Actor is an isolated container with a predictable lifecycle, typed input, and first-class storage for results and files.
What is an Actor?
An Actor is a program packaged as a Docker image that Apify schedules on shared infrastructure. Callers pass JSON input; the run writes structured rows to a dataset, optional files to a key-value store, and finishes with SUCCEEDED, FAILED, or ABORTED semantics that the API and Console surface the same way.
Tools in the Apify Store, from Google Maps to Instagram, are Actors built on this model.
Design goals
- Reproducible: Same input and platform settings should yield comparable outputs, which helps testing and audits.
- Scalable: Many runs can execute in parallel; the platform handles placement and limits per plan.
- Composable: One Actor can enqueue or call others, or hand off via webhooks and orchestrators (n8n, Make, etc.).
- Portable: Anything that runs in Linux Docker can ship; Node.js and Python are the most common SDK paths.
Actor lifecycle
Every run follows the same coarse phases:
- Bootstrap: Apify starts the container, injects environment (tokens, default storages, proxy URLs if configured).
- Input: Your code loads input JSON (from the API, Console, or task).
- Work: Crawling, browser automation, API calls, transforms.
- Output: Rows go to the default dataset (or another named store); binaries and snapshots often go to KV.
- Completion: Normal exit marks success; thrown errors or timeouts mark failure; logs and metrics remain queryable.
For interface details (events, webhooks, run schema), see Actor interface.
Input and output schema
Runtime input (JSON)
At run time you read a single JSON object. JavaScript:
import { Actor } from 'apify';
await Actor.init();
const input = (await Actor.getInput()) ?? {};
const startUrl = input.startUrl ?? 'https://example.com';
const maxPages = input.maxPages ?? 10;
Python:
import asyncio
from apify import Actor
async def main():
async with Actor:
actor_input = await Actor.get_input() or {}
start_url = actor_input.get('startUrl', 'https://example.com')
if __name__ == '__main__':
asyncio.run(main())
Declared input (INPUT_SCHEMA.json)
Publishing Actors typically includes .actor/INPUT_SCHEMA.json so the Console can render a form, validate types, and document defaults. That file is the contract for integrators; the runtime object matches those fields.
Minimal example
{
"title": "Example scraper input",
"type": "object",
"schemaVersion": 1,
"properties": {
"startUrl": { "type": "string", "description": "First page to crawl" },
"maxPages": { "type": "integer", "default": 10 }
},
"required": ["startUrl"]
}
Output: datasets and key-value store
Actor.pushData(record)appends one JSON-serializable object (or an array of them) to the run's default dataset. The Console and API expose it as JSON, CSV, Excel, and other tabular exports.Actor.setValue(key, body, { contentType })writes a blob (HTML dumps, screenshots, PDFs, checkpoint JSON) to the run's key-value store. Default content type isapplication/json; setcontentTypefor binary payloads.
Consumers pull results through the Apify API (GET /v2/datasets/{id}/items, GET /v2/key-value-stores/{id}/records/{key}) or linked integrations.
Storage types (mental model)
| Storage | Best for | Typical access |
|---|---|---|
| Dataset | Many homogeneous records (products, posts, map pins) | pushData, API dataset items |
| Key-value store | Files, single JSON payloads, crawl state | setValue / getValue, named keys |
| Request queue | Crawler frontiers (URLs + metadata) | Crawlee / SDK queue APIs |
Default storages are scoped to the run; named storages let you share data across runs when you design for it.
Proxy configuration
Actors set proxies in code so each run picks the right group (datacenter vs residential) and country. JavaScript (inside an Actor):
import { Actor } from 'apify';
import { CheerioCrawler } from 'crawlee';
await Actor.init();
const proxyConfiguration = await Actor.createProxyConfiguration({
groups: ['RESIDENTIAL'],
countryCode: 'US',
});
const crawler = new CheerioCrawler({ proxyConfiguration });
// Pass proxyConfiguration to any Crawlee crawler; it rotates per session.
// For ad-hoc requests, call await proxyConfiguration.newUrl(sessionId) and
// feed the string to fetch / got-scraping / Playwright launch options.
await Actor.exit();
Python uses the same pattern: await Actor.create_proxy_configuration(groups=['RESIDENTIAL'], country_code='US'), then hand the result to a Crawlee crawler or call await proxy_configuration.new_url() for individual requests.
Exact group names and plan limits depend on your Apify account; configure rotation and session stickiness to match the target site.
Philosophy (why this shape?)
- Reproducible: Versioned Docker images + logged inputs make “what ran?” answerable.
- Operational: Logging, retries, and scheduling are platform features, not one-off shell scripts.
- Shareable: Store publishing reuses the same interface for strangers running your Actor.
- Integrable: Everything is API-first: start runs, poll status, stream dataset rows.
Actors vs. traditional scripts
| Aspect | Traditional script | Actor |
|---|---|---|
| Hosting | Your server / laptop | Apify managed containers |
| Scaling | You provision | Parallel runs per limits |
| State | Custom DB / files | KV + datasets + queues |
| Proxies | DIY | First-class configuration |
| Scheduling | Cron on a VM | Built-in tasks & cron |
| Sharing | Git only | Store + input schema |
Getting started
This conceptual overview pairs with the official Web Actor programming model whitepaper.
- Scaffold with the CLI. See Build an Apify Actor.
- Define INPUT_SCHEMA.json early so API and Console users stay aligned.
- Push data incrementally with
pushDatainstead of holding huge arrays in memory.
It is Apify’s contract for serverless automation: Docker-packaged code that receives JSON input, uses platform-managed storage (datasets, key-value stores, queues), can use Apify proxies, and reports run status through a unified API and Console.
At runtime, code reads one JSON object via the SDK (for example Actor.getInput). For users and integrations, you declare fields and types in .actor/INPUT_SCHEMA.json so the Console renders a validated form and documentation stays accurate.
A dataset is an ordered list of JSON records, ideal for tabular export and analytics pipelines. A key-value store holds named blobs (screenshots, raw HTML, checkpoint JSON) addressed by key.
You create a proxy configuration from the Actor context, obtain rotating URLs, and pass them to your HTTP client or browser. Groups and country codes map to your Apify proxy product; combine with session options when a site requires sticky IPs.
Any language that fits in a Linux Docker image. JavaScript/TypeScript and Python have the richest Apify SDK support and examples; other stacks call the HTTP API or use thin SDK wrappers.



