Exports
An export writes every row of a REST read to a file in the background, so a large pull is one request instead of hundreds of pages. Start a job, poll it, then download the file from a signed URL.
// A large result set as a file: start an export job, poll until it succeeds,// then download the CSV from its signed URL (valid 15 minutes, no credentials).import { setTimeout as sleep } from "node:timers/promises";import { Status302 } from "@status302/sdk";
const status302 = new Status302({ apiKey: process.env.STATUS302_API_KEY, baseUrl: process.env.STATUS302_API_URL });
let job = await status302.post("/v1/exports", { body: { kind: "events", format: "csv", filters: { sort: "startsAt" } } });while (job.status === "queued" || job.status === "running") { await sleep(1000); job = await status302.get("/v1/exports/{exportId}", { path: { exportId: job.id } });}if (job.status !== "succeeded" || !job.downloadUrl) throw new Error(`export ${job.status}: ${job.error ?? ""}`);
const csv = await (await fetch(job.downloadUrl)).text();console.log(`${job.rows} rows, ${job.bytes} bytes${job.truncated ? " (cut at the plan's export limit)" : ""}`);console.log(csv.split("\n").slice(0, 3).join("\n"));# A large result set as a file: start an export job, poll until it succeeds,# then download the CSV from its signed URL (valid 15 minutes, no credentials).import osimport time
import httpx
from status302 import Status302
with Status302(os.environ["STATUS302_API_KEY"], base_url=os.environ.get("STATUS302_API_URL", "https://api.status302.com")) as status302: job = status302.create_export(body={"kind": "events", "format": "csv", "filters": {"sort": "startsAt"}}) while job["status"] in ("queued", "running"): time.sleep(1) job = status302.get_export(job["id"]) if job["status"] != "succeeded" or not job["downloadUrl"]: raise SystemExit(f"export {job['status']}: {job['error'] or ''}")
csv = httpx.get(job["downloadUrl"]).raise_for_status().text print(f"{job['rows']} rows, {job['bytes']} bytes{' (cut at the plan export limit)' if job['truncated'] else ''}") print("\n".join(csv.splitlines()[:3]))Start an export
Section titled “Start an export”POST /v1/exports with the kind of data, the file format and the read’s filters:
{ "kind": "sales", "format": "jsonl", "filters": { "country": "US", "confidence": ["inferred_high"], "minPrice": 10000 }}kind |
Rows of | Required filters | Scopes |
|---|---|---|---|
events |
GET /v1/events |
catalog:read |
|
sales |
GET /v1/sales/feed, or GET /v1/events/{eventId}/sales with eventId |
catalog:read, sales:read |
|
series |
GET /v1/events/{eventId}/series |
eventId |
catalog:read, market:read |
list |
A saved list’s rows with their metrics (the spreadsheet view) | listId |
catalog:read |
formatiscsv(with a header row) orjsonl(one JSON object per line, the same shape as the read’sitems).filtersare the read’s query parameters, with the same validation, defaults and plan clamps.cursorandlimitaren’t allowed: an export always covers every row. Unknown filter names answer400listing the allowed ones.
The API checks the request right away by reading the first row as your key: invalid filters answer 400, and a missing scope, plan or
quota answers 403, 402 or 429 exactly as the read would. Otherwise it answers 202 with the queued job. Creating an export counts as
one request against your quota; the rows it writes don’t.
Poll the job
Section titled “Poll the job”GET /v1/exports/{exportId} returns the job. Poll every few seconds until status leaves queued and running:
status |
Meaning |
|---|---|
queued |
Waiting for a worker. |
running |
Writing the file. |
succeeded |
Done: rows, bytes and downloadUrl are set. |
failed |
error says why. Jobs retry transient failures by themselves before failing. |
expired |
The file was deleted, 7 days after it was written. |
GET /v1/exports lists your organization’s exports, newest first.
Download
Section titled “Download”downloadUrl is a signed URL that needs no credentials and is valid for 15 minutes from the response that returned it. Download it
with any HTTP client; fetch the job again for a fresh URL. Files are kept for 7 days (expiresAt).
The file name is status302-<kind>-<id>.csv or .jsonl; CSV files are UTF-8 with a header row, JSON Lines files are
application/x-ndjson. Amounts follow the API’s money conventions in both formats.
Plan limits
Section titled “Plan limits”Exports need the Pro plan or higher. Pro exports stop at 1,000 rows; Elite and Enterprise exports are unlimited. When a file was cut at
the limit, truncated is true. A single export runs for at most 15 minutes.

