Back
Guides›Export data in bulk with Python

Export data in bulk with Python

Run a query server-side as an asynchronous export job and download the results as files: create, poll, download, straight into pandas.

Some questions end in a table too large to page through: full history for a brand, a whole metro's series, the training set for a model. The export API is built for exactly that: it runs the same query server-side as an asynchronous, high-throughput job and returns downloadable files, free of the 90-second per-request timeout that interactive queries run under.

The request shape (dataReferenceName, fields, filters, filterLogic) is identical to :runQuery, so anything built there ports directly. Exports return up to 100 million records by default (limit caps it lower; result sets beyond the cap fail with RESOURCE_EXHAUSTED rather than truncating silently).


1. Create the export

One POST starts the job. This example pulls a brand's June-2025 month in one city. Swap in your own filters, or none at all for a full dataset pull:

import os, requests

BASE = "https://api.pine59.com"
auth = {"Authorization": f"Bearer {os.environ['PINE59_API_KEY']}"}

job = requests.post(f"{BASE}/v3/exports", headers=auth, json={
    "dataReferenceName": "visitation_usa_places.foot_traffic_month",
    "fields": [{"name": "location_id"}, {"name": "street_address"}, {"name": "visits_sum"}],
    "filters": [
        {"fieldName": "brands", "operator": "==", "value": "H-E-B"},
        {"fieldName": "city", "operator": "==", "value": "San Antonio"},
        {"fieldName": "observation_start_date", "operator": "==", "value": "2025-06-01"},
    ],
    "format": "CSV",
}).json()

print(job["id"], job["status"])   # export_..., RUNNING

format takes CSV, JSON, AVRO, or PARQUET. Parquet is the right choice for anything headed into pandas or a warehouse at volume. fileConfig.filenamePrefix names the output files when you organize many exports.

2. Poll until it completes

Exports move through RUNNING (and sometimes COPYING) to COMPLETED or FAILED:

import time

while job["status"] in ("RUNNING", "COPYING"):
    time.sleep(5)
    job = requests.get(f"{BASE}/v3/exports/{job['id']}", headers=auth).json()

print(job["status"])

The small run above completed in under twenty seconds; full-dataset exports take longer, so poll every few seconds rather than continuously.

3. Download the files

A completed export lists its files under fileExportInfo.files, each with a url, name, sizeBytes, and format metadata. The URLs are time-limited (fileExportInfo.expireTime says until when), so download promptly and re-create the export if the links lapse:

import pandas as pd
from io import BytesIO

frames = []
for f in job["fileExportInfo"]["files"]:
    content = requests.get(f["url"]).content
    frames.append(pd.read_csv(BytesIO(content)))

df = pd.concat(frames, ignore_index=True)
print(len(df), "rows")

For June 2025, the example above returns 45 rows, one per H-E-B store in San Antonio, ready for the same notebook work the examples build on. Large exports split across multiple files; the loop handles either case.

4. Manage export jobs

The remaining endpoints round out the lifecycle: GET /v3/exports lists your exports, and DELETE /v3/exports/{id} removes one. For exports that should land directly in your own BigQuery project instead of files, the create request takes a bigqueryConfig. The API reference documents the destination options.

When to export, when to query

:runQuery is built for interactive work: filtered questions, aggregations, anything a person or agent iterates on. As data volumes grow, the export API becomes the more effective path: the same query, run server-side at full throughput, delivered as files sized for notebooks and warehouses. The two compose naturally: explore and refine interactively, then export the refined query at scale.

Did you find what you were looking for?