Skip to main content

Build and Deploy Your First Apify Actor: Step-by-Step Tutorial (2026)

· 4 min read
Yassine El Haddad
Software Developer & Automation Specialist

I build production AI agents, web scrapers, and automation pipelines. Most of what I publish here comes from the actual problems they run into: proxies that get banned, anti-bot stacks that fingerprint your client, RAG that drifts when the underlying data moves. Stack: Python, TypeScript, Go, FastAPI, LangChain, Crawlee, Playwright, deployed on AWS, GCP, and Cloudflare.

An Apify Actor is a serverless scraper or automation packaged for cloud execution. You write standard Node.js code, push it to Apify, and it runs on demand — with built-in proxies, storage, scheduling, and API access included.

This tutorial takes you from an empty folder to a deployed, runnable Actor in about 20 minutes.

Freshness note: Steps verified with Apify CLI 3.x and Apify SDK 3.x (March 2026).

Prerequisites

  • Node.js 18+ installed
  • Apify account (free tier works)
  • npm install -g apify-cli

Step 1: Scaffold the Actor

apify create my-first-actor

Choose the "Getting started example" template. This gives you:

my-first-actor/
├── src/
│ └── main.js # Your scraper logic
├── .actor/
│ ├── actor.json # Metadata: name, version, description
│ └── INPUT_SCHEMA.json # Input definition (rendered as UI form)
├── package.json
└── README.md

Step 2: Write Extraction Logic

Replace src/main.js with a real scraper. Here's a Hacker News scraper using Crawlee:

import { Actor } from 'apify';
import { CheerioCrawler } from 'crawlee';

await Actor.init();

const input = await Actor.getInput() ?? {};
const { maxItems = 30 } = input;

const crawler = new CheerioCrawler({
maxRequestsPerCrawl: 5,
async requestHandler({ $, request, enqueueLinks }) {
const stories = [];
$('.athing').each((_, el) => {
stories.push({
rank: $(el).find('.rank').text().replace('.', ''),
title: $(el).find('.titleline a').first().text(),
url: $(el).find('.titleline a').first().attr('href'),
source: request.url,
});
});

const limited = stories.slice(0, maxItems);
await Actor.pushData(limited);

if (limited.length === maxItems) return; // Hit limit
await enqueueLinks({ selector: 'a.morelink' });
},
});

await crawler.run(['https://news.ycombinator.com/']);
await Actor.exit();

Step 3: Define the Input Schema

Edit .actor/INPUT_SCHEMA.json to give your Actor a form-based UI in Apify Console:

{
"title": "Hacker News Scraper Input",
"type": "object",
"schemaVersion": 1,
"properties": {
"maxItems": {
"title": "Max stories to scrape",
"type": "integer",
"description": "Maximum number of HN stories to collect.",
"default": 30,
"minimum": 1,
"maximum": 500
}
}
}

Step 4: Test Locally

cd my-first-actor
npm install
apify run

Crawlee stores results in ./storage/datasets/default/. Inspect with:

ls storage/datasets/default/
cat storage/datasets/default/000000001.json

Step 5: Deploy to Apify Cloud

apify login # Authenticate once
apify push # Build and deploy to your account

After pushing, go to console.apify.com → Actors → Your Actor. Click Start to run it in the cloud.


Step 6: Run via API

Every Actor gets a REST API endpoint:

curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~my-first-actor/runs?fpr=use-apify" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{"maxItems": 50}'

Retrieve results:

curl "https://api.apify.com/v2/acts/YOUR_USERNAME~my-first-actor/runs/last/dataset/items?fpr=use-apify" \
-H "Authorization: Bearer YOUR_API_TOKEN"

Step 7: Schedule Runs

In Apify Console → Schedules → Create Schedule:

  • Cron: 0 9 * * 1 (every Monday at 9 AM UTC)
  • Target: your Actor
  • Input: { "maxItems": 100 }

Results are stored in Apify Dataset and optionally pushed via webhook to Slack, Google Sheets, or any HTTP endpoint.


Step 8: Publish to Apify Store

Sharing your Actor publicly generates royalty income each time another user runs it.

  1. In .actor/actor.json, set "isPublic": true
  2. Write a thorough README.md (shown as the Store listing)
  3. Run apify push again

Monetized Actors receive a share of revenue from platform credits consumed by external runs.


Actor Pricing (What It Costs to Run)

Actors are billed in Compute Units (CUs). The free tier includes $5/month (~5 CUs):

Actor typeCU per 1,000 pages
CheerioCrawler~0.1 CU
PlaywrightCrawler~1 CU
Puppeteer~1 CU

For the Hacker News scraper above (30 stories), cost is essentially zero. A site with 10,000 pages using Playwright costs roughly $0.50–$1.

See full pricing →


Summary

StepCommand
Scaffoldapify create my-first-actor
Test locallyapify run
Loginapify login
Deployapify push
Run via APIPOST /v2/acts/{actorId}/runs
ScheduleApify Console → Schedules

Common mistakes and fixes

Actor fails with 'Input validation error' on first run.

Check your INPUT_SCHEMA.json against the actual input payload. All required fields must be present. Use the Actor console test run to inspect the exact input received.

Actor exits with exit code 1 but no error in logs.

Wrap your main logic in a try/catch and call Actor.fail(error.message) explicitly. Uncaught Promise rejections cause silent exit code 1.

Dataset is empty after a successful run.

Ensure you await Actor.pushData(). Synchronous calls won't complete. Also verify the Actor.exit() call happens after all pushData calls.