Run
Run a Devil Scrapes Actor via the Apify REST API
Trigger any Devil Scrapes Actor from curl, Python, JavaScript, or the Apify CLI, poll the run, and pull the dataset — full worked example.
Every Devil Scrapes Actor is callable from outside the Console the same way: POST an input to the Actor’s runs endpoint, poll (or wait) for the run to finish, then GET the dataset items. This page walks through that pattern in curl, Python, JavaScript, and the Apify CLI, using google-ads-transparency as the worked example.
The example we’ll use everywhere
Actor ID (username~slug form): DevilScrapes~google-ads-transparency
Input:
{
"searchDomains": ["nike.com"],
"maxResults": 50
}
Pricing for reference: $0.20 actor-start fee + $3.00 per 1,000 ad rows returned. This Actor scrapes creatives from the Google Ads Transparency Center — the worked example below returns real ad rows for nike.com, not placeholder data.
curl
Start the run asynchronously:
curl -X POST "https://api.apify.com/v2/acts/DevilScrapes~google-ads-transparency/runs" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"searchDomains": ["nike.com"], "maxResults": 50}'
The response includes data.id (the run ID) and data.defaultDatasetId. Poll until the run leaves RUNNING:
curl "https://api.apify.com/v2/actor-runs/<runId>" \
-H "Authorization: Bearer $APIFY_TOKEN" | jq '.data.status'
Once status is SUCCEEDED, pull the dataset:
curl "https://api.apify.com/v2/datasets/<defaultDatasetId>/items?format=json&clean=1" \
-H "Authorization: Bearer $APIFY_TOKEN"
clean=1 strips internal metadata fields and returns just your result rows. Swap format=json for csv, jsonl, or xlsx to get a different export shape from the same endpoint.
Always pass the token via the Authorization header, not a ?token= query parameter — query strings end up in logs and browser history.
Python (apify-client)
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("DevilScrapes/google-ads-transparency").call(
run_input={"searchDomains": ["nike.com"], "maxResults": 50}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["advertiserName"], item["landingUrl"])
.call() blocks until the run finishes, then returns the run object — no manual polling loop needed. The apify-client package is the official SDK; install it with pip install apify-client.
JavaScript (apify-client)
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('DevilScrapes/google-ads-transparency').call({
searchDomains: ['nike.com'],
maxResults: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'ads scraped');
Install with npm install apify-client. Same call-then-fetch shape as the Python client — this SDK is built directly on the REST API, so anything you can do here you can also do with raw fetch/axios calls against the endpoints in the curl section above.
Apify CLI
If you’d rather script this from a shell without writing a client:
apify login -t "$APIFY_TOKEN"
apify call DevilScrapes/google-ads-transparency \
-i '{"searchDomains": ["nike.com"], "maxResults": 50}'
apify call runs synchronously and prints the result summary to your terminal — useful for quick checks. For scripted pipelines prefer the async POST /runs pattern above, since a long-running Actor will otherwise hold your terminal open for the full duration.
Choosing async vs sync
The pattern above (POST /runs → poll → GET dataset) is the async pattern: it returns immediately and you check back. It’s the right choice for anything that might take more than a few seconds, or that you’re kicking off from a background job. If you want a single blocking HTTP call that returns the data directly — no polling, no run-ID bookkeeping — see Synchronous runs, webhooks, and scheduling for run-sync-get-dataset-items.
Capping spend on Pay-Per-Event runs
Every run call accepts maxTotalChargeUsd and maxItems query parameters (or body fields, depending on the SDK) to cap what a single run can cost or return — useful when you’re calling an Actor from an automated pipeline and want a hard ceiling regardless of how much matching data the target has. See Pricing and billing for the full PPE model.
FAQ
Do I need a paid Apify plan to call the API?
No. The REST API works on the free tier the same way it works on paid plans — you just pay per Actor run via Pay-Per-Event, same as running from the Console.
What’s the difference between the actor ID formats DevilScrapes~slug and DevilScrapes/slug?
They’re interchangeable across Apify’s tooling: the REST API path uses the tilde form (DevilScrapes~google-ads-transparency), while apify-client SDKs and the CLI accept the slash form (DevilScrapes/google-ads-transparency). Use whichever your library expects.
How do I know the run actually finished successfully?
Check data.status on the run object — it moves through READY → RUNNING → a terminal state, usually SUCCEEDED or FAILED. .call() in both official SDKs already waits for a terminal state before returning, so you don’t need to poll manually there.
Can I pass my own webhook instead of polling?
Yes — see Synchronous runs, webhooks, and scheduling for registering a webhook against run events like ACTOR.RUN.SUCCEEDED.
Where do rate limits apply to my API calls?
Apify’s own API enforces a request-rate ceiling (roughly 30 requests/second on free-tier accounts) separate from anything a scrape target enforces. The official apify-client packages already retry with backoff on 429 responses, so you generally don’t need to handle that yourself.
Still stuck?
Open the Issues tab on the Actor's Apify listing, or write to us. Real engineers answer.