Every file is zstd-compressed JSON Lines, one domain per line. Here is how to open it, ask it questions and load it into your stack. Each recipe was run on the free 10K-row preview, and the output you see is what it printed.
Format
JSON Lines, zstd compressed
.jsonl.zst
One row per
Domain, every key always present
values may be null
File name
Dataset and export date
all-data-2026-10-16.jsonl.zst
Freshness
A new export every day
same columns every day
1 · What you download
What you download
Every webatla dataset is JSON Lines: one domain per line, one JSON object per line. Paid files are compressed with zstd.
File
What's inside
Format
Full dataset
All rows of the dataset
.jsonl.zst
Slice
All rows for one TLD, TLD suffix, country (by IP) or technology, with the same fields as the full file
.jsonl.zst
Free preview (10K rows)
The first 10,000 lines of the full file
.jsonl plain, not compressed
20-row preview on TLD, country and technology pages
Rows of that entity, with the same field names as the paid file. It shows some of the most complete rows, not random ones.
on the page
Download a slice instead of filtering the full file
If you need one zone, country or technology, its slice is already filtered and many times smaller.
The free preview shows the format, not a typical mix
Most files are sorted by TLD suffix, so the first 10,000 lines cover only a few suffixes from the start of the alphabet, and .com is not among them. A filter that returns nothing on the preview can return millions of rows on the full file. Use the preview to build and test your commands. They run unchanged on the paid file.
2 · Open the file
Look inside without unpacking it
Every tool here reads the compressed file as a stream. The only difference between the preview and the paid file is how you open it.
import io, json, zstandard
with open("dns-2026-10-16.jsonl.zst", "rb") as fh:
stream = zstandard.ZstdDecompressor().stream_reader(fh, read_across_frames=True)
for line in io.TextIOWrapper(stream, encoding="utf-8"):
row = json.loads(line)
mx = (row["DNS records"] or {}).get("MX") or []
if any((m.get("host") or "").lower().endswith(".google.com") for m in mx):
print(row["Domain"])
Ranked by PR value, saved as woocommerce-de.csv with 12 rows. A CSV your CRM or spreadsheet opens.
Show the query
COPY (SELECT Domain, "PR value" FROM read_json('technologies-2026-10-16.jsonl.zst',
columns = {Domain: 'VARCHAR', "Country by IP": 'VARCHAR',
Technologies: 'JSON', "PR value": 'DOUBLE'})
WHERE "Country by IP" = 'DE' AND list_contains(CASE json_type(Technologies)
WHEN 'OBJECT' THEN json_keys(Technologies)
WHEN 'ARRAY' THEN from_json(Technologies, '["VARCHAR"]') END, 'WooCommerce')
ORDER BY "PR value" DESC
) TO 'woocommerce-de.csv';
Share among these six builders, not among all websites. Run it on a country slice to compare one market.
Show the query
SELECT tech, count(*) AS sites,
round(100 * count(*) / sum(count(*)) OVER (), 1) AS pct
FROM (SELECT unnest(CASE json_type(Technologies)
WHEN 'OBJECT' THEN json_keys(Technologies)
WHEN 'ARRAY' THEN from_json(Technologies, '["VARCHAR"]') END) AS tech
FROM read_json('technologies-2026-10-16.jsonl.zst', columns = {Technologies: 'JSON'}))
WHERE tech IN ('WordPress', 'Wix', 'Squarespace', 'Webflow', 'Drupal', 'Joomla')
GROUP BY tech ORDER BY sites DESC;
Real output on the free 10K-row preview, run on Oct 16, 2026
kayorashop.store2026-10-11
moonovaactivewearshop.com2026-10-10
shopcastava.com2026-10-01
Registered in the last 30 days, with the registrar. Swap shop for your brand name.
Show the query
SELECT "Domain creation date"[:10] AS created, Domain, Registrar
FROM read_json('rdap-whois-2026-10-16.jsonl.zst', columns = {Domain: 'VARCHAR',
"Domain creation date": 'VARCHAR', Registrar: 'VARCHAR'})
WHERE Domain LIKE '%shop%'
AND "Domain creation date" >= CAST(current_date - 30 AS VARCHAR)
ORDER BY created DESC;
Real output on the free 10K-row preview, run on Oct 16, 2026
adamcadre.ac2026-10-24
ama.ab.ca2026-10-31
psychologistsassociation.ab.ca2026-11-09
Sorted by PR value. Many registries renew automatically, so treat it as a watchlist, not a drop list.
Show the query
SELECT Domain, "Domain expiration date"[:10] AS expires
FROM read_json('websites-ranking-2026-10-16.jsonl.zst', columns = {Domain: 'VARCHAR',
"Domain expiration date": 'VARCHAR', "PR value": 'DOUBLE'})
WHERE "Domain expiration date"
BETWEEN CAST(current_date AS VARCHAR) AND CAST(current_date + 30 AS VARCHAR)
ORDER BY "PR value" DESC LIMIT 3;
Share of domains with a usable NS record, grouped by the last two labels of the host name.
Show the query
CREATE OR REPLACE MACRO provider(h) AS
array_to_string(string_split(lower(rtrim(h, '.')), '.')[-2:], '.');
SELECT provider(json_extract_string("DNS records", '$.NS[0]')) AS dns,
count(*) AS domains, round(100 * count(*) / sum(count(*)) OVER (), 1) AS pct
FROM read_json('dns-2026-10-16.jsonl.zst', columns = {"DNS records": 'JSON'})
WHERE dns <> '' GROUP BY dns ORDER BY domains DESC, dns LIMIT 3;
The full file does not fit a spreadsheet, so filter it first, or start from a slice: one technology, TLD, TLD suffix or country as its own file, from its webatla page or the API. With Technologies or All data access, every technology page has a download box for its slice.
From the full file
One TLD or one suffix
jq
TLD is the last label of the domain. TLD suffix is the exact public suffix.
# every domain under one TLD (de covers every suffix that ends in .de)
zstd -dc all-data-2026-10-16.jsonl.zst | jq -c 'select(.TLD == "de")' > de.jsonl
# exactly one suffix
zstd -dc all-data-2026-10-16.jsonl.zst | jq -c 'select(."TLD suffix" == "2ix.de")' > 2ix.de.jsonl
# a suffix that equals its TLD (com, de, ac) has no file of its own:
# filter the TLD slice instead
zstd -dc webatla-all-data-ac-2026-10-16.jsonl.zst | jq -c 'select(."TLD suffix" == "ac")' > ac.jsonl
A website is a row with an IP address, and its country is where the server is hosted (an uppercase ISO code). grep throws away the lines that cannot match, then jq checks the rest exactly.
zstd -dc all-data-2026-10-16.jsonl.zst \
| grep -F '"Country by IP":"DE"' \
| jq -c 'select(."IP address" != null and ."Country by IP" == "DE")' > de-websites.jsonl
Real output on the free 10K-row preview
de-websites.jsonl: 595 lines, the same as jq alone
Export to CSV for Excel or Google Sheets
jq
Excel shows at most 1,048,576 rows, so filter first. Import the CSV as UTF-8 (Data, From Text/CSV) so non-Latin names display correctly.
COPY (
SELECT Domain AS Website, "Country by IP" AS Country
FROM read_json('webatla-technologies-WooCommerce-2026-10-16.jsonl.zst',
columns = {'Domain': 'VARCHAR', 'Country by IP': 'VARCHAR', 'PR value': 'DOUBLE'})
WHERE "Country by IP" = 'DE'
ORDER BY "PR value" DESC
) TO 'woocommerce-germany.csv';
Upsell list: HubSpot sites that also run WordPress
The HubSpot slice, then keep the stacks with WordPress.
COPY (
SELECT Domain AS Website, "Country by IP" AS Country
FROM read_json('webatla-technologies-HubSpot-2026-10-16.jsonl.zst', columns = {
'Domain': 'VARCHAR', 'Country by IP': 'VARCHAR', 'Technologies': 'JSON'})
WHERE list_contains(CASE json_type(Technologies) WHEN 'OBJECT' THEN json_keys(Technologies)
ELSE json_extract_string(Technologies, '$[*]') END, 'WordPress')
) TO 'hubspot-on-wordpress.csv';
Real output on the free 10K-row preview
Website,Country
ateliercrescendo.ac,US
Sales ops
One file per territory
Split a slice into one CSV per server country.
COPY (
SELECT Domain AS Website, coalesce("Country by IP", 'unknown') AS Country
FROM read_json('webatla-technologies-WooCommerce-2026-10-16.jsonl.zst',
columns = {'Domain': 'VARCHAR', 'Country by IP': 'VARCHAR'})
) TO 'woocommerce-by-country'
(FORMAT csv, PARTITION_BY (Country), WRITE_PARTITION_COLUMNS true, OVERWRITE);
Will it fit in a spreadsheet?One Excel sheet holds 1,048,576 rows
HubSpot sites375,063Fits one sheet
WordPress sites23,798,524Filter or split
All data, the full file420,933,308Too big for any spreadsheet
5 · Load it
Postgres, Parquet, or a join with your own list
PostgreSQL
psql
Each line becomes one jsonb row, with a GIN index on Technologies.
psql -c 'CREATE TABLE domains (doc jsonb)'
zstdcat technologies-2026-10-16.jsonl.zst \
| psql -c "\copy domains FROM STDIN WITH (FORMAT csv, QUOTE e'\x01', DELIMITER e'\x02')"
psql -c "CREATE INDEX ON domains USING gin ((doc->'Technologies'))"
psql -c "SELECT doc->>'Country by IP' AS country, count(*) AS sites FROM domains
WHERE doc->'Technologies' ? 'WordPress' GROUP BY 1 ORDER BY 2 DESC, 1 LIMIT 5"
Real output on the free 10K-row preview
COPY 10000
country | sites
---------+-------
US | 300
DE | 110
CA | 29
Parquet
DuckDB
Typed columns, with Technologies as a list of names. Loads into BigQuery, Snowflake, Spark or pandas.
CREATE OR REPLACE MACRO techs(t) AS CASE json_type(t) WHEN 'OBJECT'
THEN json_keys(t) WHEN 'ARRAY' THEN t->>'$[*]' ELSE []::VARCHAR[] END;
COPY (SELECT "Domain" AS domain, "Country by IP" AS country,
"PR value" AS pr_value, techs("Technologies") AS technologies
FROM read_json('technologies-2026-10-16.jsonl.zst', columns = {
'Domain': 'VARCHAR', 'Country by IP': 'VARCHAR',
'PR value': 'DOUBLE', 'Technologies': 'JSON'}))
TO 'technologies.parquet' (FORMAT parquet);
Real output on the free 10K-row preview
technologies.parquet: 10000 rows
domain · country · pr_value · technologies (list of names)
Enrich your own list
DuckDB
Your CSV on one side, the export on the other, matched on Domain.
SELECT c.domain, w."Country by IP" AS country, w."Registrar" AS registrar,
w."HTTP status" AS http, w."Domain creation date"[:10] AS created
FROM read_csv('my-domains.csv') c
LEFT JOIN read_json('all-data-2026-10-16.jsonl.zst', columns = {
'Domain': 'VARCHAR', 'Country by IP': 'VARCHAR', 'Registrar': 'VARCHAR',
'HTTP status': 'INTEGER', 'Domain creation date': 'VARCHAR'}) w
ON w."Domain" = lower(trim(c.domain))
ORDER BY 1;
Real output on the free 10K-row preview
domain country registrar http created
Tamm.AbuDhabi AE ADSIC Registry Operator 200 2018-07-09
easy.ac US MarkMonitor Inc. 200 2014-02-27
global.abb US CSC Corporate Domains, Inc. 301 2019-10-03
6 · Daily updates
See what changed overnight
A fresh export lands every day. Diff two days on Domain and send only the changes downstream. Swap the two files to list the domains that dropped out.
COPY (
SELECT "Domain" FROM read_json('all-active-domains-2026-10-16.jsonl.zst',
columns = {'Domain': 'VARCHAR'})
EXCEPT
SELECT "Domain" FROM read_json('all-active-domains-2026-10-15.jsonl.zst',
columns = {'Domain': 'VARCHAR'})
) TO 'new-since-yesterday.csv' (HEADER false);
7 · Common pitfalls
Things that trip people up
Field names have spaces
Quote them in both SQL and jq.
jq: ."TLD suffix"SQL: "TLD suffix" = 'com.sa'
DNS Status is text
It holds the text "TRUE" or "FALSE", not a boolean, so compare it as a string.
select(."DNS Status" == "TRUE")
The Technologies field comes in several shapes
A map of names to versions (or to objects with a version), a list of names, or null. Read the keys of a map and the items of a list, and you get one list either way.
jq: (.Technologies // []) | if type == "object" then keys else . end
Never unpack a full file to disk
The full All data file is about 34 GB compressed and several hundred GB unpacked. Stream it, as every recipe here does.
Check the first rows
In DuckDB a misspelt column name comes back as NULL, not as an error.
8 · Troubleshooting
When something goes wrong
Symptom
Cause and fix
zstd: … unsupported format on the free preview
The preview is plain JSONL, not compressed. Give it to jq directly, or use zstdcat, which reads both.
Python returns far fewer rows than zstd -dc FILE | wc -l
The file has many zstd frames and was read with a call that stops after the first one. Use stream_reader with read_across_frames=True, as in the Python recipe.
jq: syntax error on .TLD suffix
Quote names that contain spaces: ."TLD suffix"
SQL returns 0 rows without an error
'Country by IP' in single quotes is a string, not a column. Use double quotes for column names.
DuckDB stops partway with Expected OBJECT, but got ARRAY
Automatic schema detection looks only at the first rows. Declare the columns, with nested fields as JSON.
Is my download complete?
zstd -tq FILE && echo OK prints OK when the file decompresses completely.
Every recipe runs on the full daily file
Monthly or yearly access, a fresh export every day, and an API for slices. Every account can also download a free 10K-row preview of each dataset.