Skip to main content

The Actor programming model

Quick Answer

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:

  1. Bootstrap: Apify starts the container, injects environment (tokens, default storages, proxy URLs if configured).
  2. Input: Your code loads input JSON (from the API, Console, or task).
  3. Work: Crawling, browser automation, API calls, transforms.
  4. Output: Rows go to the default dataset (or another named store); binaries and snapshots often go to KV.
  5. 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 is application/json; set contentType for 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)

StorageBest forTypical access
DatasetMany homogeneous records (products, posts, map pins)pushData, API dataset items
Key-value storeFiles, single JSON payloads, crawl statesetValue / getValue, named keys
Request queueCrawler 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?)

  1. Reproducible: Versioned Docker images + logged inputs make “what ran?” answerable.
  2. Operational: Logging, retries, and scheduling are platform features, not one-off shell scripts.
  3. Shareable: Store publishing reuses the same interface for strangers running your Actor.
  4. Integrable: Everything is API-first: start runs, poll status, stream dataset rows.

Actors vs. traditional scripts

AspectTraditional scriptActor
HostingYour server / laptopApify managed containers
ScalingYou provisionParallel runs per limits
StateCustom DB / filesKV + datasets + queues
ProxiesDIYFirst-class configuration
SchedulingCron on a VMBuilt-in tasks & cron
SharingGit onlyStore + input schema

Getting started

This conceptual overview pairs with the official Web Actor programming model whitepaper.

  1. Scaffold with the CLI. See Build an Apify Actor.
  2. Define INPUT_SCHEMA.json early so API and Console users stay aligned.
  3. Push data incrementally with pushData instead of holding huge arrays in memory.
Apify Affiliate Banner 728x90Apify Affiliate Banner 728x90Apify Affiliate Banner 300x50Apify Affiliate Banner 300x50
Frequently Asked Questions

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.

Apify Affiliate Banner 728x90Apify Affiliate Banner 728x90Apify Affiliate Banner 300x50Apify Affiliate Banner 300x50