# Jobs: fetches and exports

Adspy API · https://www.adspy.sh/docs/api/jobs · Updated October 24, 2026

Collections and exports answer 202 with a job; read the job until it is done.

## 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.

GET /v1/fetches/{id}:

```json
{
  "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,
  "…": "…"
}
```

```sh
# 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"
```

```js
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);
```

```python
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.

```sh
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"
```

```js
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();
```

```python
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()
```

GET /v1/exports/{id}:

```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).