How to use webatla files

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.

FileWhat's insideFormat
Full datasetAll rows of the dataset.jsonl.zst
SliceAll 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 pagesRows 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.

Before you start: pick a tool

sudo apt install zstd jq        # Debian, Ubuntu
brew install zstd jq duckdb     # macOS
pip install duckdb zstandard    # Python, any OS

On Windows, use WSL.

The same command on the preview and on the paid file

# free preview (plain JSONL): give the file to jq directly
jq -c 'select(."Country by IP" == "BG") | {Domain, "TLD suffix", "IP address"}' \
  webatla-all-data-preview-10k.jsonl

# paid file or slice (.jsonl.zst): decompress on the fly into the same jq
zstd -dc all-data-2026-10-16.jsonl.zst \
  | jq -c 'select(."Country by IP" == "BG") | {Domain, "TLD suffix", "IP address"}'
Real output on the free 10K-row preview
{"Domain":"360.0.bg","TLD suffix":"0.bg","IP address":"31.13.236.27"}
{"Domain":"doktoranti5.0.bg","TLD suffix":"0.bg","IP address":"185.80.2.212"}
{"Domain":"edu5.0.bg","TLD suffix":"0.bg","IP address":"79.98.104.12"}
...

zstdcat reads both kinds of file, so one pipeline works for the preview and the paid file: zstdcat FILE | ...

DuckDB

brew install duckdbwinget install DuckDB.cli

SELECT Domain, TLD, "Country by IP", "HTTP status"
FROM read_json('all-data-2026-10-16.jsonl.zst',
  columns = {Domain: 'VARCHAR', TLD: 'VARCHAR',
             "Country by IP": 'VARCHAR', "HTTP status": 'INTEGER'})
LIMIT 3;
Real output on the free 10K-row preview
Domain       TLD  Country by IP  HTTP status
360.0.bg     bg   BG             200
digsys.0.bg  bg   NULL           200
dnssec.0.bg  bg   NULL           0

zstdcat + jq

brew install zstd jqapt install zstd jq

zstdcat dns-2026-10-16.jsonl.zst | head -n 1 | jq .
Real output on the free 10K-row preview
{
  "Domain": "360.0.bg",
  "TLD suffix": "0.bg",
  "TLD": "bg",
  "Domain type": "ICANN",
  ...

Python

pip install zstandard

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"])
Real output on the free 10K-row preview
edu5.0.bg
shopping.9guacu.br
ffe.a.se
...

Count rows and list the fields

brew install zstd jqapt install zstd jq

zstd -dc all-data-2026-10-16.jsonl.zst | wc -l
zstd -dc all-data-2026-10-16.jsonl.zst | head -n 1 | jq -r 'keys_unsorted[]'
Real output on the free 10K-row preview
10000
Domain
TLD suffix
TLD
Domain type
HTTP status
...

Make your own random sample

The free preview is not random. To get a representative sample from a file you downloaded:

Linux: shufmacOS: gshuf (brew install coreutils)

zstd -dc all-data-2026-10-16.jsonl.zst | shuf -n 1000 > random-1000.jsonl
wc -l < random-1000.jsonl
Real output on the free 10K-row preview
1000

3 · Answer questions

Questions the daily file answers

The answer first, the DuckDB query one click away. Open a terminal in the folder with your download, type duckdb and paste the query.

Sales Technologies

Which WooCommerce shops are hosted in Germany?

Real output on the free 10K-row preview
ariana.acDE
fjak.acDE
empower.abudhabiDE

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';
Technologies dataset →
Product marketing Technologies

Which site builder runs the most websites?

Real output on the free 10K-row preview
WordPress628 · 80.6%
Wix52 · 6.7%
Squarespace45 · 5.8%
Drupal22 · 2.8%
Webflow20 · 2.6%
Joomla12 · 1.5%

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;
Technologies dataset →
Brand protection RDAP & WHOIS

Which new domains contain your brand name?

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;
RDAP & WHOIS dataset →
Domain investing Websites + Ranking

Which ranked websites expire in the next 30 days?

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;
Websites + Ranking dataset →
Email & infrastructure DNS

Who runs DNS for these domains?

Real output on the free 10K-row preview
cloudflare.com2,277 · 25.1%
registrar-servers.com704 · 7.7%
domaincontrol.com622 · 6.8%

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;
DNS dataset →
Sales All data

Which companies use Google Workspace for email?

Real output on the free 10K-row preview
edu5.0.bgGoogle
shopping.9guacu.brGoogle
ffe.a.seGoogle

456 domains in the free preview, read from their MX records. For Microsoft 365, test for .mail.protection.outlook.com instead.

Show the query
zstd -dc all-data-2026-10-16.jsonl.zst | jq -r '
  select(any(."DNS records".MX[]?.host;
             ascii_downcase | test("(^|\\.)(google|googlemail)\\.com$")))
  | .Domain' > google-workspace.txt
All data dataset →

4 · Build a list

Build a list from the full file or a slice

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
Real output on the free 10K-row preview
de.jsonl      224 lines
2ix.de.jsonl   52 lines
ac.jsonl     5463 lines

Websites hosted in one country, fast

grep + jq

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.

zstd -dc all-data-2026-10-16.jsonl.zst | jq -nr '
  ["Domain","TLD suffix","HTTP status","Technologies"],
  (inputs | [.Domain, ."TLD suffix", ."HTTP status",
             (.Technologies | if . then tojson else null end)])
  | @csv' > all-data.csv
Real output on the free 10K-row preview
"Domain","TLD suffix","HTTP status","Technologies"
"360.0.bg","0.bg",200,"{""HTTP/2"":{""version"":null},...}"
...  10,001 lines

From a slice

Sales

Prospects in one market

The WooCommerce slice, then keep one country.

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';
Real output on the free 10K-row preview
Website,Country
ariana.ac,DE
fjak.ac,DE
empower.abudhabi,DE
Agencies

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);
Real output on the free 10K-row preview
woocommerce-by-country/Country=DE/data_0.csv
woocommerce-by-country/Country=US/data_0.csv
...
Will it fit in a spreadsheet?One Excel sheet holds 1,048,576 rows
HubSpot sites 375,063 Fits one sheet
WordPress sites 23,798,524 Filter or split
All data, the full file 420,933,308 Too 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

SymptomCause and fix
zstd: … unsupported format on the free previewThe 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 -lThe 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 suffixQuote 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 ARRAYAutomatic 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.