Beta. This endpoint is new and its response shape may still change. Where
Export Crawl Analytics
returns aggregates, this endpoint returns the individual requests behind
them — one row per bot hit, straight from the daily archive.
json(default) — rows in a JSON envelope, cursor-paginated.csv— the same rows and pagination as an RFC 4180 CSV download.parquet— presigned download links to the daily Apache Parquet archive files themselves, ready for DuckDB, pandas, or any warehouse. No server-side decoding, no pagination — the fastest way to bulk-load history.
Programmatic (API key) access requires the Pro plan and above — requests
from Starter accounts return
403 plan_upgrade_required.Recent days are provisional. A day is only frozen into the permanent
archive after a settling window of a few days; until then it is served from a
provisional copy and listed in
provisionalDays — real rows, but tier
verdicts may still upgrade. See Data freshness. Archived
history is retained for ~13 months.Windows are capped at 31 days
Raw logs are far denser than aggregates, so this endpoint acceptsperiod values of 1d, 7d (the default), or 30d, and explicit startDate/endDate ranges spanning at most 31 days. For longer history, iterate over consecutive windows — or use format: "parquet" and download months of daily files with one request per 31-day window.
Data freshness
Every day in your window falls into exactly one of three states:
A finished day becomes exportable shortly after the next UTC midnight. Provisional rows are the same requests you’ll see after the freeze — only the verification verdict (
tier) can change, upgrading as late verification completes. If your pipeline depends on final verdicts, re-export any day that appeared in provisionalDays once it has settled.
Request
Headers
Body Parameters
Filters and
pageSize apply only to the json/csv formats; sending them with format: "parquet" is a 400 (parquet returns whole archive files, which are never server-filtered).
Example Request
Response
Success (200) — format: "json"
Row Fields
Privacy: no IP addresses. Raw client IPs are never stored anywhere in
HadoSEO. The archive keeps only facts derived from the IP before it was
dropped, of which the operator/CIDR match above is what this endpoint
exposes.
Envelope Fields
Success (200) — format: "csv"
The body is the CSV itself (Content-Type: text/csv), with a header row of the same field names and RFC 4180 quoting. Because there is no JSON envelope, pagination metadata moves to response headers:
Success (200) — format: "parquet"
parts). Files with provisional: true come from the not-yet-frozen copy of a recent day — the frozen file that later replaces it may carry upgraded verdict_tier values. Each url is a temporary, pre-authorized link — fetch it with a plain GET, no Authorization header. The files use the parquet schema underlying the row fields above (snake_case column names: ts as epoch-millisecond INT64, ua_raw, matched_operator, verdict_tier, …) plus two archive-internal columns not present in json/csv rows: stream_id (ingest id) and ip_asn (org-level ASN of the source IP; 0 = unknown).
Pagination
json and csv exports walk the archive oldest → newest and return pageSize rows per request. Pass the returned cursor (body nextCursor, or the X-Next-Cursor header for CSV) as cursor in the next request — by itself; the token pins everything else. Two properties worth knowing:
- Resuming is cheap: the cursor records an exact file position, so page 50 costs the same as page 2.
- Consistency is guaranteed: frozen archive files are write-once, and the cursor carries an integrity check — if the file behind your cursor changed you get
400 invalid_cursorrather than silently shifted rows. Restart the export. This is rare for frozen days but expected for provisional days paginated across a day close (they’re rewritten daily), so treatinvalid_cursoras a normal restart signal in long-running exports.
Error Responses
Errors return the JSON shapes above regardless of the requested
format.