1# Apify Actors Development Guide
2
3Important: Before you begin, fill in the `generatedBy` property in the meta section of `.actor/actor.json`. Replace it with the model you're currently using. This helps Apify monitor and improve AGENTS.md for specific LLM models.
4
5## What are Apify Actors?
6
7- Actors are serverless programs that run in the cloud. They're inspired by the UNIX philosophy - programs that do one thing well and can be easily combined to build complex systems.
8- Actors are programs packaged as Docker images that run in isolated containers
9
10## Core Concepts
11
12- Accept well-defined JSON input
13- Perform isolated tasks (web scraping, automation, data processing)
14- Produce structured JSON output to datasets and/or store data in key-value stores
15- Can run from seconds to hours or even indefinitely
16- Persist state and can be restarted
17
18## Do
19
20- accept well-defined JSON input and produce structured JSON output
21- use Apify SDK (`apify`) for code running ON Apify platform
22- validate input early with proper error handling and fail gracefully
23- use CheerioCrawler for static HTML content (10x faster than browsers)
24- use PlaywrightCrawler only for JavaScript-heavy sites and dynamic content
25- use router pattern (createCheerioRouter/createPlaywrightRouter) for complex crawls
26- implement retry strategies with exponential backoff for failed requests
27- use proper concurrency settings (HTTP: 10-50, Browser: 1-5)
28- set sensible defaults in `.actor/input_schema.json` for all optional fields
29- set up output schema in `.actor/output_schema.json`
30- clean and validate data before pushing to dataset
31- use semantic CSS selectors and fallback strategies for missing elements
32- respect robots.txt, ToS, and implement rate limiting with delays
33- check which tools (cheerio/playwright/crawlee) are installed before applying guidance
34- use `apify/log` package for logging (censors sensitive data)
35- implement readiness probe handler for standby Actors
36- handle the `aborting` event to gracefully shut down when Actor is stopped
37
38## Don't
39
40- do not rely on `Dataset.getInfo()` for final counts on Cloud platform
41- do not use browser crawlers when HTTP/Cheerio works (massive performance gains with HTTP)
42- do not hard code values that should be in input schema or environment variables
43- do not skip input validation or error handling
44- do not overload servers - use appropriate concurrency and delays
45- do not scrape prohibited content or ignore Terms of Service
46- do not store personal/sensitive data unless explicitly permitted
47- do not use deprecated options like `requestHandlerTimeoutMillis` on CheerioCrawler (v3.x)
48- do not use `additionalHttpHeaders` - use `preNavigationHooks` instead
49- do not disable standby mode (`usesStandbyMode: false`) without explicit permission
50
51## Logging
52
53- **ALWAYS use the `apify/log` package for logging** - This package contains critical security logic including censoring sensitive data (Apify tokens, API keys, credentials) to prevent accidental exposure in logs
54
55### Available Log Levels in `apify/log`
56
57The Apify log package provides the following methods for logging:
58
59- `log.debug()` - Debug level logs (detailed diagnostic information)
60- `log.info()` - Info level logs (general informational messages)
61- `log.warning()` - Warning level logs (warning messages for potentially problematic situations)
62- `log.warningOnce()` - Warning level logs (same warning message logged only once)
63- `log.error()` - Error level logs (error messages for failures)
64- `log.exception()` - Exception level logs (for exceptions with stack traces)
65- `log.perf()` - Performance level logs (performance metrics and timing information)
66- `log.deprecated()` - Deprecation level logs (warnings about deprecated code)
67- `log.softFail()` - Soft failure logs (non-critical failures that don't stop execution, e.g., input validation errors, skipped items)
68- `log.internal()` - Internal level logs (internal/system messages)
69
70**Best practices:**
71
72- Use `log.debug()` for detailed operation-level diagnostics (inside functions)
73- Use `log.info()` for general informational messages (API requests, successful operations)
74- Use `log.warning()` for potentially problematic situations (validation failures, unexpected states)
75- Use `log.error()` for actual errors and failures
76- Use `log.exception()` for caught exceptions with stack traces
77
78## Graceful Abort Handling
79
80Handle the `aborting` event to terminate the Actor quickly when stopped by user or platform, minimizing costs especially for PPU/PPE+U billing.
81
82```typescript
83Actor.on('aborting', async () => {
84 // Persist any state, do any cleanup you need, and terminate the Actor using `await Actor.exit()` explicitly as soon as possible
85 // This will help ensure that the Actor is doing best effort to honor any potential limits on costs of a single run set by the user
86 // Wait 1 second to allow Crawlee/SDK useState and other state persistence operations to complete
87 // This is a temporary workaround until SDK implements proper state persistence in the aborting event
88 await new Promise((resolve) => setTimeout(resolve, 1000));
89 await Actor.exit();
90});
91```
92
93## Standby Mode
94
95- **NEVER disable standby mode (`usesStandbyMode: false`) in `.actor/actor.json` without explicit permission** - Actor Standby mode solves this problem by letting you have the Actor ready in the background, waiting for the incoming HTTP requests. In a sense, the Actor behaves like a real-time web server or standard API server instead of running the logic once to process everything in batch. Always keep `usesStandbyMode: true` unless there is a specific documented reason to disable it
96- **ALWAYS implement readiness probe handler for standby Actors** - Handle the `x-apify-container-server-readiness-probe` header at GET / endpoint to ensure proper Actor lifecycle management
97
98You can recognize a standby Actor by checking the `usesStandbyMode` property in `.actor/actor.json`. Only implement the readiness probe if this property is set to `true`.
99
100### Readiness Probe Implementation Example
101
102```typescript
103// Apify standby readiness probe at root path
104app.get('/', (req: Request, res: Response) => {
105 res.writeHead(200, { 'Content-Type': 'text/plain' });
106 if (req.headers['x-apify-container-server-readiness-probe']) {
107 res.end('Readiness probe OK\n');
108 } else {
109 res.end('Actor is ready\n');
110 }
111});
112```
113
114Key points:
115
116- Detect the `x-apify-container-server-readiness-probe` header in incoming requests
117- Respond with HTTP 200 status code for both readiness probe and normal requests
118- This enables proper Actor lifecycle management in standby mode
119
120## Commands
121
122```bash
123# Local development
124apify run # Run Actor locally
125
126# Authentication & deployment
127apify login # Authenticate account
128apify push # Deploy to Apify platform
129
130# Help
131apify help # List all commands
132```
133
134## Safety and Permissions
135
136Allowed without prompt:
137
138- read files with `Actor.getValue()`
139- push data with `Actor.pushData()`
140- set values with `Actor.setValue()`
141- enqueue requests to RequestQueue
142- run locally with `apify run`
143
144Ask first:
145
146- npm/pip package installations
147- apify push (deployment to cloud)
148- proxy configuration changes (requires paid plan)
149- Dockerfile changes affecting builds
150- deleting datasets or key-value stores
151
152## Project Structure
153
154.actor/
155├── actor.json # Actor config: name, version, env vars, runtime settings
156├── input_schema.json # Input validation & Console form definition
157└── output_schema.json # Specifies where an Actor stores its output
158src/
159└── main.js # Actor entry point and orchestrator
160storage/ # Local storage (mirrors Cloud during development)
161├── datasets/ # Output items (JSON objects)
162├── key_value_stores/ # Files, config, INPUT
163└── request_queues/ # Pending crawl requests
164Dockerfile # Container image definition
165AGENTS.md # AI agent instructions (this file)
166
167## Actor Input Schema
168
169The input schema defines the input parameters for an Actor. It's a JSON object comprising various field types supported by the Apify platform.
170
171### Structure
172
173```json
174{
175 "title": "<INPUT-SCHEMA-TITLE>",
176 "type": "object",
177 "schemaVersion": 1,
178 "properties": {
179 /* define input fields here */
180 },
181 "required": []
182}
183```
184
185### Example
186
187```json
188{
189 "title": "E-commerce Product Scraper Input",
190 "type": "object",
191 "schemaVersion": 1,
192 "properties": {
193 "startUrls": {
194 "title": "Start URLs",
195 "type": "array",
196 "description": "URLs to start scraping from (category pages or product pages)",
197 "editor": "requestListSources",
198 "default": [{ "url": "https://example.com/category" }],
199 "prefill": [{ "url": "https://example.com/category" }]
200 },
201 "followVariants": {
202 "title": "Follow Product Variants",
203 "type": "boolean",
204 "description": "Whether to scrape product variants (different colors, sizes)",
205 "default": true
206 },
207 "maxRequestsPerCrawl": {
208 "title": "Max Requests per Crawl",
209 "type": "integer",
210 "description": "Maximum number of pages to scrape (0 = unlimited)",
211 "default": 1000,
212 "minimum": 0
213 },
214 "proxyConfiguration": {
215 "title": "Proxy Configuration",
216 "type": "object",
217 "description": "Proxy settings for anti-bot protection",
218 "editor": "proxy",
219 "default": { "useApifyProxy": false }
220 },
221 "locale": {
222 "title": "Locale",
223 "type": "string",
224 "description": "Language/country code for localized content",
225 "default": "cs",
226 "enum": ["cs", "en", "de", "sk"],
227 "enumTitles": ["Czech", "English", "German", "Slovak"]
228 }
229 },
230 "required": ["startUrls"]
231}
232```
233
234## Actor Output Schema
235
236The Actor output schema builds upon the schemas for the dataset and key-value store. It specifies where an Actor stores its output and defines templates for accessing that output. Apify Console uses these output definitions to display run results.
237
238### Structure
239
240```json
241{
242 "actorOutputSchemaVersion": 1,
243 "title": "<OUTPUT-SCHEMA-TITLE>",
244 "properties": {
245 /* define your outputs here */
246 }
247}
248```
249
250### Example
251
252```json
253{
254 "actorOutputSchemaVersion": 1,
255 "title": "Output schema of the files scraper",
256 "properties": {
257 "files": {
258 "type": "string",
259 "title": "Files",
260 "template": "{{links.apiDefaultKeyValueStoreUrl}}/keys"
261 },
262 "dataset": {
263 "type": "string",
264 "title": "Dataset",
265 "template": "{{links.apiDefaultDatasetUrl}}/items"
266 }
267 }
268}
269```
270
271### Output Schema Template Variables
272
273- `links` (object) - Contains quick links to most commonly used URLs
274- `links.publicRunUrl` (string) - Public run url in format `https://console.apify.com/view/runs/:runId`
275- `links.consoleRunUrl` (string) - Console run url in format `https://console.apify.com/actors/runs/:runId`
276- `links.apiRunUrl` (string) - API run url in format `https://api.apify.com/v2/actor-runs/:runId`
277- `links.apiDefaultDatasetUrl` (string) - API url of default dataset in format `https://api.apify.com/v2/datasets/:defaultDatasetId`
278- `links.apiDefaultKeyValueStoreUrl` (string) - API url of default key-value store in format `https://api.apify.com/v2/key-value-stores/:defaultKeyValueStoreId`
279- `links.containerRunUrl` (string) - URL of a webserver running inside the run in format `https://<containerId>.runs.apify.net/`
280- `run` (object) - Contains information about the run same as it is returned from the `GET Run` API endpoint
281- `run.defaultDatasetId` (string) - ID of the default dataset
282- `run.defaultKeyValueStoreId` (string) - ID of the default key-value store
283
284## Dataset Schema Specification
285
286The dataset schema defines how your Actor's output data is structured, transformed, and displayed in the Output tab in the Apify Console.
287
288### Example
289
290Consider an example Actor that calls `Actor.pushData()` to store data into dataset:
291
292```typescript
293import { Actor } from 'apify';
294// Initialize the JavaScript SDK
295await Actor.init();
296
297/**
298 * Actor code
299 */
300await Actor.pushData({
301 numericField: 10,
302 pictureUrl: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png',
303 linkUrl: 'https://google.com',
304 textField: 'Google',
305 booleanField: true,
306 dateField: new Date(),
307 arrayField: ['#hello', '#world'],
308 objectField: {},
309});
310
311// Exit successfully
312await Actor.exit();
313```
314
315To set up the Actor's output tab UI, reference a dataset schema file in `.actor/actor.json`:
316
317```json
318{
319 "actorSpecification": 1,
320 "name": "book-library-scraper",
321 "title": "Book Library Scraper",
322 "version": "1.0.0",
323 "storages": {
324 "dataset": "./dataset_schema.json"
325 }
326}
327```
328
329Then create the dataset schema in `.actor/dataset_schema.json`:
330
331```json
332{
333 "actorSpecification": 1,
334 "fields": {},
335 "views": {
336 "overview": {
337 "title": "Overview",
338 "transformation": {
339 "fields": [
340 "pictureUrl",
341 "linkUrl",
342 "textField",
343 "booleanField",
344 "arrayField",
345 "objectField",
346 "dateField",
347 "numericField"
348 ]
349 },
350 "display": {
351 "component": "table",
352 "properties": {
353 "pictureUrl": {
354 "label": "Image",
355 "format": "image"
356 },
357 "linkUrl": {
358 "label": "Link",
359 "format": "link"
360 },
361 "textField": {
362 "label": "Text",
363 "format": "text"
364 },
365 "booleanField": {
366 "label": "Boolean",
367 "format": "boolean"
368 },
369 "arrayField": {
370 "label": "Array",
371 "format": "array"
372 },
373 "objectField": {
374 "label": "Object",
375 "format": "object"
376 },
377 "dateField": {
378 "label": "Date",
379 "format": "date"
380 },
381 "numericField": {
382 "label": "Number",
383 "format": "number"
384 }
385 }
386 }
387 }
388 }
389}
390```
391
392### Structure
393
394```json
395{
396 "actorSpecification": 1,
397 "fields": {},
398 "views": {
399 "<VIEW_NAME>": {
400 "title": "string (required)",
401 "description": "string (optional)",
402 "transformation": {
403 "fields": ["string (required)"],
404 "unwind": ["string (optional)"],
405 "flatten": ["string (optional)"],
406 "omit": ["string (optional)"],
407 "limit": "integer (optional)",
408 "desc": "boolean (optional)"
409 },
410 "display": {
411 "component": "table (required)",
412 "properties": {
413 "<FIELD_NAME>": {
414 "label": "string (optional)",
415 "format": "text|number|date|link|boolean|image|array|object (optional)"
416 }
417 }
418 }
419 }
420 }
421}
422```
423
424**Dataset Schema Properties:**
425
426- `actorSpecification` (integer, required) - Specifies the version of dataset schema structure document (currently only version 1)
427- `fields` (JSONSchema object, required) - Schema of one dataset object (use JsonSchema Draft 2020-12 or compatible)
428- `views` (DatasetView object, required) - Object with API and UI views description
429
430**DatasetView Properties:**
431
432- `title` (string, required) - Visible in UI Output tab and API
433- `description` (string, optional) - Only available in API response
434- `transformation` (ViewTransformation object, required) - Data transformation applied when loading from Dataset API
435- `display` (ViewDisplay object, required) - Output tab UI visualization definition
436
437**ViewTransformation Properties:**
438
439- `fields` (string[], required) - Fields to present in output (order matches column order)
440- `unwind` (string[], optional) - Deconstructs nested children into parent object
441- `flatten` (string[], optional) - Transforms nested object into flat structure
442- `omit` (string[], optional) - Removes specified fields from output
443- `limit` (integer, optional) - Maximum number of results (default: all)
444- `desc` (boolean, optional) - Sort order (true = newest first)
445
446**ViewDisplay Properties:**
447
448- `component` (string, required) - Only `table` is available
449- `properties` (Object, optional) - Keys matching `transformation.fields` with ViewDisplayProperty values
450
451**ViewDisplayProperty Properties:**
452
453- `label` (string, optional) - Table column header
454- `format` (string, optional) - One of: `text`, `number`, `date`, `link`, `boolean`, `image`, `array`, `object`
455
456## Key-Value Store Schema Specification
457
458The key-value store schema organizes keys into logical groups called collections for easier data management.
459
460### Example
461
462Consider an example Actor that calls `Actor.setValue()` to save records into the key-value store:
463
464```typescript
465import { Actor } from 'apify';
466// Initialize the JavaScript SDK
467await Actor.init();
468
469/**
470 * Actor code
471 */
472await Actor.setValue('document-1', 'my text data', { contentType: 'text/plain' });
473
474await Actor.setValue(`image-${imageID}`, imageBuffer, { contentType: 'image/jpeg' });
475
476// Exit successfully
477await Actor.exit();
478```
479
480To configure the key-value store schema, reference a schema file in `.actor/actor.json`:
481
482```json
483{
484 "actorSpecification": 1,
485 "name": "data-collector",
486 "title": "Data Collector",
487 "version": "1.0.0",
488 "storages": {
489 "keyValueStore": "./key_value_store_schema.json"
490 }
491}
492```
493
494Then create the key-value store schema in `.actor/key_value_store_schema.json`:
495
496```json
497{
498 "actorKeyValueStoreSchemaVersion": 1,
499 "title": "Key-Value Store Schema",
500 "collections": {
501 "documents": {
502 "title": "Documents",
503 "description": "Text documents stored by the Actor",
504 "keyPrefix": "document-"
505 },
506 "images": {
507 "title": "Images",
508 "description": "Images stored by the Actor",
509 "keyPrefix": "image-",
510 "contentTypes": ["image/jpeg"]
511 }
512 }
513}
514```
515
516### Structure
517
518```json
519{
520 "actorKeyValueStoreSchemaVersion": 1,
521 "title": "string (required)",
522 "description": "string (optional)",
523 "collections": {
524 "<COLLECTION_NAME>": {
525 "title": "string (required)",
526 "description": "string (optional)",
527 "key": "string (conditional - use key OR keyPrefix)",
528 "keyPrefix": "string (conditional - use key OR keyPrefix)",
529 "contentTypes": ["string (optional)"],
530 "jsonSchema": "object (optional)"
531 }
532 }
533}
534```
535
536**Key-Value Store Schema Properties:**
537
538- `actorKeyValueStoreSchemaVersion` (integer, required) - Version of key-value store schema structure document (currently only version 1)
539- `title` (string, required) - Title of the schema
540- `description` (string, optional) - Description of the schema
541- `collections` (Object, required) - Object where each key is a collection ID and value is a Collection object
542
543**Collection Properties:**
544
545- `title` (string, required) - Collection title shown in UI tabs
546- `description` (string, optional) - Description appearing in UI tooltips
547- `key` (string, conditional) - Single specific key for this collection
548- `keyPrefix` (string, conditional) - Prefix for keys included in this collection
549- `contentTypes` (string[], optional) - Allowed content types for validation
550- `jsonSchema` (object, optional) - JSON Schema Draft 07 format for `application/json` content type validation
551
552Either `key` or `keyPrefix` must be specified for each collection, but not both.
553
554## Apify MCP Tools
555
556If MCP server is configured, use these tools for documentation:
557
558- `search-apify-docs` - Search documentation
559- `fetch-apify-docs` - Get full doc pages
560
561Otherwise, reference: `@https://mcp.apify.com/`
562
563## Resources
564
565- [docs.apify.com/llms.txt](https://docs.apify.com/llms.txt) - Quick reference
566- [docs.apify.com/llms-full.txt](https://docs.apify.com/llms-full.txt) - Complete docs
567- [crawlee.dev](https://crawlee.dev) - Crawlee documentation
568- [whitepaper.actor](https://raw.githubusercontent.com/apify/actor-whitepaper/refs/heads/master/README.md) - Complete Actor specification