Skip to main content

Pagination

Endpoints that return collections use cursor-based pagination. You ask for a page of results; if more exist, the response carries an opaque cursor you pass back to fetch the next page.

Why cursors (and not page numbers)?​

Page numbers break when underlying data shifts (a new record inserted between requests would shuffle what's on each page). Cursors anchor to a stable position in the result set so you never miss or duplicate records — even when data is being written in parallel.

Response shape​

{
"items": [
{ "...": "..." },
{ "...": "..." }
],
"next_cursor": "eyJ0IjoiMjAyNi0wMS0xNVQxMDoxNTowMCIsImlkIjo5OTk5fQ"
}
FieldMeaning
itemsThe records on this page.
next_cursorOpaque string. Pass back as cursor in the JSON request body for the next page. null when there are no more results.

The cursor value is opaque — don't try to decode or modify it. Its format is internal and may change.


Consuming a paginated endpoint​

The traffic data search endpoint is POST (not GET) — the time range, filters, page size and cursor all travel in the JSON body:

curl — first page
curl -X POST \
-H "Authorization: Token ..." \
-H "Content-Type: application/json" \
-d '{
"time": {"from_dt": "2026-01-01T00:00:00Z", "to_dt": "2026-01-31T00:00:00Z"},
"limit": 1000
}' \
https://mytrafficdata.com/api/v1/traffic/data/search

The response includes next_cursor if there's more. Pass it back in the body:

curl — next page
curl -X POST \
-H "Authorization: Token ..." \
-H "Content-Type: application/json" \
-d '{
"time": {"from_dt": "2026-01-01T00:00:00Z", "to_dt": "2026-01-31T00:00:00Z"},
"limit": 1000,
"cursor": "eyJ0Ij…"
}' \
https://mytrafficdata.com/api/v1/traffic/data/search

Keep passing the latest next_cursor back until you receive next_cursor: null. That's the end.

:::warning 30-day time-range cap On /api/v1/traffic/data/search, the time window is capped at 30 days per query. For longer ranges, use the export endpoint (POST /api/v1/traffic/exports) which produces an async file you can download. :::


Page size (limit)​

FieldDefaultMaximum
limit (in the JSON body)1000check the per-endpoint reference

Prefer bigger pages. One request of 1,000 records is roughly ten times cheaper than ten requests of 100 — fewer network round-trips, less rate-limit pressure, less server work per record.


Full walk-through pattern​

Python — walk every page
import requests

TOKEN = "..."
URL = "https://mytrafficdata.com/api/v1/traffic/data/search"

def walk(initial_body):
body = dict(initial_body)
while True:
r = requests.post(
URL,
headers={"Authorization": f"Token {TOKEN}"},
json=body,
timeout=(5, 30),
)
r.raise_for_status()
page = r.json()
yield from page["items"]
if not page.get("next_cursor"):
break
body["cursor"] = page["next_cursor"]

for record in walk({
"time": {"from_dt": "2026-01-01T00:00:00Z", "to_dt": "2026-01-31T00:00:00Z"},
"limit": 1000,
}):
process(record)
JavaScript — same idea
async function* walk(url, initialBody) {
let body = {...initialBody};
while (true) {
const r = await fetch(url, {
method: "POST",
headers: {
"Authorization": `Token ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!r.ok) throw new Error(await r.text());
const page = await r.json();
yield* page.items;
if (!page.next_cursor) break;
body.cursor = page.next_cursor;
}
}

for await (const record of walk(URL, {
time: {from_dt: "2026-01-01T00:00:00Z", to_dt: "2026-01-31T00:00:00Z"},
limit: 1000,
})) {
process(record);
}

Stable ordering​

Records within items are returned in a stable, deterministic order (typically (timestamp, id)). The cursor encodes that position, so requesting the next page is always exactly "the next records after the last one I saw" — even if new records were inserted in the meantime.


Cursor lifetime​

Cursors are valid for at least 24 hours. If you pause a long pagination job (e.g., overnight) and resume from a stored cursor the next day, that's fine.

If you receive a 400 Validation Error mentioning the cursor, it's expired or malformed — start over from the first page (omit cursor from the body).


Summary checklist​

  • ✅ Cursor-based — pass next_cursor back as cursor in the JSON body.
  • ✅ Cursors are opaque — never decode or modify.
  • ✅ next_cursor: null means no more results.
  • ✅ Prefer bigger limit values.
  • ✅ Cursors stay valid for ~24 hours; restart on 400.