api
Jobs: fetches and exports
Collections and exports answer 202 with a job; read the job until it is done.
Updated · API v1
Fetches
POST /v1/fetches asks Adspy to collect an advertiser's ads from the Meta Ad Library or Google Ads Transparency; find the advertiser's url with GET /v1/advertisers. POST /v1/social/fetches collects a TikTok or YouTube account's posts. Both answer 202 with a job.
- state existing: Adspy already had the brand or account. Nothing is collected or counted; read it now.
- queued, pending, submitting, processing: the collection runs. Read the job again after poll_after_seconds.
- waiting (social accounts): the platform held the request back; Adspy retries it at retry_at. Do not submit it again.
- completed or failed: is_terminal is true. A job is done only when is_complete is true; progress alone never says so.
{
"job_id": "0b8e3f5c-3a8c-4c0e-9d0e-6c1f2b7a9e41",
"kind": "brand_fetch",
"state": "processing",
"is_complete": false,
"is_terminal": false,
"poll_after_seconds": 30,
"brand_key": "northstar",
"source": "meta",
"business_id": "123456789",
"total_ads": 240,
"processed_ads": 96,
"remaining_ads": 144,
"progress_percent": 40,
"result_cap": 20,
"…": "…"
}# Ask for a brand Adspy does not have yet
curl -X POST "https://www.adspy.sh/api/v1/fetches" \
-H "Authorization: Bearer $ADSPY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a8e-collect-northstar" \
-d '{"url": "https://www.facebook.com/ads/library/?view_all_page_id=123456789", "name": "Northstar"}'
# Follow it
curl "https://www.adspy.sh/api/v1/fetches/$JOB_ID" \
-H "Authorization: Bearer $ADSPY_API_KEY"const headers = { Authorization: `Bearer ${process.env.ADSPY_API_KEY}` };
const submitted = await fetch("https://www.adspy.sh/api/v1/fetches", {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json",
"Idempotency-Key": "6f1c2a8e-collect-northstar",
},
body: JSON.stringify({
url: "https://www.facebook.com/ads/library/?view_all_page_id=123456789",
name: "Northstar",
}),
});
let job = await submitted.json();
if (!submitted.ok) throw new Error(`${job.code}: ${job.detail}`);
while (!job.is_terminal) {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds ?? 30) * 1000));
const response = await fetch(`https://www.adspy.sh/api/v1/fetches/${job.job_id}`, { headers });
job = await response.json();
if (!response.ok) throw new Error(`${job.code}: ${job.detail}`);
}
console.log(job.state, job.brand_key);headers = {"Authorization": f"Bearer {os.environ['ADSPY_API_KEY']}"}
response = requests.post(
"https://www.adspy.sh/api/v1/fetches",
json={
"url": "https://www.facebook.com/ads/library/?view_all_page_id=123456789",
"name": "Northstar",
},
headers={**headers, "Idempotency-Key": "6f1c2a8e-collect-northstar"},
timeout=70,
)
response.raise_for_status()
job = response.json()
while not job["is_terminal"]:
time.sleep(job["poll_after_seconds"] or 30)
response = requests.get(
f"https://www.adspy.sh/api/v1/fetches/{job['job_id']}", headers=headers, timeout=70
)
response.raise_for_status()
job = response.json()
print(job["state"], job["brand_key"])Once complete, read the brand's creatives (GET /v1/brands/{brand_key}/creatives) or the account's posts (GET /v1/social/posts).
Exports
POST /v1/exports builds a file of up to 10,000 items in the background: creatives by filters or by ids, a collection, social posts, posts run as ads, a social comparison or brand X-Rays, as CSV, Excel, JSON, ZIP with media, PDF, Markdown or HTML.
curl -X POST "https://www.adspy.sh/api/v1/exports" \
-H "Authorization: Bearer $ADSPY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"source": {"kind": "creatives", "filters": {"brand_key": "northstar"}}, "format": "csv"}'
curl "https://www.adspy.sh/api/v1/exports/$JOB_ID" \
-H "Authorization: Bearer $ADSPY_API_KEY"const created = await fetch("https://www.adspy.sh/api/v1/exports", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ADSPY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
source: { kind: "creatives", filters: { brand_key: "northstar" } },
format: "csv",
}),
});
const job = await created.json();job = requests.post(
"https://www.adspy.sh/api/v1/exports",
json={
"source": {"kind": "creatives", "filters": {"brand_key": "northstar"}},
"format": "csv",
},
headers={
"Authorization": f"Bearer {os.environ['ADSPY_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
timeout=70,
).json(){
"job_id": "d90a832e-99d2-4bcf-853c-ffe225a980b1",
"state": "completed",
"title": "412 ads of Northstar",
"source": { "kind": "creatives", "count": 412 },
"format": "csv",
"completed_items": 412,
"requested_items": 412,
"missing_count": 0,
"bytes": 384120,
"filename": "adspy-export-d90a832e-99d2-4bcf-853c-ffe225a980b1.csv",
"created_at": "2026-10-24T09:51:43.389Z",
"expires_at": "2026-10-31T09:51:43.389Z",
"error_code": null,
"download_url": "https://www.adspy.sh/exports/d90a832e…?grant=…"
}- state moves from pending and running to completed, partial (some items could not be read: missing_count) or failed.
- download_url appears once the file is ready and is valid 5 minutes; read the job again for a new one.
- Files are kept 7 days (expires_at). GET /v1/exports lists the recent ones of the key's user in its workspace, including exports made in Adspy, except those of a source the API does not offer (Instagram among them).