Skip to main content

Apify API v2 is a REST API for runs, storage, and webhooks

Quick answer: The Apify API v2 is a REST API that lets you run Actors, manage datasets, and access storage programmatically. Auth is via Bearer token. Base URL: https://api.apify.com/v2.

Most responses wrap payloads in a data object. Errors return error.type and error.message. See the canonical reference: Apify API v2 documentation.

ConcernDetail
Base URLhttps://api.apify.com/v2
Auth (recommended)Authorization: Bearer <APIFY_TOKEN>
Actor ID formsInternal ID, or username~actor-name (tilde, not slash, in raw REST paths)
Sync waitPOST .../runs?waitForFinish=60: max 60 seconds. For up to 300s, use POST /v2/acts/{id}/run-sync or /run-sync-get-dataset-items

Replace placeholders (YOUR_TOKEN, compass~crawler-google-places, RUN_ID, DATASET_ID) with your values.

Common endpoints (cheat sheet)

MethodEndpointPurpose
POST/acts/{actorId}/runsStart an Actor run; body { "input": { ... } }
GET/actor-runs/{runId}Poll status, defaultDatasetId, status
GET/datasets/{datasetId}/itemsPage through result rows (limit, offset, format)
GET/PUT/POST/key-value-stores/...Read/write run metadata, INPUT, OUTPUT
POST/webhooksRegister HTTP callbacks for run events
GET/users/meSanity-check token / account

Authentication (API token)

Create a token under Apify Console → Integrations. Export it in your shell:

export APIFY_TOKEN='YOUR_TOKEN'

curl (verify the token)

curl -sS "https://api.apify.com/v2/users/me" \
-H "Authorization: Bearer ${APIFY_TOKEN}"

Python

import os, requests

token = os.environ["APIFY_TOKEN"]
r = requests.get(
"https://api.apify.com/v2/users/me",
headers={"Authorization": f"Bearer {token}"},
timeout=60,
)
r.raise_for_status()
print(r.json()["data"])

JavaScript (fetch)

const token = process.env.APIFY_TOKEN;
const res = await fetch('https://api.apify.com/v2/users/me', {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(await res.text());
const { data } = await res.json();
console.log(data);

Run an Actor synchronously (wait for finish)

waitForFinish on POST /v2/acts/{id}/runs caps at 60 seconds. For longer sync calls (up to 300s), use POST /v2/acts/{id}/run-sync (returns the run object) or POST /v2/acts/{id}/run-sync-get-dataset-items (returns dataset items directly). Anything longer: start async and poll, or use webhooks.

curl

ACTOR_ID='compass~crawler-google-places'

curl -sS -X POST "https://api.apify.com/v2/acts/${ACTOR_ID}/runs?waitForFinish=60" \
-H "Authorization: Bearer ${APIFY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"input": {
"searchStringsArray": ["plumbers in New York"],
"maxCrawledPlacesPerSearch": 10
}
}'

Python

import os, requests

token = os.environ["APIFY_TOKEN"]
actor_id = "compass~crawler-google-places"
url = f"https://api.apify.com/v2/acts/{actor_id}/runs?waitForFinish=60"
payload = {
"input": {
"searchStringsArray": ["plumbers in New York"],
"maxCrawledPlacesPerSearch": 10,
}
}
r = requests.post(
url,
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
json=payload,
timeout=400,
)
r.raise_for_status()
run = r.json()["data"]
print(run["id"], run["status"], run["defaultDatasetId"])

JavaScript (apify-client, recommended)

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('compass/crawler-google-places').call({
searchStringsArray: ['plumbers in New York'],
maxCrawledPlacesPerSearch: 10,
});

console.log(run.id, run.status, run.defaultDatasetId);

Install: npm install apify-client. The client maps compass/crawler-google-places to the API’s username~actor form.

Run an Actor asynchronously (fire-and-forget)

Omit waitForFinish (or set 0). You receive a runId immediately.

curl

ACTOR_ID='compass~crawler-google-places'

curl -sS -X POST "https://api.apify.com/v2/acts/${ACTOR_ID}/runs" \
-H "Authorization: Bearer ${APIFY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"input":{"searchStringsArray":["plumbers in Austin"],"maxCrawledPlacesPerSearch":5}}'

Python

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("compass/crawler-google-places").start(
run_input={
"searchStringsArray": ["plumbers in Austin"],
"maxCrawledPlacesPerSearch": 5,
}
)
print("started", run["id"])

pip install apify-client

JavaScript

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const { id } = await client.actor('compass/crawler-google-places').start({
searchStringsArray: ['plumbers in Austin'],
maxCrawledPlacesPerSearch: 5,
});
console.log('started', id);

Poll run status

Poll GET /v2/actor-runs/{runId} until status is terminal (SUCCEEDED, FAILED, ABORTED, TIMED-OUT).

curl

RUN_ID='YOUR_RUN_ID'

curl -sS "https://api.apify.com/v2/actor-runs/${RUN_ID}" \
-H "Authorization: Bearer ${APIFY_TOKEN}"

Python

import os, time, requests

token = os.environ["APIFY_TOKEN"]
run_id = "YOUR_RUN_ID"
headers = {"Authorization": f"Bearer {token}"}

while True:
r = requests.get(f"https://api.apify.com/v2/actor-runs/{run_id}", headers=headers, timeout=60)
r.raise_for_status()
run = r.json()["data"]
print(run["status"])
if run["status"] in ("SUCCEEDED", "FAILED", "ABORTED", "TIMED-OUT"):
break
time.sleep(5)

JavaScript

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.run('YOUR_RUN_ID').waitForFinish();
console.log(run.status, run.defaultDatasetId);

Read dataset output

Use defaultDatasetId from the run. Paginate with limit and offset.

curl

DATASET_ID='YOUR_DATASET_ID'

curl -sS "https://api.apify.com/v2/datasets/${DATASET_ID}/items?format=json&clean=true&limit=1000&offset=0" \
-H "Authorization: Bearer ${APIFY_TOKEN}"

Python

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
page = client.dataset("YOUR_DATASET_ID").list_items(limit=1000, clean=True)
items = page.items
print(len(items))

JavaScript

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const result = await client.dataset('YOUR_DATASET_ID').listItems({ limit: 1000 });
console.log(result.items.length);

Webhooks (run finished → your HTTPS endpoint)

Register a webhook with POST /v2/webhooks. You must own or control requestUrl (HTTPS). The condition object scopes events; actorId is often the Actor’s internal ID from the Actor’s API tab in Console (you can also manage webhooks in the UI).

curl

curl -sS -X POST "https://api.apify.com/v2/webhooks" \
-H "Authorization: Bearer ${APIFY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"requestUrl": "https://your.domain/apify-webhook",
"eventTypes": ["ACTOR.RUN.SUCCEEDED", "ACTOR.RUN.FAILED", "ACTOR.RUN.TIMED_OUT"],
"condition": { "actorId": "YOUR_ACTOR_INTERNAL_ID" },
"description": "notify backend when compass maps run ends"
}'

Python

import os, requests

token = os.environ["APIFY_TOKEN"]
body = {
"requestUrl": "https://your.domain/apify-webhook",
"eventTypes": ["ACTOR.RUN.SUCCEEDED", "ACTOR.RUN.FAILED", "ACTOR.RUN.TIMED_OUT"],
"condition": {"actorId": "YOUR_ACTOR_INTERNAL_ID"},
"description": "pipeline notifications",
}
r = requests.post(
"https://api.apify.com/v2/webhooks",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
json=body,
timeout=60,
)
r.raise_for_status()
print(r.json()["data"])

JavaScript

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const wh = await client.webhooks().create({
requestUrl: 'https://your.domain/apify-webhook',
eventTypes: ['ACTOR.RUN.SUCCEEDED', 'ACTOR.RUN.FAILED', 'ACTOR.RUN.TIMED_OUT'],
condition: { actorId: 'YOUR_ACTOR_INTERNAL_ID' },
description: 'pipeline notifications',
});
console.log(wh.id);

Payload shape and template interpolation are documented under Webhooks in API v2.

Rate limits

ScopeLimit
Global250,000 requests / minute per user (or per IP if unauthenticated)
Default per resource60 requests / second (single Actor, run, dataset, store, etc.)
Key-value store records200 rps
Dataset items / request-queue operations400 rps

On 429, sleep with jitter and retry (exponential backoff). The official JavaScript and Python clients implement this for you.

Next steps

Apify Affiliate Banner 728x90Apify Affiliate Banner 728x90Apify Affiliate Banner 300x50Apify Affiliate Banner 300x50
Frequently Asked Questions

Use version 2 at https://api.apify.com/v2. All examples in this guide target that base path.

Send Authorization: Bearer <token> on each request. Create the token under Console → Integrations. Avoid ?token= in URLs for production, because it leaks via logs and referrers.

Up to 60 seconds on POST /v2/acts/{id}/runs. For up to 300 seconds, use POST /v2/acts/{id}/run-sync or /run-sync-get-dataset-items. For anything longer, start asynchronously and poll GET /v2/actor-runs/{runId}, or attach a webhook.

Usually in the run’s default dataset (defaultDatasetId). Some Actors also write INPUT/OUTPUT or files to the default key-value store, so check the run object and the Actor’s README.

POST /v2/actor-tasks/{taskId}/runs with the same patterns (optional waitForFinish, same dataset flow). Task IDs use username~taskName naming similar to Actors.

Globally ~250k requests/minute per account. Per resource, most endpoints default to 60 rps; dataset/queue endpoints allow up to 400 rps; key-value record CRUD allows 200 rps. Official clients retry 429/5xx with exponential backoff.

Yes. Use the HTTP module with Bearer auth, or native Apify modules. For runs longer than Make’s module timeout, start async and continue on webhook or polling. See the Make.com integration guide on this site.

Common mistakes and fixes

401 token-not-provided or 403 on run.

Send `Authorization: Bearer <token>` on every call. Use a token from [Console → Integrations](https://console.apify.com/account/integrations?fpr=use-apify). Named Actor IDs (`username~actor`) require auth.

Synchronous run returns 408 or times out.

`waitForFinish` waits up to 60s on `POST /v2/acts/{id}/runs`. For longer runs, omit it and poll `GET /v2/actor-runs/{runId}`, attach a webhook, or use the `run-sync` / `run-sync-get-dataset-items` endpoints (max 300s).

429 rate-limit-exceeded.

Backoff exponentially. Prefer official clients (`apify-client`), which retry 429/5xx. Spread polling across time; avoid tight loops per resource.

Run succeeds but dataset looks empty.

Confirm `defaultDatasetId` from the run object. Some Actors write to key-value stores, so use `defaultKeyValueStoreId` and record keys from logs.

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