Skip to main content
Exports turn a filtered slice of permit data into a downloadable CSV or XLSX file. They run asynchronously: you create a job, poll until it’s ready, then download a short-lived signed URL.
Export volume is tier-gated: the Free tier includes 5,000 exported rows / month; Pro is unlimited. See Pricing.

The export flow

1

Create the job

POST /v1/exports returns an export_id and a poll_url.
2

Poll until complete

GET /v1/exports/{export_id} until status is completed (or failed).
3

Download

A completed job carries a signed_url — fetch it before expires_at.

Create an export

POST /v1/exportsapplication/json body:
curl
202 Accepted

Poll an export

GET /v1/exports/{export_id}status is one of pending, running, completed, failed.
completed
While pending/running, signed_url and the size/count fields are null. On failed, error_message explains why. Poll with a few seconds of backoff; most exports complete in well under a minute.

List your exports

GET /v1/exports?limit=&before_id= — most recent first, keyset-paginated.
Pass next_before_id back as before_id to page older.

Warehouse-native delivery

GET /v1/exports/warehouse/latest returns a manifest of the latest full-dataset drop — Parquet + gzipped CSV files with signed R2 URLs, for loading straight into Snowflake / BigQuery / a lake. This is the bulk alternative to per-query exports.
preserves_tristate_dq_flags marks files that carry PermitCore’s true/false/unknown data-quality flags intact (rather than collapsing unknown to false) — so the honesty semantics survive the round-trip into your warehouse.

Errors

Exports use the standard RFC 9457 problem+json shape. An invalid cohort, metros, or date_range returns 422 (invalid-request); a key that lacks the required scope returns 403 (missing-scope); an unknown export_id returns 404 (export-not-found); too many concurrent jobs returns 429 (export-concurrency-exceeded); and an exhausted export quota returns 429 (monthly-exports-quota-exceeded / daily-exports-quota-exceeded).