Bulk export¶
Every dataset has a streaming export endpoint — the whole dataset (or a filtered subset) in one request, no pagination:
format=csv(default) — UTF-8 CSV with a header row, served astext/csvwith aContent-Dispositionfilename.format=ndjson— one JSON object per line (application/x-ndjson), ideal for piping and incremental parsing.format=jsonis accepted as an alias.- The same filter syntax and
?fields=projection as the list endpoint. - Rows stream in stable insertion order straight from the database
(
X-Cache: BYPASS— exports are never cached). - Responses carry
Last-Modified(the newest successful ingestion), so periodic re-downloads can be conditional instead of unconditional pulls of yearly data.
As a reference point: the ~380k-row vehicle registry (≈21 MB of CSV) exports in about a second on the development stack.
Examples¶
# Whole dataset to a file
curl -H "Authorization: Bearer $API_KEY" \
"https://api.canovix.ro/v1/geospatial/siruta/export?format=csv" -o siruta.csv
# Filtered subset, selected columns
curl -H "Authorization: Bearer $API_KEY" \
"https://api.canovix.ro/v1/transport/vehicle_registry/export?county=CLUJ&fields=manufacturer,vehicle_count" \
-o cluj.csv
# Stream NDJSON through jq
curl -sN -H "Authorization: Bearer $API_KEY" \
"https://api.canovix.ro/v1/health/physicians/export?format=ndjson" \
| jq -r '.county' | sort | uniq -c
# Straight into pandas
import io
import httpx
import pandas as pd
response = httpx.get(
"https://api.canovix.ro/v1/transport/vehicle_registry/export",
params={"county": "CLUJ"},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=120,
)
df = pd.read_csv(io.BytesIO(response.content))
# Or stream row by row without holding the file in memory
import json
with httpx.stream(
"GET",
"https://api.canovix.ro/v1/transport/vehicle_registry/export",
params={"format": "ndjson"},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=None,
) as response:
for line in response.iter_lines():
row = json.loads(line)
...
// Save to disk (Node 18+)
import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const res = await fetch(
`${BASE}/v1/transport/vehicle_registry/export?format=csv`,
{ headers: { Authorization: `Bearer ${API_KEY}` } },
);
await pipeline(Readable.fromWeb(res.body), createWriteStream("vehicle_registry.csv"));
// Or process NDJSON incrementally
const ndjson = await fetch(
`${BASE}/v1/transport/vehicle_registry/export?format=ndjson&county=CLUJ`,
{ headers: { Authorization: `Bearer ${API_KEY}` } },
);
let buffer = "";
for await (const chunk of ndjson.body.pipeThrough(new TextDecoderStream())) {
buffer += chunk;
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines.filter(Boolean)) {
const row = JSON.parse(line);
// ...
}
}
Export vs cursor pagination¶
Both walk the full dataset. Choose by what's consuming the data:
- Export — you want a file, a DataFrame, or a database load. One request, one stream, least overhead.
- Cursor — you want JSON pages with
checkpointing: you can stop, remember
pagination.next_cursor, and resume later; an export stream restarts from the beginning if interrupted.
Limits¶
An export holds a database connection for as long as you keep reading, so it carries its own limits on top of your plan's quota:
| Limit | Value | What happens |
|---|---|---|
| Rows per export | 1,000,000 | The stream stops there. X-Max-Rows states the cap on every response. |
| Exports per account | 20 / hour | Further exports get 429 with Retry-After. |
| Concurrent exports | 4 (service-wide) | Extra exports queue rather than fail. |
If a file comes back with exactly the row cap, treat it as truncated: filter it down
(the same query syntax as the list endpoint works on /export) or switch to
cursor pagination, which walks any dataset in pages and
can be resumed.
Exports count as one request against your monthly quota, the same as any other call.