Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@
{
"group": "Example tasks",
"pages": [
"guides/examples/apify-actor-run",
"guides/examples/dall-e3-generate-image",
"guides/examples/deepgram-transcribe-audio",
"guides/examples/fal-ai-image-to-cartoon",
Expand Down
221 changes: 221 additions & 0 deletions docs/guides/examples/apify-actor-run.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
---
title: "Run Apify Actors with Trigger.dev"
sidebarTitle: "Apify Actor run"
description: "These examples demonstrate how to run an Apify Actor from a Trigger.dev task, either waiting for the results or resuming the run with a webhook."
---

import ScrapingWarning from "/snippets/web-scraping-warning.mdx";

## Overview

[Apify](https://apify.com) is a platform for web scraping and browser automation. Its [Apify Store](https://apify.com/store) has thousands of ready-made scrapers, called Actors, for sites like Google Maps, Amazon and Instagram. Actors run on Apify's infrastructure and proxies, so your task calls the Apify API instead of scraping from Trigger.dev's servers.

Here are two ways to run an Actor from a Trigger.dev task:

- [Example 1](#example-1-run-an-actor-and-wait-for-the-results): start the Actor and wait for it to finish. Use this for Actors that finish in seconds.
- [Example 2](#example-2-run-a-long-actor-and-resume-on-a-webhook): start the Actor and pause the run until Apify calls back with a webhook. Use this for anything longer.

<ScrapingWarning />

## Prerequisites

- A project with [Trigger.dev initialized](/quick-start)
- An [Apify](https://apify.com) account and an [Apify API token](https://console.apify.com/settings/integrations?utm_source=trigger.dev&utm_medium=integrations)
- The `apify-client` package installed in your project:

```bash
npm install apify-client
```

### Set your API token

Add `APIFY_TOKEN` to your `.env` file for local development, and to your Trigger.dev dashboard on the Environment Variables page:

```bash
APIFY_TOKEN="<your-apify-token>"
```

### Mark apify-client as external

Add `apify-client` to [`external`](/config/config-file#external) in your `trigger.config.ts` file. When it's bundled, `apify-client` fails on its first API call with `TypeError: ProxyAgent is not a constructor`.

```ts trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
external: ["apify-client"],
},
});
```

### Create the Apify client

Both examples share this client. The `x-apify-integration-platform` header tells Apify the requests come from Trigger.dev.

```ts trigger/apify.ts
import { ApifyClient } from "apify-client";

export const apify = new ApifyClient({
token: process.env.APIFY_TOKEN,
requestInterceptors: [
(config) => {
// Identifies requests from Trigger.dev tasks to Apify
config.headers = { ...config.headers, "x-apify-integration-platform": "trigger.dev" };
return config;
},
],
});
```

## Example 1: run an Actor and wait for the results

This task starts an Actor, waits for it to finish and returns the items from the run's default dataset. Your run stays active and counts towards compute usage while it waits, so keep this for Actors that finish in seconds and use [Example 2](#example-2-run-a-long-actor-and-resume-on-a-webhook) for anything longer.

### Key features

- `timeout` stops the Actor on Apify before `waitSecs` runs out, so the task never retries while an earlier Actor run is still going.
- `maxItems` caps how many results a pay-per-result Actor charges you for. Nobody is watching a background job, so a cap stops a runaway crawl.
- Throwing an error on a failed or timed-out Actor run lets your [retry settings](/errors-retrying) start the Actor again. An aborted run throws an `AbortTaskRunError` instead, because someone stopped it on purpose.
- `call()` copies the Actor's log into your run logs. Pass `log: null` in the options to turn this off.

### Task code

```ts trigger/apify-actor-run.ts
import { AbortTaskRunError, logger, task } from "@trigger.dev/sdk";
import { apify } from "./apify";

export const apifyActorRun = task({
id: "apify-actor-run",
maxDuration: 600,
retry: { maxAttempts: 3 },
run: async (payload: { actorId: string; input: Record<string, unknown> }) => {
const run = await apify.actor(payload.actorId).call(payload.input, {

@coderabbitai coderabbitai Bot Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make Actor starts retry-safe. Both tasks can start a second Actor when an attempt fails after the first Actor starts. In the short task, waitSecs can expire while the Actor remains READY or RUNNING. In the long task, the webhook wait or dataset request can fail after start() succeeds. Persist the Actor run ID across attempts and resume or reconcile that run before starting another. (docs.apify.com)

  • docs/guides/examples/apify-actor-run.mdx#L95-L95: resume or stop an unfinished Actor before a retry can call it again.
  • docs/guides/examples/apify-actor-run.mdx#L164-L164: reuse the previously started Actor run when a later step fails.
📍 Affects 1 file
  • docs/guides/examples/apify-actor-run.mdx#L95-L95 (this comment)
  • docs/guides/examples/apify-actor-run.mdx#L164-L164

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC @daveomri this fells bit nitpicky - maybe we could left comment in code stating that this is example not production code

@daveomri daveomri Oct 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, these examples aren't production code, but rather an example.

Added a note stating this fact (with proposed fix).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

timeout: 300, // Apify stops the Actor after 5 minutes, so it doesn't keep running after the task gives up
waitSecs: 360,
maxItems: 1000, // cap spend on pay-per-result Actors
});

if (run.status === "ABORTED") {
// Someone stopped the Actor on purpose, so don't start it again
throw new AbortTaskRunError(`Actor run ${run.id} was aborted`);
}

if (run.status !== "SUCCEEDED") {
throw new Error(`Actor run ${run.id} finished with status ${run.status}`);
}

const { items } = await apify.dataset(run.defaultDatasetId).listItems();

logger.info("Actor run finished", { runId: run.id, itemCount: items.length });

return { runId: run.id, datasetId: run.defaultDatasetId, items };
},
});
```

### Testing your task

You can test your task by triggering it from the Trigger.dev dashboard. This payload runs [Website Content Crawler](https://apify.com/apify/website-content-crawler) on three pages:

```json
{
"actorId": "apify/website-content-crawler",
"input": {
"startUrls": [{ "url": "https://docs.apify.com/academy" }],
"maxCrawlPages": 3,
"crawlerType": "cheerio",
"proxyConfiguration": { "useApifyProxy": true }
}
}
```

## Example 2: run a long Actor and resume on a webhook

This task starts an Actor with an [ad-hoc webhook](https://docs.apify.com/platform/integrations/webhooks/ad-hoc-webhooks) that points at a [wait token](/wait-for-token) callback URL. The run pauses on `wait.forToken()` until the Actor finishes, and [paused runs don't count towards compute usage](/wait), however long the Actor takes.

### Key features

- The webhook covers all four ways an Actor run can end, so a failed, aborted or timed-out Actor resumes your run with an error instead of leaving it waiting until the token times out.
- `payloadTemplate` sends only `eventType` and `resource` (the Actor run). Whatever Apify posts to `token.url` becomes the token's result, so the `ApifyWebhookPayload` type matches this template, not Apify's default webhook payload.
- The token `timeout` is longer than the Actor's `timeout`, so the Actor always finishes, and the webhook fires, before the token expires.
- Webhook event types use underscores (`ACTOR.RUN.TIMED_OUT`), but a run's `status` uses a hyphen (`TIMED-OUT`). Compare `eventType` with event types and `status` with statuses.

### Task code

```ts trigger/apify-long-actor-run.ts
import { AbortTaskRunError, logger, task, wait } from "@trigger.dev/sdk";
import { apify } from "./apify";

// Shape produced by the payloadTemplate below, not Apify's default webhook payload
type ApifyWebhookPayload = {
eventType: string;
resource: { id: string; status: string; defaultDatasetId: string };
};

export const apifyLongActorRun = task({
id: "apify-long-actor-run",
retry: { maxAttempts: 3 },
run: async (payload: { actorId: string; input: Record<string, unknown> }) => {
const token = await wait.createToken({ timeout: "1h" });

const run = await apify.actor(payload.actorId).start(payload.input, {
timeout: 3000, // 50 minutes, shorter than the token timeout
maxItems: 1000,
webhooks: [
{
eventTypes: [
"ACTOR.RUN.SUCCEEDED",
"ACTOR.RUN.FAILED",
"ACTOR.RUN.ABORTED",
"ACTOR.RUN.TIMED_OUT",
],
requestUrl: token.url,
payloadTemplate: `{ "eventType": {{eventType}}, "resource": {{resource}} }`,
},
],
});

logger.info("Actor run started, waiting for webhook", { runId: run.id });

const result = await wait.forToken<ApifyWebhookPayload>(token).unwrap();

if (result.eventType === "ACTOR.RUN.ABORTED") {
// Someone stopped the Actor on purpose, so don't start it again
throw new AbortTaskRunError(`Actor run ${result.resource.id} was aborted`);
}

if (result.eventType !== "ACTOR.RUN.SUCCEEDED") {
throw new Error(`Actor run ${result.resource.id} finished as ${result.eventType}`);
}

const { items } = await apify.dataset(result.resource.defaultDatasetId).listItems();

logger.info("Actor run finished", { runId: result.resource.id, itemCount: items.length });

return { runId: result.resource.id, datasetId: result.resource.defaultDatasetId, items };
},
});
```

### Testing your task

Trigger the task from the Trigger.dev dashboard with the same payload as Example 1. The run trace shows `wait.forToken()` covering the time the Actor was running, and the run resumes once Apify sends the webhook.

<Note>
Cancelling a Trigger.dev run doesn't stop the Actor run it started. Abort the Actor run in
[Apify Console](https://console.apify.com?utm_source=trigger.dev&utm_medium=integrations) or with `apify.run(runId).abort()`.
</Note>

<Note>
These examples aren't retry-safe: retries always start a new Actor run. In production, store the Actor run ID and resume that run on retry rather than starting over.
</Note>

## Learn more

- [Wait for token](/wait-for-token)
- [Apify ad-hoc webhooks](https://docs.apify.com/platform/integrations/webhooks/ad-hoc-webhooks)
- [Apify JavaScript client](https://docs.apify.com/api/client/js/)
- [Apify Store](https://apify.com/store)
1 change: 1 addition & 0 deletions docs/guides/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Task code you can copy and paste to use in your project. They can all be extende

| Example task | Description |
| :---------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Apify Actor run](/guides/examples/apify-actor-run) | Run an Apify Actor and get its results, waiting for short runs or resuming on a webhook for long ones. |
| [DALL·E 3 image generation](/guides/examples/dall-e3-generate-image) | Use OpenAI's GPT-4o and DALL·E 3 to generate an image and text. |
| [Deepgram audio transcription](/guides/examples/deepgram-transcribe-audio) | Transcribe audio using Deepgram's speech recognition API. |
| [Fal.ai image to cartoon](/guides/examples/fal-ai-image-to-cartoon) | Convert an image to a cartoon using Fal.ai, and upload the result to Cloudflare R2. |
Expand Down