Sign in

Daily file reference

What is inside each daily file, how the daily deltas work, and how to apply them. How to fetch the files with your API key is on Data feeds and downloads.

This page describes what is inside each file. Downloads lists what is published right now, with sizes and row counts, and GET /api/v1/files returns the same listing to a script.

What each day holds

Files are published into one directory per day, named for the UTC date the run started. Generation starts at midnight US Eastern (04:00 UTC, 05:00 during standard time) and the datasets publish one at a time over the following hours.

A day holds up to five datasets, each in two formats, plus a manifest.json describing them. Basic covers the first two datasets; Pro covers all five. Compare the plans.

DatasetOne row perKey to upsert onPlan
domainsregistrable domaindomainBasic
dnsname and record type, merged across vantage pointsname, record_typeBasic
dns_vantagename, record type and vantage pointname, record_type, vantagePro
certificatescertificatecert_idPro
address_historydomain and addressdomain, ipPro

A key appears at most once in a file. Every row carries the record's complete current state, not just the fields that changed.

File names

Each file is named {kind}.{dataset}.{format}.zst. The kind is delta for a daily file and snapshot for a full one, and the format is csv or json.

2026-09-17/manifest.json
2026-09-17/delta.domains.csv.zst
2026-09-17/delta.domains.json.zst
2026-09-17/delta.dns.csv.zst
2026-09-17/delta.dns.json.zst
2026-09-17/delta.dns_vantage.csv.zst
...

The CSV and JSON file of a dataset for a day are cut from the same data. They hold the same rows, with the same values, in the same order, so pick one format and ignore the other.

The manifest: what a day contains and what each file covers

manifest.json lists every file published for the day, sorted by name. It is the source of truth for what a day contains.

{
  "day": "2026-09-17",
  "generated": "2026-09-17T05:41:09Z",
  "files": [
    {
      "day": "2026-09-17",
      "name": "delta.domains.csv.zst",
      "dataset": "domains",
      "kind": "delta",
      "format": "csv",
      "since": "2026-09-16T00:00:00Z",
      "as_of": "2026-09-17T04:00:02Z",
      "rows": 61204877,
      "bytes": 1597224401,
      "sha256": "0f1c...e9a4"
    }
  ]
}
FieldTypeMeaning
daystringThe day directory, YYYY-MM-DD.
namestringThe file name within the day.
datasetstringOne of the five datasets.
kindstringdelta or snapshot.
formatstringcsv or json.
sincetimestamp | nullThe lower bound this delta covers; see Deltas. null for a snapshot.
as_oftimestampWhen the data for this file was read. Everything recorded before this moment is in the file.
rowsintegerData rows in the file, the same for both formats. The CSV header is not counted.
bytesintegerSize of the compressed file.
sha256stringSHA-256 of the compressed file, 64 lowercase hex characters.

A file listed in the manifest is complete. Files are written elsewhere and moved into the day only after their row counts and compression have been verified.

The manifest grows during the run. Each dataset is added when it publishes. A day where nothing has been published yet has no manifest at all.

A missing dataset is not an empty one. If a dataset could not be generated, it is left out of that day's manifest and its next file covers the gap: its since reaches back to cover the missed days. A dataset can also be added to a day's manifest later, including after that UTC day has ended, when it is generated again.

Trust sha256, not the size. A file can be regenerated, which changes its bytes and its hash. While that happens its entries briefly leave the manifest, so a manifest can for a moment list fewer files, or none at all.

A CSV file can have more lines than rows plus one, because a quoted value can contain a line break. Count records with a CSV parser, not with line counts.

Deltas: a lower bound and no upper bound

A delta holds every record that changed on or after its since up to the moment the file was generated.

since is 00:00 UTC of the day this dataset's previous file was published for. On a normal day that is yesterday. If a day was missed, it stays at the last published day, so the next file is larger and nothing falls between two files.

Because there is no upper bound, consecutive files overlap. The 2026-09-17 file covers from 2026-09-16 00:00, and the 2026-09-16 file already carried changes up to its own as_of that morning. Records that changed in that overlap appear in both files. Applying rows as upserts makes this harmless.

DatasetA row is in the delta when
domainsAnything about the domain was updated on or after since: registration data, address, certificate or zone sighting.
dnsThe answer for the name and record type changed at any vantage point on or after since.
dns_vantageThe answer at that vantage point changed on or after since.
certificatesWe recorded the certificate on or after since, for the first time or again.
address_historyThe address appeared in a new or changed answer for the domain on or after since.

Late arrivals still ship. Some records reach us after the fact and carry an older timestamp, such as a zone file that is a day old when we process it. A delta also includes every record we received on or after since, so these arrive in the next file even though their dates are earlier than since.

Historical backfill does not. When we import older data in bulk, it reaches you through the full snapshot rather than as a sudden oversized daily file.

Applying a delta

Upsert every row on its dataset's key, replacing the whole row. Apply each dataset's files in day order. Replaying an older file after a newer one would move rows backwards.

An example for the DuckDB command-line client, applying one day's domains delta to a local table keyed on domain. Verify the file first, against the manifest's sha256 and with zstd -t: DuckDB reads a truncated .zst without reporting an error.

.bail on
BEGIN;
DROP TABLE IF EXISTS d;
CREATE TEMP TABLE d AS
  SELECT * FROM read_csv('delta.domains.csv.zst', header = true, all_varchar = true,
                         allow_quoted_nulls = false);
CREATE TABLE IF NOT EXISTS domains AS SELECT * FROM d LIMIT 0;
DELETE FROM domains WHERE domain IN (SELECT d.domain FROM d);
INSERT INTO domains SELECT * FROM d;
COMMIT;

Each line guards against a failure we reproduced. Without .bail on the client carries on past an error, and COMMIT keeps the DELETE of a file that did not load. With domain unqualified in the subquery, a file that loads without a domain column makes the DELETE remove every row. Without allow_quoted_nulls = false, empty values load as NULL instead of empty strings. DROP TABLE IF EXISTS d lets one session apply several days in turn.

The full snapshot

The whole corpus for each dataset is published as snapshot.{dataset} files, with the same columns and encoding as the deltas. Only the latest is kept on the download page. A snapshot's manifest entries carry kind snapshot and a null since. Seed a new system from it, then apply the daily deltas published after its as_of.

Retention and the download allowance

Files stay downloadable here for 14 days, counting today. Every file we publish is archived permanently. Those are two different numbers and it is worth keeping them apart: 14 days is how long a file stays on the download page, not how long the data lives. A consumer that falls further behind than that has missed deltas and needs to re-seed: take the latest full snapshot from the download page and apply the deltas published after its as_of.

The archive is what makes the 14-day certificate window in the database a working window rather than a limit. Certificate detail ages out of the API after 14 days; the daily file that recorded it does not age out at all.

Each file can be downloaded once per day, shared between the API and the buttons in your account. How to automate the download within that allowance is covered in Automating the download.

Encoding rules shared by every file

TopicRule
CompressionZstandard. DuckDB, polars, pandas and ClickHouse read .zst directly; otherwise stream through zstd -dc.
CSVA header row naming the columns in the order given below. Every string value and every header name is double-quoted, with a literal " written as "". Integers and booleans are not quoted. Lines end with LF.
JSONOne object per line, keys in column order. Standard JSON escapes apply, and / is written as \/.
ArraysA real JSON array in the JSON file. In the CSV file the cell holds the same JSON text, quoted as a CSV string.
TimestampsYYYY-MM-DDTHH:MM:SSZ in UTC, whole seconds.
Unknown valuesAn empty string, "", in both formats. Never null, never a placeholder date, never :: or a hash of zeros. An empty value is an answer about what we hold: we hold no value for that field.
Integers and booleansNever empty. Booleans are true or false.
AddressesIPv4 as a dotted quad, never as ::ffff:-mapped IPv6. IPv6 in its standard compressed text form.
HexLowercase.
Row orderIdentical in the CSV and JSON file of the same dataset and day. Otherwise unspecified.
Empty filesA delta with no rows is still published: a CSV file with only the header row and an empty JSON file.

Basic: domains

One row per registrable domain, keyed on domain. Where the domain lookup API publishes the same fact, the column carries the API field's name.

ColumnTypeMeaning
domainstringThe registrable domain, resolved through the Public Suffix List and lowercased.
tldstringThe final label, with no leading dot. A .co.uk domain carries uk.
registeredtimestampThe earliest creation date in the registry (WHOIS and RDAP) data we hold for the domain. Empty where we hold none, which can happen while registrar and expires are filled. Never inferred from certificate dates or zone files. A domain that was deleted and registered again keeps the earliest creation date we hold.
expirestimestampThe latest expiry date in the registry data we hold.
registrarstringA sponsoring registrar named in the registry data we hold. After a transfer it may not be the current one.
ipstringAn address from the most recent new or changed A or AAAA answer our resolvers recorded for the domain or any name under it. One address, even where the answer holds several; which one is arbitrary, and it can change without the answer changing. Empty where our resolvers have recorded no address for the domain.
first_cert_seentimestampThe earliest time our Certificate Transparency collection recorded a certificate for the domain. It is not when the domain's first certificate was issued, so it can be later than the certificate's own date, and it is empty where every certificate we hold for the domain predates our record of it.
first_zone_seentimestampWhen the domain first appeared as a new name in a registry zone file we hold. Our zone history begins in March 2025, so a domain already listed then and never deleted has none. It is an upper bound on the domain's first registration. For a domain that was deleted and registered again it can predate the current registration by months.
last_updatetimestampThe newest timestamp we hold for the domain from any source: a DNS answer, a certificate, a zone file or a registry record. For registry data it is when the record was fetched, which can predate our other collection.

Basic: DNS

One row per name and record type, merged across our vantage points, keyed on name and record_type.

DNS does not expire on a clock. We hold the current answer for as long as the name still resolves, however many years that runs, and we keep the record of what changed for 18 months. A name that stops answering entirely leaves the live data; the daily files that recorded it keep it permanently. There is no fixed window to design around.

ColumnTypeMeaning
namestringThe fully qualified name.
record_typestringA, AAAA, NS, MX, TXT and the rest.
valuesarray of stringsEvery value in the current answer at any vantage point, de-duplicated. The order carries no meaning.
ttlintegerTTL in seconds, the largest across vantage points, as the resolver returned it when the answer last changed. It is not refreshed by re-resolutions that return the same answer, and a resolver's cached TTL can be lower than the authoritative one.
last_confirmedtimestampThe most recent time any vantage point resolved this name and type and either got its current answer back unchanged or saw it change.

Unchanged re-resolutions have been recorded since 2026-09-16. A name not resolved again since then carries the time its answer last changed; each name is picked up as the sweep reaches it.

Pro: DNS by vantage point

One row per name, record type and vantage point, keyed on all three. A name can answer differently by geography, and the merged Basic file hides that.

ColumnTypeMeaning
namestringThe fully qualified name.
record_typestringRecord type.
vantagestringAn identifier for the resolver node that saw this answer, such as fra, lax, nyc or dal. Several nodes can share a region, and nrd is the lane that resolves newly registered domains. Treat it as an opaque label: nodes are added over time.
valuesarray of stringsThe answer this vantage point holds, unmerged.
ttlintegerTTL in seconds as this vantage point's resolver returned it when the answer last changed. Re-resolutions that return the same answer do not refresh it.
last_seentimestampThe most recent time this vantage point resolved the name and type and either got the same answer back or saw it change.
last_changedtimestampWhen the answer at this vantage point last changed.
changesintegerHow many times the answer has changed at this vantage point. A high count on an A record marks rotation; on NS, a migration.

Before 2026-09-17, most heavily on 2026-08-27 and 2026-08-28, a fault on our side reset some rows without a real change: last_changed moved to the time of the check and changes went back to 0. The next real change corrects last_changed. It does not correct changes, which stays lower than the true count.

Pro: certificates

One row per certificate, keyed on cert_id. The Pro API returns the same fields for a single certificate.

Certificates cover a rolling 14 days in the database, and every daily file is kept permanently. A certificate we have not recorded for 14 days leaves the database, and first_observed and ct_logs_seen count only the records still held there. The daily certificates file for the day we recorded it keeps the full row regardless, so a date outside the window is a file to read rather than a gap in what we observed.

ColumnTypeMeaning
cert_idstringOur certificate key, 32 lowercase hex characters. It is the same value as a domain record's ssl_cert, which the API writes in uppercase, so join on it case-insensitively.
cert_sha256stringSHA-256 of the DER-encoded certificate. Empty where we have seen only the precertificate.
serialstringSerial number, hex.
spki_sha256stringSHA-256 of the SubjectPublicKeyInfo. Certificates sharing it were issued for the same key pair. Empty where we have not derived it.
issuerstringFull issuer distinguished name, for example CN=YE2,O=Let's Encrypt,C=US.
issuer_orgstringIssuing CA organization on its own, for grouping.
subject_cnstringSubject common name. Empty on certificates that carry names only in the SAN extension, and where we did not derive it, which happens only on certificates first recorded before 03:00 UTC on 2026-09-07.
key_algstringPublic key algorithm, such as RSA or ECDSA.
key_bitsintegerKey size in bits. 0 where we did not derive it, which happens only on certificates first recorded before 03:00 UTC on 2026-09-07.
signature_algstringSignature algorithm, such as SHA256-RSA.
sansarray of stringsEvery name the certificate covers. This is the column that links co-tenants behind one CDN certificate.
san_countintegerLength of sans.
wildcardbooleanTrue where any SAN begins with *..
ja4xstringJA4X fingerprint of the certificate's structure. Certificates minted by the same tooling share it.
first_observedtimestampWhen we first recorded the certificate from a CT log, within the 14 days we keep.
last_observedtimestampWhen we last recorded it.
ct_logs_seenintegerHow many CT logs we have seen it in, within the 14 days we keep.

Pro: address history

One row per domain and address, keyed on domain and ip. It covers addresses our resolvers saw for the domain and for any name under it, including the ones it has moved away from. An address known to us only from a third-party dataset is not included.

ColumnTypeMeaning
domainstringThe registrable domain.
ipstringAn address the domain, or a name under it, resolved to.
first_seentimestampThe first time this address appeared in an answer for the domain. For some addresses that also appear in a third-party hosting dataset we loaded, it is 2026-08-05, the date of that dataset, rather than our own first answer.
last_seentimestampThe last time this address appeared in a new or changed answer. Re-resolutions that return the same answer do not move it; the DNS files carry confirmation times.
observedintegerHow many new or changed answers included this address. For an address that also appears in the third-party hosting dataset, the count includes that sighting once.
currentbooleanTrue for the address in the domains file's ip column; which one is arbitrary when several were recorded together. When you apply a row with current true, clear it on the domain's other addresses; see Applying a delta.

Sample rows

The same two domains rows in each format, as the generator writes them. The second domain has no registration data, so those cells are empty.

"domain","tld","registered","expires","registrar","ip","first_cert_seen","first_zone_seen","last_update"
"example-shop.com","com","2019-04-02T11:15:00Z","2027-04-02T11:15:00Z","GoDaddy.com, LLC","203.0.113.42","2026-09-06T06:22:11Z","","2026-09-16T04:11:50Z"
"newly-parked.xyz","xyz","","","","","2026-09-16T01:19:30Z","2026-09-15T22:07:48Z","2026-09-16T04:12:44Z"
{"domain":"example-shop.com","tld":"com","registered":"2019-04-02T11:15:00Z","expires":"2027-04-02T11:15:00Z","registrar":"GoDaddy.com, LLC","ip":"203.0.113.42","first_cert_seen":"2026-09-06T06:22:11Z","first_zone_seen":"","last_update":"2026-09-16T04:11:50Z"}
{"domain":"newly-parked.xyz","tld":"xyz","registered":"","expires":"","registrar":"","ip":"","first_cert_seen":"2026-09-16T01:19:30Z","first_zone_seen":"2026-09-15T22:07:48Z","last_update":"2026-09-16T04:12:44Z"}

A DNS-by-vantage row with an array value. In the CSV file the array is JSON text inside a quoted cell, so its quotes are doubled.

"name","record_type","vantage","values","ttl","last_seen","last_changed","changes"
"example-shop.com","TXT","fra","[""v=spf1 ip4:203.0.113.0\/24 -all""]",300,"2026-09-16T05:02:11Z","2026-08-30T17:40:02Z",1
{"name":"example-shop.com","record_type":"TXT","vantage":"fra","values":["v=spf1 ip4:203.0.113.0\/24 -all"],"ttl":300,"last_seen":"2026-09-16T05:02:11Z","last_changed":"2026-08-30T17:40:02Z","changes":1}

Where to go next

Last updated September 17, 2026. Questions the docs do not answer: get in touch.

↑ Top