Apify API: Get a Token and Run an Actor in 5 Minutes
Quick answer: Your
APIFY_TOKENlives under Console → Integrations. Copy it, export it as an environment variable, and send it asAuthorization: Bearer <token>on every call tohttps://api.apify.com/v2. From there you can start an Actor run, poll it, and read the dataset.
That's the whole loop. Most responses wrap payloads in a data object; errors return error.type and error.message. Field-by-field detail lives in the Apify API v2 documentation. This guide covers the parts a reference alone doesn't make obvious: which endpoint to pick for a 10-second job versus a 10-minute one, the timeout each one enforces, and where the output actually ends up.
| Concern | Detail |
|---|---|
| Token location | Console → Integrations |
| Base URL | https://api.apify.com/v2 |
| Auth (recommended) | Authorization: Bearer <APIFY_TOKEN> |
| Actor ID forms | Internal ID, or username~actor-name (tilde, not slash, in raw REST paths) |
| Sync wait | POST .../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 own values. This guide uses Compass's Google Maps Scraper (compass/crawler-google-places) as the running example, the same Actor you can run no-code from Scrape Google Maps with Apify if you'd rather skip the API entirely.
Every number on this page (timeouts, rate limits, webhook event names) was re-checked against live Apify docs on 2026-09-10. Nothing had changed since the last review, but you're reading today's numbers, not an archived copy.
Common endpoints (cheat sheet)
| Method | Endpoint | Purpose |
|---|---|---|
POST | /acts/{actorId}/runs | Start an Actor run; body { "input": { ... } } |
GET | /actor-runs/{runId} | Poll status, defaultDatasetId, status |
GET | /datasets/{datasetId}/items | Page through result rows (limit, offset, format) |
GET/PUT/POST | /key-value-stores/... | Read/write run metadata, INPUT, OUTPUT |
POST | /webhooks | Register HTTP callbacks for run events |
GET | /users/me | Sanity-check token / account |
Authentication (API token)
Create a token under Apify Console → Settings → API & Integrations. That tab is also where you revoke one, and where you review which third-party apps are connected to your account. Treat revocation as the recovery path: a leaked token cannot be rotated in place, so generate the replacement, move your deployments over, then revoke the old one. Two-factor authentication and active session management sit separately under Settings → Login & Privacy (help.apify.com — account settings, checked 2026-09-10).
Export the token in your shell rather than committing it:
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
| Scope | Limit |
|---|---|
| Global | 250,000 requests / minute per user (or per IP if unauthenticated) |
| Default per resource | 60 requests / second (single Actor, run, dataset, store, etc.) |
| Key-value store records | 200 rps |
| Dataset items / request-queue operations | 400 rps |
On 429, sleep with jitter and retry (exponential backoff). The official JavaScript and Python clients implement this for you.
Next steps
- Ship faster: Use Apify API v2 reference for every field and query parameter.
- Stay on the free tier: Sign up for Apify. The free plan includes monthly credits to exercise these calls without a card. See Apify pricing and Compute Units for exactly what that covers.
- No-code routing: Long runs often pair with Make.com (integration guide) or n8n instead of hand-rolled polling.
Use version 2 at https://api.apify.com/v2. All examples in this guide target that base path.
Under Console → Integrations, in your Apify account settings. Copy it and send it as Authorization: Bearer <token> on each request. 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.