Provide current and historical ownership information on domains / IPs. Identify all connections between domains, registrants, registrars, and DNS servers.
Look into all current and historical DNS / IP connections between domains and A, MX, NS, and other records. Monitor suspicious changes to DNS records.
Get detailed context on an IP address, including its user’s geolocation, time zone, connected domains, connection type, IP range, ASN, and other network ownership details.
Access our web-based solution to dig into and monitor all domain events of interest.
Get access to a web-based enterprise-grade solution to search and monitor domain registrations and ownership details for branded terms, fuzzy matches, registrants of interest, and more.
Independent, evidence-based DNS and abuse intelligence for applicants, advisors, governments, and counsel participating in the ICANN 2026 New gTLD Program.
Predictive threat intelligence is your best first line of defense. Subscribe to the feeds to strengthen your cybersecurity posture. Contact us today for more information.
Unlock integrated intelligence on Internet properties and their ownership, infrastructure, and other attributes.
Our complete set of domain, IP, and DNS intelligence available via API calls as an annual subscription with predictable pricing.
Offers complete access to WHOIS, IP, DNS, and subdomain data for product enrichment, threat hunting and more.
Uncover entire attack surfaces with this API to embed asset discovery, vulnerability scanning, and technology metadata into your platform. Now in early access.
Talk to our APIs using LLMs. Connect your preferred LLM to WhoisXML API and simply chat about WHOIS, DNS, threat intelligence, and more.
I’m your Domain Intelligence Assistant. I make it easy to explore WHOIS, DNS, and threat data from WhoisXML API — I’m cloud-based, fast, and always ready to help.
A custom GPT for WHOIS, DNS, IP, and threat intelligence research. Connects ChatGPT directly to WhoisXML API to enable fast, conversational investigations and domain insights.
One Universal API Credit Pool
Simplify API credit purchasing and usage with one balance that works across our product portfolio.
Consumer-visible changes to the bulk feed's published values and semantics — including the ones that are invisible in the schema: same columns, different meaning or different numbers. Nothing else forces a consumer to notice those, so they are called out here.
Read this page beside the data dictionary Website Categorization bulk feed data dictionary, which defines every column and every value domain. Entries are newest first.
Download this document: website-categorization-feed-changelog.md
Also available: the data dictionary
website-categorization-feed-data-dictionary.md, the policy pack policy_v1
group reference website-categorization-policy-pack-v1.md and the policy pack
policy_v2 group reference website-categorization-policy-pack-v2.md.
feed_schema_version in manifest.json is a semantic version, and the component that
moves tells you what to check. A MINOR bump is purely additive to the schema: new
columns appended at the end of the contract order, new manifest keys, or a new manifest
block — a reader that selects by name, or that stops reading after the column it
integrated against, is unaffected by the append itself. A PATCH bump changes the
published row population with no column change at all: no column is added, removed,
renamed or retyped, and no manifest key is removed or given a new meaning, though a PATCH
may add keys inside an existing manifest object — so validate the manifest permissively,
as an extensible object rather than a closed set of properties. A MAJOR bump would
reorder or rename a column; that is the licensable contract and it does not move without
notice. Two bumps below are MINOR because they do both things at once — they append
columns and move the row population — and each says so in its own entry.
One rule cuts across several entries and is worth stating once: a change that is derived at export rather than tracked per row reaches you only through a full snapshot. A row whose band, policy group or suppression status moves under a new rule is not a changed row and does not cross a delta cutoff. Every entry that carries that property says "consume the first ≥ x.y.z set as a full per-partition replace", and the exporter holds the same line on its side: a delta whose policy differs from the snapshot it extends is reported as a chain gap and is refused.
| Artifact | Schedule |
|---|---|
| Full snapshot | Weekly, starting on Sundays, and lately taking about 30 hours to run. Delivered as sixteen partition directories; complete when run_manifest.json appears, usually on Monday, UTC. |
| Incremental delta | Daily at 10:00 UTC, every day except Sunday — the snapshot holds the export lock then — landing within about an hour on weekdays. The next delta spans the gap. The Monday delta waits for the weekly set's run marker, so it lands after the set does. |
Export folders are named by the start of the window they cover, not by the time the
export finished. A dated snapshot is complete if and only if
exports/snapshot_<date>/run_manifest.json exists: read the sixteen partition manifests
from that marker rather than listing the prefix, and do not ingest a partial set. The
marker's absence on a current delivery means the set is still being written or recovered.
Deltas alone cannot keep you compliant. Removals — takedowns and suppressions — retract a row, and only a full per-partition replace expresses a retraction; the status-only lane's baseline is likewise snapshot-borne. The licence carries a term requiring each full snapshot to be ingested within an agreed window and applied as a per-partition replace rather than a merge. See the data dictionary Website Categorization bulk feed data dictionary for the exact wording.
Nothing published changes with this entry: no column, value or row moves.
Corrected in the data dictionary published beside this entry Website Categorization bulk feed data dictionary:
uncategorizable rows keep whatever label is already on file, if there is one. The
dictionary no longer adds that usually there is none.calibrated_confidence now says what it estimates, as the
column's own section does: the probability that a strict blind judge, reading the page,
assigns the same tier1, a strict-match estimate rather than the chance that a label is
usable. It said only that the column is meant to be read as a probability.policy_group_transitions projects each move's full tier path, with an erratum for the sets from 2026-09-20 through 2026-11-01 (first artifact: the 2026-11-08 set's run marker; no schema or version change)No column, manifest key or feed_schema_version changes. From the 2026-11-08 set, the run
marker's policy_group_transitions projects each move through the full tier path it was
recorded at, as data dictionary §10 Website Categorization bulk feed data dictionary
now says. Deltas still do not carry the key.
Erratum. From the 2026-09-20 set through the 2026-11-01 set, the projection read each
move's tier-1 label alone, so a move whose path falls under a deeper rule of the policy pack
was counted in the group of its bare tier-1 label. The 2026-09-17 set, the first 1.14.0 set,
had no earlier marker to measure from and published []. Deeper rules sit under several
tier-1 labels: under Sensitive Topics, for example, deeper paths map to groups such as
adult, hate and weapons, while the bare label maps to sensitive_other. In the
2026-10-18 set's marker, a move to or from Attractions > Casinos & Gambling (gambling) was
counted as travel, the group of bare Attractions. Other policy_group_transitions entries can
also have counted part of their moves in the wrong group pair. top_tier1_transitions and row_count_tier1_changed
are unchanged.
blocked, not parked (first artifacts: delta_2026-11-03_100000Z and the 2026-11-08 set; no schema or version change)No column, manifest key or feed_schema_version changes. What changes is the lane of one
group of rows: those whose crawl succeeded but whose capture extracted to no readable text —
no title, no description and no body text.
parked, with
its category columns NULL, because the capture's content fingerprint was read as the
parking-provider redirect stub that produced most of these captures in July 2026. A page
rendered entirely by JavaScript, a meta or JavaScript redirect and a bot interstitial
produce the same fingerprint, so live sites shipped as parked. That contradicted the data
dictionary's meaning of parked, "an affirmative parking signal".0
or 1 — a best rank of 1,000,000 or better on the Tranco, Majestic Million or Cisco
Umbrella list (data dictionary §10
Website Categorization bulk feed data dictionary) — a capture made from that date
that extracted to no readable text is read as a live site we could not read, and its row
ships as blocked. Most such pages are live; a minority are hosting placeholders or parking
pages our crawler could not recognize. Everywhere else such a row still ships as parked:
outside the Tranco top million, many of the pages we checked turned out to be parking,
for-sale or registrar pages, or hosting placeholders, whose text sits in an image or a
script, so an empty capture alone cannot tell them from a live site. A capture made before
2026-07-31 still ships as parked until a crawl fetches the page again: a
304 Not Modified answer keeps the earlier capture, and with it the parked lane. An
export run without the popularity buckets (rank_ladder: absent) moves no row. Data
dictionary §3 now says so for both lanes.last_known_* block, because no label can be checked against a capture with no
text. A domain our crawler recorded as parked (a parking page, a for-sale page, the redirect
stub itself) still ships as parked. A row whose only label is a name-only one keeps the
lane its crawl decides, as since the 2026-10-02 entry below.
parked_stub_suppression.row_count_overridden counts the same rows as before.site_purpose_status: on a blocked or uncategorizable row in popularity bucket 0
or 1 whose empty capture was made from 2026-07-31 it now reads not_available instead of
undefined, including rows whose lane does not move, because an empty capture is not
evidence that a domain has no business purpose. Rows on parked, inactive and dead
still read undefined, as does a parked-demoted label (data dictionary §11), and
row_count_by_site_purpose_status in manifest.json moves with it.parked to blocked; no row outside
popularity buckets 0 and 1 changes lane. Live checks of a sample of these
pages found mostly live sites, along with pages with no site behind them, redirects to other
domains, JavaScript apps that looked like gambling sites (judged without rendering them), very
few parking or for-sale pages, and hosting placeholders or blank pages, which are wrong as
parked too: their lane is inactive.0 and 1 can change lane without being crawled again; that move,
too, reaches you only through a full set.Nothing published changes with this entry. It is the customer notice for feed 1.17.0,
dated ahead of its first artifact. feed_schema_version 1.16.0 → 1.17.0, a MINOR bump on
the definition in data dictionary §1
Website Categorization bulk feed data dictionary: two columns are appended after
column 44, and columns 1–44 keep their names, positions and types. Reading by name, or by
position up to column 44, is unaffected, and no row enters or leaves the feed.
| # | Column | Type | Value |
|---|---|---|---|
| 45 | content_purpose_id |
string |
1020 — IAB Content Purpose > Informational > News — or NULL |
| 46 | content_purpose_source |
string |
curated, or NULL exactly where column 45 is NULL; schema_org is reserved (see below) |
What it says. IAB Content Taxonomy v3.1 has no News topic, so a news outlet's
tier1–tier4 name what it covers — Politics, Sports and so on. The IAB Tech Lab
expresses news as a Content Purpose: a Descriptive Vector carried next to the topic, never
in place of it, and column 45 carries its id. No topic column changes: filter news on column
45 and keep your topic rules on columns 2–6. 1020 is not a Content Taxonomy id and never
appears in iab_id or openrtb_cat.
Where it comes from — a curated list, and only that. The value is set on the domains of a
curated list of known news publishers maintained by our team, and on no other row; the list is
versioned, and every change to it is reviewed before release. Every domain on the list is also
one of our hand-vetted curated/* rows, so the value appears only on those rows. The feed
reads no page markup, so it cannot tag a news site that is not on the list: this is a list,
not news-site coverage. Most news sites in the feed read NULL.
NULL means unknown, never "not news". Do not build an "exclude news" rule out of the NULL rows.
schema_org is a reserved value of column 46. The real-time API's opt-in
include=content_purpose block has a second source, schema_org — the site's own schema.org
NewsMediaOrganization markup. The feed does not emit it in 1.17.0, because the export carries
no page markup. It is named now so a loader that validates column 46 against a strict enum can
accept it in advance; a release that starts emitting it will say so here first. For a listed
domain the feed and that API block agree (1020, curated); a domain the API tags from markup
alone reads NULL in the feed.
When. The 1.17.0 exporter deploys no earlier than Tuesday 2026-11-03 at 13:00 UTC,
after that day's 10:00 UTC delta; if this date moves, we will say so in a dated amendment to
this entry. If it deploys then, the first 1.17.0 artifact is Wednesday 2026-11-04's delta,
in the folder delta_2026-11-03_100000Z, and the first 1.17.0 full set is the
Sunday 2026-11-08 set. Nothing published before the deploy carries the two columns.
The deltas are enough for this change. Curated rows ride every delta whole, so the Wednesday 2026-11-04 delta already carries every tagged row. If you consume deltas only, add the two columns to your table as NULL and apply that delta: the two columns then hold exactly what a 1.17.0 full set publishes in them. A chain check that compares each artifact with the one before it (data dictionary §10) reports the 1.16.0 → 1.17.0 version step on that first delta — it cannot know what a version change carries — so expect that one report; this entry is what it points to.
The 1.16.0 rule still binds. 1.17.0 carries 1.16.0's band policy unchanged: column 8 and
confidence_band_policy (v4) mean exactly what the 1.16.0 notice below says. That notice
asks for a full set at 1.16.0 or later before you rely on ≥ 1.16.0 deltas, and the 2026-10-11
set was the first such set.
Two new manifest keys, on every partition and delta manifest (data dictionary §10);
manifest_keys_version moves 1.0.0 → 1.1.0, and no key is removed or given a new meaning:
content_purpose_list — {path, sha256, row_count} of the list the values were read from.
sha256 changes whenever the list does; row_count is how many domains it names.row_count_by_content_purpose_source — rows per column 46 value: curated, schema_org
(always 0 in 1.17.0) and null; sums to row_count, and the run marker sums it across the
sixteen partitions.On a delta, and on a full set's run marker, the curated count equals the list's row_count
unless a listed domain did not ship as a curated row in that artifact — a takedown withholds
it, for example. A partition manifest counts only its own partition's tagged rows. The run
marker also carries the set's list as content_purpose_list ({sha256, row_count}), and a set
whose partitions were tagged from two different lists gets no marker — it is not complete
until it is re-published from one.
No column or schema-version change. Which rows a delta carries changes slightly, and the data dictionary published beside this entry Website Categorization bulk feed data dictionary corrects three passages.
From delta_2026-10-30_100000Z, each delta also re-reads crawls stamped in the 30 minutes
before its window. A crawl is stamped when its page is fetched and saved a little later, so a
crawl stamped just before a 10:00 UTC cutoff could be saved after that delta's read and still
fall outside the next delta's window, so it reached no delta and only the full set carried it.
The look-back delivers any crawl saved within 30 minutes of its stamp. Its cost is repetition: a
delta can repeat a row the previous delta carried (its status_observed_at falls in the 30
minutes before the window), which an upsert by domain absorbs.
Corrected in the data dictionary published beside this entry:
domain is normally, not always, the registrable domain — some rows are a host below one
or a bare public suffix. Look a name up as written, then fall back to its registrable domain.Nothing published changes with this entry: no column, value or row moves. It corrects documentation that a review of the 2026-10-18 set found out of step with the data, and it carries one notice that is late.
Late notice: the bulk categorization lanes apply the vendor rule, and have since
2026-09-02. The data dictionary said they did not yet and that this changelog would say
when they did; it did not say so until now. A row the rule rewrote carries
model_name = pipeline/vendor_rule, tier1 = Technology & Computing with tier2–tier4
NULL, and NULL calibrated_*. The rule itself, and the trade it makes, are in data
dictionary §5 Website Categorization bulk feed data dictionary and in the
2026-09-02 entry on the pipeline/adult_filter and pipeline/vendor_rule families, below.
Corrected in the data dictionary published beside this entry:
classified_at is NULL, not old.parked or inactive, so neither lane is a
cull key on its own; parked, not blocked, is the largest non-active lane; how to filter
the labels blocked rows carry; curated infrastructure zones carry an asserted active.calibrated_confidence estimates — the probability that a strict blind judge
assigns the same tier1, which on average understates how often a label is acceptable; the
vendor rule in the bulk lanes; the gambling filter's band, which reads LOW on every row
from 1.14.0; which rows the language-NULL guard spares; that the v4 API returns no
confidence_tier; and how to read a name-only row's model_version."0", "1" and "3" are emitted and "2" is reserved, and
coverage_by_rank_bucket counts presence; parked_stub_suppression does not trend to zero;
the run-level language map usually names 29 subtags.Corrected in this changelog: in the 2026-09-11 entry below, how the delta rows with no
timestamp inside the window read, and, under the 2026-09-13 correction of its blocked-lane
item, a dated note that 30-day eligibility for a re-attempt is not a schedule.
delta_2026-10-09_100000Z, published Saturday 2026-10-10, and the Sunday 2026-10-11 set)Nothing published changes with this entry. It is the customer notice for feed 1.16.0,
published before the deploy it describes so that a loader can adjust first.
feed_schema_version 1.15.0 → 1.16.0, a MINOR bump: no column is added, removed or moved,
and the 44 columns keep their order and their types. What moves is the published value of
one column — confidence_tier (column 8) — on one population.
Unlike 1.15.0, this re-rates rows you already have. The 1.15.0 notice below describes a new lane arriving and says no other row re-bands; that was true of 1.15.0 and is not true of this release. Rows published in every set since 1.14.0 change band here. That is why this is its own release with its own notice rather than an amendment to that one.
When. The 1.16.0 exporter deploys Friday 2026-10-09 at 13:00 UTC. The first artifact
that reports feed_schema_version 1.16.0 is the daily delta published Saturday 2026-10-10,
in folder delta_2026-10-09_100000Z: a delta folder is named by the start of its window, so
this one carries the deploy's date but is cut after the deploy. Every delta after it reports
1.16.0 or later. The re-rating itself does not arrive with them. The band is derived at
export rather than tracked, so a row whose band moves is not a changed row and does not cross a
delta cutoff: no delta carries the re-rating as a whole, and a capped row reaches a delta only
when the row changes for some other reason, such as a re-crawl or a re-label. The re-rating
reaches you with the Sunday 2026-10-11 full set, and only by ingesting that set. Nothing
published before 2026-10-09 carries any of this.
Ingest the full 2026-10-11 snapshot set before relying on 1.16.0 deltas. This is the same
transition rule the 1.14.0 notice states, for the same reason and with no exception: the first
≥ 1.16.0 artifact you ingest must be the full sixteen-partition set, or your table will carry
two bands for one row set with nothing marking which is which. manifest.json carries
confidence_band_policy so you can tell them apart — see below. In practice: skip
delta_2026-10-09_100000Z, the one delta that reports 1.16.0 before that set, and do not apply
it after the set either, because it is older than the set; once the set is loaded, resume with
delta_2026-10-10_100000Z.
Rows whose model_version (column 11) carries one of three provenance suffixes — the
second +-delimited field of that column, split_part(model_version, '+', 2) — and whose
model_name (column 10) is an ensemble writer (ensemble-*, multitask-ensemble-*):
| provenance suffix | what wrote it |
|---|---|
teacher_bf1 |
the 2026-07/08 teacher pass that re-scored Business and Finance rows: a second model's verdict written over the classifier's own call. |
nocontent_bf1 |
rows the same pass marked as having no judgeable content — it looked, there was nothing to read, and the classifier's original score was left standing. |
teacher_purpose_v2 |
a purpose-teacher lane that has not been written yet. It is named here ahead of its first row so that the cap is in place from that row rather than one release later. |
On such a row:
HIGH publishes MEDIUM;LOW where calibrated_confidence_tier (column 25) is DOUBTFUL;MEDIUM also publishes LOW where that same column is DOUBTFUL — a second,
smaller population that leaves MEDIUM as well as the one that leaves HIGH. If you key on
confidence_tier IN ('HIGH','MEDIUM') rather than on 'HIGH' alone, these rows leave your
selection too;LOW is unchanged, and so is a stored MEDIUM at any other calibrated tier —
the rule is a cap: it moves a band down or leaves it alone, and it never promotes
anything;PARKED and a NULL band pass through untouched, as under every policy.No other row and no other column moves. A row outside the three suffixes — including the
ml_en / ml_non_en route stamps and the bare base version, which record which routed model
produced the label rather than a pass over it — bands exactly as it does today. The value
domain of column 8 is unchanged: HIGH / MEDIUM / LOW, plus PARKED. confidence, the
calibrated_* block, tier1–tier4, iab_id, model_name, model_version and
policy_group carry exactly what they would have carried under the old policy, on every row
including these.
No Business and Finance row moves. The teacher pass writes an ensemble
model_name, so a teacher row that kept tier1 = 'Business and Finance' has been capped by
1.14.0's ensemble rule since that release. What 1.16.0 moves is the capped-suffix population
whose tier1 is not Business and Finance.
This is not a loss of rows, labels or groups. Every row that re-bands still ships, with the
same tier1–tier4, the same iab_id, the same policy_group and the same model_name. If
your block list or your routing is keyed on confidence_tier = 'HIGH', it will stop seeing
them — and if it is keyed on confidence_tier IN ('HIGH','MEDIUM') it will still stop seeing
the smaller MEDIUM-at-DOUBTFUL population, which lands on LOW. That is what this notice is
for.
A blind re-read on 2026-09-12 put a sample of these rows in front of a judge that reads the
live page and compared its verdict with the stored label. This family's labels agreed with a
fresh reading of the page far less often than the threshold we had pre-registered for keeping
them at our top confidence word — so publishing them as that word was not defensible. They
are scheduled for re-vetting; until that has happened they publish at MEDIUM at most, which
is the honest reading of what a re-scoring pass over a label actually establishes.
The three suffixes travel together because they are one lane: a re-scoring pass, its "nothing to read" marker, and the purpose pass that follows it.
To exclude the capped population entirely, on any version of the feed:
WHERE COALESCE(split_part(model_version, '+', 2), '') NOT IN ('teacher_bf1', 'nocontent_bf1', 'teacher_purpose_v2')
The COALESCE is load-bearing, not defensive. Rows that ship a crawl status but no category —
the status-only lane — publish model_version as NULL, and in SQL NULL NOT IN (...) is NULL,
not true. Without it those rows fail the predicate and drop out of a query whose job is to
exclude the capped population and keep everything else.
To rebuild the band as it was before any of the policies — the raw confidence cut —
confidence (column 7) is untouched on every row, and the policy-v1 reconstruction recipe in
the 2026-09-10 1.14.0 notice below still applies unchanged, including its rounding rule. On
every row the feed already carried before 1.15.0, that recipe returns the band the feed
published through 1.13.x, whatever policy the artifact you are reading was produced under. A
name-only row (model_name beginning llm-name/, the 1.15.0 lane) is the exception: it is
stored at MEDIUM whatever its confidence, so there the recipe can return HIGH where policy
v1 returns MEDIUM.
confidence_band_policy in manifest.json reads v4 from the first 1.16.0 artifact on
(v1 or an absent key is the raw pass-through, v2 from 1.14.0, v3 from 1.15.0). Pin it
beside confidence_calibration_map_version, as before: the DOUBTFUL cell above is decided by
the calibration map, so a refit moves which of these rows land on LOW rather than MEDIUM.
The v4 API returns no confidence_tier, so no v4 answer changes with this release. On the
legacy /categorize route, an answer served from the stored corpus the feed is exported from (routed_to: "hetzner-cache") carries the v4 band from the same deploy, so until you load the 2026-10-11 set it can differ
from the band in your copy of the feed.
domain_status (first artifacts: delta_2026-10-02_100000Z and the 2026-10-04 set; no schema or version change)No column, manifest key or feed_schema_version changes. The 1.15.0 notice (the 2026-09-14
entry below) and data dictionary §3 Website Categorization bulk feed data dictionary
say a name-only label leaves domain_status as the crawl decided it. The 2026-10-01 entry
describes a small set of rows for which the deltas published before this change, starting with
delta_2026-09-30_100000Z, did not keep that promise: the exporter counted the name-only label
as a category when it chose the lane. From the artifacts named above it no longer does:
active ship as uncategorizable, still with their name-only
label. A name-only label no longer puts a row in active, so these rows also leave the
manifest's content_accuracy_denominator_row_count.parked ship as uncategorizable. The last page captured for
each was a parking provider's redirect page, so their category columns stay NULL, as before.Only rows whose sole label is a name-only one and whose latest crawl succeeded change lane; no
other row changes lane and no category changes. Deltas carry these rows again only when their
classified_at or crawl_attempted_at crosses a delta's cutoff, as a new label or a new crawl
attempt does; this change moves neither, so a consumer that applies only deltas keeps the
earlier lane until then. To pick up every corrected lane, replace from the 2026-10-04 set, the
first full set carrying name-only rows.
No column, manifest key or feed_schema_version changes. This entry corrects the 1.15.0 notice
(the 2026-09-14 entry below) on four points: how many rows the lane labels, which rows those
are, the lane a labelled row ships in, and when a page-derived label replaces a name-only one.
The rows were written on 2026-09-30 as announced:
blocked or
uncategorizable rows of the 2026-09-13 set without a category, the rows the notice
described, which the lane cannot label (next point), and the others were not in that set but
had a category row by the time of the load.blocked and uncategorizable rows. It cannot: the lane writes only where we
hold no category row for a domain, and the feed carries such a domain only when its crawl
finds it parked or a placeholder, or it is on our curated list. The first artifacts carry
them.blocked. The rest ship with
NULL category columns, as data dictionary §3
Website Categorization bulk feed data dictionary describes. Nearly all of them are
in the feed only because a name-only row now exists for them. Some are domains the crawl
found dead, inactive or parked, where a name-only label is withheld like any other; for
others the last page we captured was a parking provider's redirect page, which withholds the
category on every lane (parked_stub_suppression in the manifest, dictionary §10). The
filters under How to leave it out in the notice remove every name-only label but not these
rows, which look like any other row without a category.uncategorizable: some would ship as active with their name-only label, and
others as parked with NULL category columns, because the last page we captured from them was
a parking provider's redirect page. To tell a name-only label from one read from the page,
test policy_confidence_basis or model_name, not domain_status. Data dictionary §3 now
notes this and points here.The first artifact carrying these rows is the delta in folder delta_2026-09-30_100000Z, the
10:00 UTC run on 2026-10-01 (folders are named by the start of their window); its manifest
counts the labelled ones under name_only in row_count_by_writer_family_x_tier. The
2026-09-14 entry now carries a dated amendment pointing here, and its description of the rows
the lane labels and its sentence on replacement are corrected in place; nothing else in it
changes.
dead, not blocked (no schema or version change)No column, manifest key or feed_schema_version changes. What changes is the lane a domain
ships in once our crawler learns that its name no longer resolves, and with the lane, the
category. dead rows carry every content column NULL by design (data dictionary §3 and §9
Website Categorization bulk feed data dictionary), so a row that would have stayed
labelled in blocked now ships without a current label. Where we keep history for its earlier
label, that label appears in the last_known_* columns (§8).
blocked. That did
not match the data dictionary's meaning of dead, which then read "DNS failure / NXDOMAIN /
404".dead.
DNS answers that are not definitive, such as a timeout or a server failure, still ship as
blocked. Data dictionary §3 now says so for both lanes.blocked row whose status_observed_at is before 2026-09-23 may still be a name that no
longer resolves. Daily deltas carry the change from delta_2026-09-22_100000Z, published
2026-09-23, and the 2026-09-27 set is the first weekly set to carry it.dead lane grew sharply in the 2026-09-27 set, and blocked grew
more slowly than in earlier weeks. In that set, each partition our nightly crawl visited after
the change added far more dead rows than the partitions it visited just before. Most rows
that move from active or parked straight to dead are this change; a 404 could already
do that.dead only if
we hold a category row for it, labelled or not (data dictionary §10), and a curated/* row
stays active, because our curated list asserts that lane rather than a crawl observing it
(§3). A domain without a category row does not ship as dead, as before, so the manifest's
inventory_dead_status_by_rank_bucket grows much faster than the dead row count. Those keys
also count as never-shippable, which is the main reason coverage_shippable_by_rank_bucket
rose in the 2026-09-27 set.No column change, no manifest change, no feed_schema_version change and no value in the feed
moves. Data dictionary §5 Website Categorization bulk feed data dictionary ("What it
estimates — and what it deliberately does not") made two statements that were wrong, both now
corrected:
tier1 the
classifier keeps the most likely child at each tier whenever that child holds more than a
small fixed share among its siblings, so nearly every path it writes runs on until the
taxonomy has no deeper category; how deep a path goes is not a measure of how much the page
supported. The guidance is unchanged: key policy on tier1 (or tier1 + tier2) and treat
tier3 / tier4 as descriptive detail.datafeeds/ (withdrawn the same day)Withdrawn, 2026-09-15. A second copy of every set and delta's data files and manifests,
under a datafeeds/ sub-folder and named the way the WhoisXML datafeeds server names files,
was announced for the exporter deploy of Thursday 2026-09-17 and withdrawn the same day. The WhoisXML datafeeds team asked for a different shape, and that copy is now built and delivered outside this bucket, so no set and no delta under exports/
carries a datafeeds/ sub-folder, and there is nothing to exclude when you sync. Only the
2026-09-13 set has one, filled by hand before the change of plan; it stays (nothing under
exports/ is ever deleted) and you can ignore it. Nothing about the documented objects
changed: the sixteen partition folders, run_manifest.json, the delta folders and every name
in the data dictionary stay exactly as they are.
The date the entries below first announced — the Sunday 2026-09-27 snapshot set — is superseded. Nothing about the schema, the columns or the corrections changes; only when they reach you. The schema and the corrections it carries were ready ahead of the announced date, and moving the exporter change forward keeps it clear of a separate release the following week.
Amended 2026-09-17: no delta publishes on Friday; the first 1.14.0 delta is Saturday's. The 2026-09-17 set started right after Thursday's deploy and, like every full set, takes about a day to run, so it still holds the export lock when Friday's 10:00 UTC delta is due. That run is skipped, exactly as Sunday's is every week while the weekly set runs. Saturday's 10:00 UTC delta covers Thursday 10:00 UTC to Saturday 10:00 UTC, so nothing is lost and the chain has no gap.
exports/snapshot_2026-09-17/. The
run starts immediately after the deploy and the set is complete when its run_manifest.json
appears — expect that on Friday 2026-09-18 UTC, about a day after the run starts. It is a
full per-partition replace exactly like a weekly set: the same sixteen partitions, the same
partition and run manifests, nothing special in its shape. Treat it as a weekly set that lands
on a Thursday.delta_2026-09-17_100000Z — named, as always, by the start of its window — covering Thursday
10:00 UTC to Saturday 10:00 UTC. Thursday's delta ran before the deploy, on 1.13.2, so the
chain has no gap.delta_2026-09-16_100000Z) report
feed_schema_version 1.13.2.Ingest a full 1.14.0 snapshot before relying on 1.14.0 deltas. The rule the 2026-09-10 entry states is unchanged and now points at the 2026-09-17 set: three of the 1.14.0 changes are re-derivations that touch every row, and a delta cannot carry a row that did not move. The dates in the 2026-09-10 entries below have been corrected in place to match.
Nothing published changes with this entry. It is the customer notice for feed 1.15.0, dated
two weeks ahead so a loader has the whole window to adjust. feed_schema_version
1.14.0 → 1.15.0, a MINOR bump: no column is added, removed or moved; column 42 gains one
value, and a small population of domains is added to the feed, most of them with a category.
Amended 2026-09-15, before any 1.15.0 artifact exists: the lane ships the HEAD ONLY. The torso validation this entry reserved came back below the precision bar we set for shipping it, so rows ranked 100,000–1,000,000 are not labelled this way for now. Nothing else in this entry changes: the mechanism, the column values, the dates and the filter are exactly as first published. If the torso ships later it will be its own changelog entry, not a quiet addition to this one.
Amended 2026-10-01: the lane added domains that the 2026-09-13 set did not carry, not labels
on existing rows. Most of them ship with a name-only label, and when we checked on 2026-09-30 a
small number would ship in the lane a label read from the page would give them, although the
domain_status row of the table below says the label leaves the lane the crawl decided. The
first delta carrying them is folder delta_2026-09-30_100000Z; see the 2026-10-01 entry. The
filters under How to leave it out remove every name-only label, but not the rows the lane
added that ship without one. Under What the lane is below, the description of the rows it
labels and the sentence on replacement are corrected in place; nothing else in this entry
changes.
Amended 2026-10-02: the domain_status row of the table below holds again from the first
artifacts exported after the 2026-10-02 deploy; a consumer that applies only deltas picks up the
corrected lanes by replacing from the 2026-10-04 set. See the 2026-10-02 entry.
When. The 1.15.0 exporter deploys Tuesday 2026-09-29 at 13:00 UTC. The rows described here are written on Wednesday 2026-09-30, so the first artifacts carrying them are the daily deltas from 2026-09-30 and the Sunday 2026-10-04 full set. Nothing published before 2026-09-29 carries any of this; a delta between the deploy and the load reports 1.15.0 with no name-only rows in it yet. No full-snapshot ingest is required before relying on 1.15.0 deltas: no existing row is re-derived, so every new row moves and every delta carries it.
For domains in the Tranco top 1,000,000 for which we held no category row, mostly sites the crawler cannot read, two independent language-model families label the site from the domain name alone, separately. A row ships only where both agree on the tier-1 category and each puts at least 0.7 on it; otherwise nothing is written and the lane leaves the domain as it was, which keeps it out of the feed unless its crawl finds it parked or a placeholder, or it is on our curated list. Every row that ships with a name-only label is ranked in the top 100,000 (see the amendments above and the 2026-10-01 entry). The lane is gap-fill only: it never replaces a label that exists, and a page-derived label replaces it once a later crawl reads the page and our classifier labels it. Only the top 100,000 is labelled this way today, and nothing ranked past 1,000,000 ever will be.
| column | value |
|---|---|
model_name (10) |
llm-name/consensus_v1 |
policy_confidence_basis (42) |
name_only — the new value. The closed set becomes model_calibrated, model_raw, filter, rule, curated, carry_over, name_only, none. |
tier1 … tier4 |
tier 1 only; tier2–tier4 NULL |
confidence (7) |
the lower of the two models' self-reported probabilities, so always in [0.7, 1] — a scale of its own, not comparable with any other family's (data dictionary §5) |
confidence_tier (8) |
MEDIUM or LOW, never HIGH: band policy v3 = v2 plus rule 5 (name_only_never_high), a cap on this family only. confidence_band_policy in manifest.json reads v3 from the first 1.15.0 artifact on; no other row re-bands, because rule 5 is keyed on this family's model_name alone. |
calibrated_confidence (24) |
NULL — the lane is never calibrated |
domain_status (3) |
unchanged: the lane the crawl decided. A blocked row with a name-only label is still blocked. |
last_known_* (dictionary §8) |
never carries a name-only label: when such a row is later withdrawn, no history block is projected from it |
row_count_by_writer_family_x_tier (manifest) |
gains the name_only family |
The separate validation this entry reserved for the torso (rank 100,000–1,000,000) ran on 2026-09-15, judged by the same blind judge that scores the rest of the feed, and did not clear the bar we set as the condition for shipping it. Those labels are therefore not shipped, and the first artifacts carry head rows only. We would rather withhold a label than ship one below the bar we set.
To exclude the name-only lane entirely, on any version of the feed:
WHERE COALESCE(policy_confidence_basis, '') <> 'name_only'
or, equivalently, on the writer family:
WHERE COALESCE(model_name, '') NOT LIKE 'llm-name/%'
The COALESCE is load-bearing, not defensive. Rows that ship a crawl status but no category —
the status-only lane — publish policy_confidence_basis and model_name as NULL, and in SQL
NULL <> 'name_only' and NULL NOT LIKE 'llm-name/%' are NULL, not true. Without it those rows
fail the predicate and drop out of a query whose job is to exclude the name-only lane and keep
everything else.
A block list built on confidence_tier = 'HIGH' never sees these rows. The real-time API does
not serve name-only labels: it keeps answering those domains live, as today.
304 Not Modified answer is not a blocked site (first artifact: the 2026-09-17 set)No column change and no schema-version change: the same rows carry a different domain_status.
The correction ships with the 1.14.0 exporter (deploy Thursday 2026-09-17), so the 2026-09-17
snapshot set is the first artifact that carries it.
304 Not Modified, the crawler records the status not_modified: the content
we hold is confirmed current and nothing was fetched. The exporter listed that status among
the blocked ones, so a healthy, unchanged site was published as blocked — against §3 of
the data dictionary Website Categorization bulk feed data dictionary, which defines
blocked as a site we could not read.not_modified status lanes like a successful
crawl: active where the row carries a label, parked where the content we hold is the
parking-provider stub (the stub override in §10 applies as before), uncategorizable where no
label exists. Nothing else about the row moves: the label, its calibrated value and
status_observed_at (which dates the 304 answer) are as they were. The real-time API is not
affected: its domain_status answer comes from the category store, not from this rule, and
the category it returns for these rows does not change.row_count_by_domain_status to show blocked lower and active higher, before the week's own
movement.blocked lane's growth over the 2026-08-30
→ 2026-09-06 sets to paused re-crawls (corrected there). The mapping corrected here also
contributed to that growth, alongside the re-crawl cadence and real anti-bot answers.No column change. The key ships with 1.14.0 (first artifact the 2026-09-17 set); the three clarifications describe artifacts you already have.
domain was an internationalised name in Unicode form (helaförsäkring.se), most of them beside the same name in punycode (xn--helafrskring-mcb3w.se) — two rows for one registrable domain, and the two disagreed on lane or label in many of those pairs. From 1.14.0 the exporter withholds a non-ASCII
domain and publishes the count as row_count_excluded_non_ascii_domain (partition and run
manifests; 0 is a measured zero). The withheld rows include names whose punycode form is not
in the feed yet; they return once the inventory ingest normalises them, after which the
counter reads 0. A delta cannot retract a row you already hold, so the correction reaches you
with the first 1.14.0 set — a full replace. Data dictionary §2 and §7
Website Categorization bulk feed data dictionary now state the form: lowercase
ASCII, punycode for internationalised names.status_observed_at can be null on a handful of rows, and the manifest says where. A
handful of rows of the 2026-09-06 set carry a status with no attempt timestamp. The
dictionary's §4 promised every row is dated; it now says every crawler-observed row is dated
and points at the null bucket of row_count_by_status_age (1.14.0), which counts the
exceptions per lane. A curated row is dated only where the inventory has a row for that
domain.classified_at is NULL and status_observed_at is older
(corrected 2026-10-21: first stated with both timestamps older). Such a row qualified
through a category-side event the feed does not show — an abstain, or a label withdrawn by
suppression — and it is a real update: applying only the rows whose timestamps fall inside the
window keeps withdrawn labels. Stated in dictionary §1.1.blocked lane is growing. It grew set-wide across the 2026-08-30, 2026-09-02 and
2026-09-06 sets. A blocked row keeps its last label and dates it (§3, §9), and
row_count_by_domain_status in each manifest is the number to watch. Corrected 2026-09-13:
this item first attributed the growth to paused re-crawls of blocked sites and said the share
would keep rising until they resumed. That was wrong. Re-crawls of blocked sites are not
paused — a blocked domain becomes eligible for a re-attempt after 30 days. The growth comes
from the nightly refresh reaching sites it last observed in May 2026, most of which no longer
answer; between the 2026-09-06 and 2026-09-13 sets, nearly every p06 row that moved from active carried a status_observed_at from May 2026 before the move (your own copies of those two sets show this). Sites answering
304 Not Modified were also published as blocked (see the entry above). The rest is sites
that did not answer the re-fetch, and real anti-bot answers. Corrected 2026-10-21: "eligible
for a re-attempt after 30 days" is eligibility, not a schedule. No re-try schedule is
promised, so a blocked row can keep its verdict for months. row_count_by_status_age in each
manifest shows how old the verdicts are (dictionary §4).feed_schema_version 1.13.2 → 1.14.0, a MINOR bump. Ships with the first ≥ 1.14.0
export — the Thursday 2026-09-17 snapshot set (exporter deploy Thursday 2026-09-17, after
that day's 10:00 UTC delta); nothing published before that date carries any of this. Ingest a full snapshot at 1.14.0 before
relying on 1.14.0 deltas: three of the changes below are re-derivations that touch every
row, and a delta cannot carry a row that did not move.
Columns 1–38 are unchanged in name, position and type — the append-only contract, same precedent as 1.6.0 and 1.12.0. A reader that selects by name or by position is unaffected by the append itself.
| # | Column | What it is |
|---|---|---|
| 39 | safety_status |
Whether a brand-safety filter reached a decision: flagged / assessed_clean / not_assessed. Never NULL. |
| 40 | safety_flags |
The flagged block-signal themes, as a list. |
| 41 | safety_detail |
A compact JSON object, one entry per filter family. |
| 42 | policy_confidence_basis |
What the row's confidence / calibrated_confidence IS — model_calibrated, model_raw, filter, rule, curated, carry_over, none. |
| 43 | policy_group_set |
policy_group plus every group the pack declares it is blocked WITH. |
| 44 | language_primary |
The BCP-47 primary subtag of column 14. zh-CN, ZH_TW and zh-Hans-CN all read zh; eng stays eng, deliberately, because the two spellings come from different detectors. Column 14 stays raw. |
The brand-safety axis is documented in data dictionary §13, columns 42–43 in §11 Website Categorization bulk feed data dictionary.
policy_basis gains the value safety — a breaking value additionThe feed's policy pack moves from policy_v1 to policy_v2. The new pack reads the
safety axis first, then site_purpose, then the primary tier path, and policy_basis
(column 37) says which answered. Its documented values are therefore primary,
site_purpose and now safety. role is reserved for a later release and is not
emitted at 1.14.0.
If you validate column 37 against a strict enum, add safety before ingesting. It is
confined to an opt-in block, and it is why the pack change ships via a full snapshot rather
than a delta: policy_pack.version in manifest.json moves with it, and a delta whose pack
differs from its predecessor is a chain gap the exporter refuses to write.
Because the safety axis is read first, some flagged rows now carry the theme's group where
they previously carried their label's — a gambling-flagged row labelled Sensitive Topics
reads gambling rather than sensitive_other. Nothing about the label, the confidence or
the calibrated block moves with it.
policy_group: news is retired, current_events replaces itpolicy_v2 also renames one group, so this is a breaking value change on column 36, not
only on column 37. The three current-affairs roots — Crime, Disasters and
War and Conflicts — published as news under policy_v1; from 1.14.0 they publish as
current_events. No other path changes group, and the group count is unchanged at 47.
If you filter or validate policy_group against a fixed list or an enum, replace news
with current_events before ingesting the first 1.14.0 snapshot — from that artifact on, a
policy keyed on news matches nothing. policy_group_set (column 43) carries the new id
too, and so does row_count_by_policy_group in manifest.json.
The rename is why policy_v2 is a new pack id rather than a mapping bump: a group id
never changes meaning inside a pack, so the old id was retired with its pack. The old id was
misleading — IAB v3.1 has no news-publisher node, so news never contained newspapers or
news portals, and a policy that allowed or blocked it allowed or blocked no news site.
current_events says what the group actually is; there is still no group that contains news
publishers. Full definition, the per-group table and the machine-readable diff:
website-categorization-policy-pack-v2.md.
confidence_tier is derived (band policy v2)Column 8 is derived at export from the writer (model_name), the label (tier1) and the
calibrated tier (calibrated_confidence_tier), instead of being the stored 0.9 / 0.7
cut passed through. The value domain is unchanged — HIGH / MEDIUM / LOW, plus
PARKED. manifest.json carries confidence_band_policy: "v2", and a manifest that does
not carry the key at all is v1. The notice below carries the per-family detail and the
recipe for rebuilding the old band from columns you already receive; read it before the
first 1.14.0 artifact lands.
This is the change that makes 1.14.0 a MINOR rather than a PATCH. Rows written by the
ensemble family that carry a category with no page language recorded, behind a captured
content hash, now ship status-only: the category block is suppressed and no
last_known_* projection is made. Those labels were produced from content we cannot show
was in a language the model was fitted for, and a judged read put their agreement low enough
that suppression is the correct answer rather than a re-score.
manifest.json carries a language_null_suppression block from 1.14.0 on, counting the
rows this export changed and the three exempt populations (block-critical groups, the
popularity head, and rows whose group could not be resolved) — those are not suppressed.
Rule lanes are untouched. Expect the labelled population of a partition to fall; the
manifest block of your own delivery is the authority on how much.
Every export now publishes the distributions a consumer previously had to scan for, at
partition, delta AND run level: row_count_by_confidence_tier,
row_count_by_writer_family_x_tier, row_count_by_policy_group (every pack group present,
0 where empty), row_count_policy_group_uncalibrated,
row_count_by_policy_group_by_writer_family, row_count_by_safety_status,
row_count_by_safety_flag and row_count_by_language_primary. The four coverage counters
that existed per partition (row_count_with_policy_group, row_count_calibrated,
row_count_by_site_purpose_status, row_count_with_last_known_category) are now summed in
the run-level run_manifest.json too, so a snapshot's totals no longer require adding up
sixteen files.
Deltas additionally carry row_count_tier1_changed, top_tier1_transitions and
tier1_transitions_window — a planned re-score announces itself there. A delta's
row_count says how many rows moved; those keys say whether they moved between categories,
and which moves were the largest.
manifest_keys_version is new and names the KEY SET's own version, independent of
feed_schema_version. Pin it if what you parse is the manifest rather than the columns.
Every key is described in data dictionary §10
Website Categorization bulk feed data dictionary.
confidence_tier will say, and how to rebuild the old bandNothing published changes with this entry. It is the customer notice for the release above: which band each writer family can reach, which rows move, and the recipe for rebuilding the band you have today. Read it now, not on the day the snapshot lands.
Why this entry is dated where it is. The first artifact carrying any of this is the Thursday 2026-09-17 snapshot set. The exporter that produces it deploys on Thursday 2026-09-17, after that day's 10:00 UTC delta, and everything described here is frozen a week before that. This notice is therefore dated before the first 1.14.0 artifact exists — deliberately, because the band is derived rather than tracked and there is no changed-row marker that would tell a loader to look for it. If you are reading this after 2026-09-17, the change has already shipped.
Amended 2026-09-11, before any 1.14.0 artifact exists. The three brand-safety filter
families are written under five model_name values: pipeline/st_filter,
pipeline/gambling_filter, pipeline/adult_filter and the two cascade twins
cascade/st_filter and cascade/gambling_filter, whose confidence is the same
non-probability as their pipeline/ spelling. The filter rule below covers all five. A
human-feedback row — a reviewer's accepted verdict, written at a fixed confidence of
1.0 — reads policy_confidence_basis = curated, not model_raw, and keeps the ordinary
0.9 / 0.7 cut, because no rule of the policy is keyed on that writer.
confidence_tier stops being the stored 0.9 / 0.7 cut passed through verbatim and
becomes a band derived at export from the writer (model_name), the label (tier1) and
the calibrated tier (calibrated_confidence_tier). The per-family table — what band each
model_name family can reach — is in data dictionary §5
Website Categorization bulk feed data dictionary. In one sentence: rows written by
the three brand-safety filters band LOW; ensemble rows labelled Business and Finance
never band HIGH; and an ensemble row the cut put in LOW bands MEDIUM when its
calibrated tier is TOSSUP or PROBABLE.
The value domain of column 8 is unchanged — HIGH / MEDIUM / LOW, plus PARKED.
No word is added, none is removed, and a PARKED or NULL band passes through untouched.
confidence, the calibrated_* block, tier1–tier4, iab_id, model_name,
policy_group and every other column carry exactly what they would have carried under the
old cut.
All three rules move rows, and the one with the clearest consequence for a block list — a
brand-safety filter's confidence is a rule score, so those rows can no longer band HIGH
— is the smallest of the three by row count, not the largest.
| Rule | Direction |
|---|---|
The three brand-safety filters band LOW only |
Rows leave HIGH and MEDIUM. They project to the sensitive_other, gambling, harmful_content and adult policy groups. |
Ensemble Business and Finance never bands HIGH |
Rows leave HIGH; those whose calibrated_confidence_tier is DOUBTFUL also leave MEDIUM for LOW. This rule moves more rows than the first. |
An ensemble LOW row whose calibrated tier is TOSSUP or PROBABLE bands MEDIUM |
Rows move up. This rule moves the most of the three, and nothing is demoted by it. |
This is not a loss of rows, labels or groups. Every row still ships, under all three
rules, with the same tier1–tier4, the same iab_id, the same policy_group and the
same model_name. What changes is the one column that never carried the information a
blocker needs: on the first rule's rows the old HIGH meant "a keyword rule fired hard",
never "this label is 90% likely to be right". If your block list is keyed on
confidence_tier, it will lose those rows — which is why the data dictionary says, in as
many words, to key on policy_group first and to read its ranking of the other keys before
relying on them. Read your own delivery's manifest counts; the per-partition figures
behind this entry are not published here, and one partition's composition is not the feed's.
confidence (column 7) is untouched, so the band you have today is reconstructible on every
row. Evaluate it exactly as the export does, or you will disagree with us on the boundary:
-- The policy-v1 band, on any row whose confidence_tier is not PARKED and not NULL.
CASE
WHEN round(confidence::numeric, 4) >= 0.9 THEN 'HIGH'
WHEN round(confidence::numeric, 4) >= 0.7 THEN 'MEDIUM'
ELSE 'LOW'
END
Round to four decimals first — both cut points need it. The pipeline produces
confidence rounded to four decimals and the Parquet column is a 32-bit float, so the
decoded value is the nearest float32 rather than the decimal you were shown. Both boundary
values fall below their cut: 0.9 decodes as 0.899999976… and 0.7 as 0.699999988…,
so an unrounded >= 0.9 silently bands every exact-0.9 row MEDIUM and an unrounded
>= 0.7 bands every exact-0.7 row LOW. The csv.gz cell is already written at four
decimals and needs no rounding; the Parquet column does.
Two rows this recipe deliberately does not touch: a PARKED band and a NULL band are not
cut points and are passed through under both policies.
The band is derived, not tracked: a row whose band moves under the new policy is not a
changed row and does not cross a delta cutoff. The first ≥ 1.14.0 artifact you ingest must
be a snapshot — the full sixteen-partition set — or your table will carry two bands for
one row set with nothing marking which is which. manifest.json carries
confidence_band_policy so you can tell them apart: v1 (or the key's absence) is the
pass-through, v2 is the derived band. The same release also makes the language-NULL
ensemble class ship status-only, which moves the published row population. One full
per-partition replace satisfies both.
Columns 39–41 — safety_status, safety_flags, safety_detail — ship on the first 1.14.0
artifact. One property of them is worth knowing, because it decides what they are evidence
of. A domain's per-filter verdicts are stored from the 1.14.0 release onwards. Rows whose
last write predates it are derived from the row's existing model_name and tier2 and
carry a legacy version marker in safety_detail. That derivation is deliberate — without
it every pre-existing filter row would report not_assessed and a blocker keyed on the new
columns would under-block on day one — but on those rows it is a re-statement of what column
10 already told you, not new evidence. Values written from a real verdict are
distinguishable by their version marker.
Nothing published changes with this entry. It puts the transition rule for the next
MINOR release, 1.14.0, on record before the artifact that needs it exists: column 8 becomes
a derived band, the first ≥ 1.14.0 artifact you ingest must be a full sixteen-partition
snapshot, and manifest.json gains confidence_band_policy so the two policies are
distinguishable ("v1", or the key's absence, is the pass-through every artifact to date has
carried). The key can appear on a 1.13.x manifest ahead of the bump, always reading "v1";
under the extensible-manifest rule that carries no change to the artifact. Blockers should
key on policy_group and model_name, never on the band. The full notice is the entry
above.
feed_schema_version 1.13.1 → 1.13.2, a PATCH bump. Ships with the first ≥ 1.13.2
export — and, unlike 1.13.1, that includes the first daily delta after the exporter
deploy, not only the first Sunday export after it.
Every delta now carries the whole hand-vetted curated list — the curated/* rows the
real-time API answers from — and not only the exclusion that already kept a competing
model-produced row out. Until now a curated row shipped in snapshots alone, so a consumer
building its table from deltas kept the old model guess for a curated domain until the next
weekly snapshot. Those are the most-visible domains in the feed, so the divergence sat
exactly where it was least acceptable. Snapshot artifacts are unchanged.
A daily delta exported between the deploy and the Sunday snapshot carries 1.13.2 too — both kinds are stamped from the same constant — and here that delta is already complete for this change: the curated rows are not keyed on a cutoff, so unlike 1.13.1's masked rows they do cross the delta boundary and arrive with the first ≥ 1.13.2 delta. No snapshot is needed to receive them. The snapshot-ingest obligation is unchanged for every other reason: removals and the status-only baseline still travel only in a full per-partition replace.
What else changes for you:
domain, never by classified_at ordering. A curated row is
dated when the entry was curated, so it is routinely older than the model-produced
row it replaces. A loader that keeps the newest classified_at per domain will discard the
curated answer — match on domain and take the row from the most recent artifact. This was
already true of snapshots; deltas make it matter daily.curated_lane in a delta's manifest.json now reads true (it read false before),
and row_count_curated reports how many curated rows that delta carried rather than always
0. Both keys already existed; neither is given a new meaning.row_count is no longer 0 on a quiet night — it is at least the curated
count. To ask "did anything change in the source?", read row_count - row_count_curated.feed_schema_version 1.13.0 → 1.13.1, the feed's first PATCH bump: the published row
population changes, no column is added, removed, renamed or retyped, and no manifest key is
removed. Ships with the first ≥ 1.13.1 snapshot set — the first Sunday export after the
exporter deploy.
Parked-flagged brand-safety filter labels — pipeline/st_filter,
pipeline/gambling_filter, pipeline/adult_filter and pipeline/vendor_rule — now ship
status-only, matching the treatment parked-branded ensemble-family labels have had since
1.11.0. Parked-flagged rows outside both those families (cascade/st_filter, for one) are
not touched by this guard at all. Affected rows publish site_purpose_status = undefined
with NULL category columns and an empty last_known_* block; the domain's status lane is
unchanged. Until now such a row asserted a current category and "no current content" at the
same time.
Those rows sit on the category-keeping lanes — the classifier's parked flag disagreeing
with the crawl-observed lane is exactly why they still carried a category — so filtering
domain_status = 'parked' will not find them. Read your own delivery's manifest for the
count on each partition. A second population, already on the parked / dead / inactive
lanes, loses its last_known_* historical label for the same reason.
Some parked domains keep an adult or gambling verdict on purpose. A row whose label
projects to a block-critical policy group — adult, drugs, gambling, gambling_games,
harmful_content, hate, piracy, weapons — is deliberately retained rather than
suppressed, and ships exactly as it does today, until an in-group accuracy read clears the
hold. What that looks like depends on the lane: on the category-keeping lanes (active,
blocked, uncategorizable) the row keeps its category and confidence_tier = 'PARKED';
on the parked / dead / inactive lanes, where the lane rules already blank the current
category, the hold instead keeps the row's last_known_* history intact. The hold does not
override the older guards: a row one of the sibling suppression blocks already owns —
parked-stub, no-evidence or boilerplate-template — is suppressed by that guard as before,
block-critical group or not. Removing a brand-safety block is the change that can hurt you,
so it is not made on an unmeasured population.
The demoted_label_suppression manifest block gains filter_model_names (the four families
above), block_critical_held (rows the hold retained — counted there, never in
row_count_overridden), block_critical_groups (the groups actually held in that run),
block_critical_group_policy (the eight-group rule), unresolved_group_held (rows held
because their policy group could not be resolved) and policy_group_delta (per-group
before / after counts). No 1.13.0 manifest key was removed.
Same transition rule as the 1.8.0 / 1.10.0 / 1.11.0 guards: consume the first ≥ 1.13.1 set as a full per-partition replace. Rows this build newly masks do not cross the delta cutoffs, so a delta applied on a 1.13.0 base keeps categories 1.13.1 withdrew.
No column change, no manifest change, no feed_schema_version change and no published value
moves. Eight statements in the data dictionary described the pipeline as it was configured at
some earlier point rather than as it runs today. Each is now corrected against the running
configuration, and each correction is pinned by a test so it cannot drift back:
0.50, not 0.30. 0.30 is a code default that
has never been the deployed value; the real-time and bulk lanes both run the filter at
0.50.*/st_filter row is not evidence the
filter cleared the page.[0.50, 0.65) on a
video-streaming domain is discarded and the page falls through to normal classification.
Those rows are not Sensitive Topics and do not project to the sensitive_other policy
group, so both counts understate what the filter fired on.model_name families ship, on seven scales. The counts differ because the
vendor-rule flip republishes the trained classifier's own probability rather than scoring
anything.v1+teacher_bf1 rows carry calibrated_* values. The 2026-08-13 refit
(calib-map-v2) deliberately fitted that suffix in; the page still described every
re-scoring suffix as out of scope. The map's allow-list today is: no suffix, ml_en,
ml_non_en, nocontent_bf1, teacher_bf1.en|rest and __global__ rows can
reach PROBABLE; en|bf and nonen|rest are capped below it and nonen|bf well below.
Previously only the nonen|bf ceiling was described, which read as if it were the only
capped segment.domain_status = 'active' does not guarantee a category. A live site can ship with
every content column NULL — never labelled, or a suppression guard withheld the label. Test
tier1 IS NOT NULL.confidence_tier = 'PARKED' is reachable and now observed. The page said it had
occurred zero times; a small number of rows carry it, all written by rule filters
disagreeing with the crawl-observed lane. Parse for the token.Two further wording changes in the same pass. The last_known_* coverage figure was measured
on one partition and scaled into a feed-wide count; the population underneath it has grown, so
the scaled figure is withdrawn and that section now tells you to read
row_count_with_last_known_category and row_count_by_domain_status off your own delivery,
which is exact and needs no scaling. And a number of cross-references inside the dictionary
pointed at section anchors that do not exist; all of them now resolve.
One API-side note, recorded here because the names collide: content_purpose (the legacy API
field; not in the feed) is reserved and null in every current deployment — no shipped model
emits it. It is unrelated to the feed's site_purpose axis (columns 27–35) and to the
policy projection; no feed column has ever carried it.
tier1..tier4 is defined, stated one way: purpose-first (wording only, no schema or version change)No column change, no manifest change, no feed_schema_version change, and no published value
moves — this entry exists only because the definition of the stored label was written two
different ways and is now written one way.
The stored label follows the ratified single-label convention, and that convention is
purpose-first: it names the site's primary purpose — what the site is for — rather than
its topic, industry or audience, with a small set of rules settling the recurring cases: a
gambling home is Attractions > Casinos & Gambling and the harmful subset ships as
Sensitive Topics; software, SaaS and hosting vendors are Technology & Computing whoever
they sell to; a multi-brand store is Shopping while a brand's own store takes its vertical;
consumer banking, BNPL, investing and pensions are Personal Finance; a content platform
takes the content type it carries, not its delivery mechanism. The data dictionary previously
described the same convention as "about-first, plus a small set of purpose conventions", which
stated the base principle backwards; the convention itself is unchanged since v1.1
(2026-08-25).
Nothing you ingest changes because of this entry: no row is relabelled by it, no threshold
moves, and the reserved site_purpose_* columns (27–35) stay reserved — eight NULL,
site_purpose_status never NULL. If a purpose pass is ever run, it is announced here before
it ships.
feed_schema_version 1.12.0 → 1.13.0. No column changes and no change to any existing
manifest key — the version covers manifest keys too, so keys alone bump it. Partition and
run-level manifests may now also carry row_count_dead_status_by_rank_bucket,
inventory_dead_status_by_rank_bucket, inventory_never_shippable_by_rank_bucket and
coverage_shippable_by_rank_bucket. The existing coverage_by_rank_bucket counts every
inventory key in its denominator, including keys whose crawl status is in our dead set (in
practice not_found, 404 / NXDOMAIN) and that carry no category row — keys that cannot
produce a feed row until a re-crawl succeeds. The new coverage_shippable_by_rank_bucket
takes those out. Both are published; nothing you read today moves. The keys appear from the
first artifact published by a 1.13.0 image and only when the inventory scan that already feeds
inventory_total_by_rank_bucket ran (snapshots; deltas never carry the scan). A 1.13.0 delta
on a 1.12.0 snapshot is safe to ingest: the rows are identical.
policy_v1 mapping 2026.09.2: casino video games move to gambling_gamesSame columns, different published values for one IAB node. Pack policy_v1 moves from mapping
version 2026.09.1 to 2026.09.2: Video Gaming > Video Game Genres > Casino and
Gambling Video Games (simulated gambling, no real-money play) now projects to a new group,
gambling_games, instead of gambling; the three real-money nodes (casinos, lotteries,
poker) stay in gambling, and the group count is 47. Every vendor definition, regulator,
rating body and app store we checked draws the line at real money, and two groups can be
unioned by you (or merged by us later) while a merged group can never be un-merged. Block
gambling and gambling_games to keep the previous behaviour — including real-money
casinos the classifier may have stored under the video-game genre, which is a stored-label
error the pack cannot correct.
Takes effect in the feed with the first full snapshot published from an image that bundles
2026.09.2 — a pack change never rides a delta, so between that image's deploy and that
snapshot no delta is published, and the first delta after it covers the gap. From that
snapshot on, policy_pack_version on every row and policy_pack.version in the manifest read
2026.09.2. Group definitions: website-categorization-policy-pack-v1.md.
No column changes. The values on a large set of rows change, and their confidence scale
moves.
What was wrong. Until 2026-08-29 the production ensemble fed three of its five English-language members tokens produced by another member's tokenizer, so those three ran near chance and the ensemble's tier-1 accuracy sat measurably below its design. Every English-routed label written before the fix carries that deficit; the non-English route was unaffected.
What was re-scored. From 2026-09-01 the affected rows that have stored page text were re-scored with the repaired ensemble: English-routed rows plus rows with no recorded page language that nevertheless carry text. Rows with no stored text at all were not re-scored — most already ship without a category under the no-evidence guard (1.8.0). The pass covered the rows that had stored text when its corpus was assembled in late August 2026, and completed on 2026-09-03.
What you will see.
LOW. The most common move is between Technology & Computing and Business and Finance.confidence rises and the LOW share falls. A re-scored row's confidence is
therefore not comparable with its own pre-repair value, and confidence_tier mixes are not
comparable across the repair boundary.calibrated_confidence and calibrated_confidence_tier on re-scored rows still come from
the map fitted on 2026-08-13 (confidence_calibration_map_version unchanged), i.e. on the
pre-repair scale. Checked against a blind-judged cohort the map is not over-confident on the
new scale — it understates LOW rows. A refit is planned and will be announced here with a
new confidence_calibration_map_version.classified_at moves to the re-score date; model_name (ensemble-5en-3nonen) and
model_version (v1+ml_en / v1+ml_non_en) are unchanged, so the provenance columns alone
do not distinguish a re-scored row — use classified_at >= 2026-09-01.Which artifacts. The off-cycle snapshot dated 2026-09-02 carries the rows re-scored before each of its partitions was exported; the 2026-09-06 snapshot carries the complete pass; daily deltas carry re-scored rows as they land, so the delta covering the pass is substantially larger than a normal night's.
model_name families the dictionary did not yet list: pipeline/adult_filter and the new pipeline/vendor_rule flip (no schema change)No column changes. This entry documents one component that has shipped since artifact 1.10.0
and one that is new, and the data dictionary's model_name family count moves from six to
eight.
pipeline/adult_filter (artifact ≥ 1.10.0). A multilingual rule layer that overrides a
benign-looking label when adult-platform markers, explicit title terms or adult-keyword density
identify an adult site, always to Sensitive Topics > Adult & Explicit Sexual Content. Its
confidence is not a probability — a fixed 0.95 or 0.90, else a density ratio capped at
1.0, range [0.5, 1.0] — and its rows carry NULL calibrated_*. Nothing about these rows
changes with this entry; the dictionary now lists them.
pipeline/vendor_rule (rows from 2026-09-01). The trained classifier over-generalizes
software, SaaS, hosting, IT-services and telecom vendor sites into Business and Finance. A
deterministic post-classifier now rewrites such a row to tier1 = Technology & Computing
(tier2–tier4 NULL, iab_id = IAB-596) when the classifier's label sits in one of three
vendor-heavy tier-3 cells — Business I.T., Technology Industry,
Telecommunications Industry — and the page text carries high-precision vendor-identity
signals. It is one-directional (it can remove a Business and Finance label from those cells,
never add one), keeps the classifier's confidence, confidence_tier and model_version
unchanged, and ships NULL calibrated_*. Filter on model_name = 'pipeline/vendor_rule' to
see every flipped row; policy_group follows the new tier path. The rule was measured against
a reserved holdout before it was enabled.
Where it applies: the real-time API has run it since 2026-09-01 23:00 UTC. The bulk categorization lanes do not apply it yet — this page will say when they do — and the September re-categorization pass (previous entry) ran without it. Expect these rows to be absent or rare in early-September artifacts, and the vendor over-generalization to persist on bulk-labeled rows until then.
site_purpose_* block and the policy_v1 projection (schema 1.12.0)Ships with the first ≥ 1.12.0 snapshot set. Twelve new columns, appended after column 26 in one bump; no existing column changes in meaning, position or value, so a consumer reading by name or by position is unaffected. Delta files carry all 38 columns.
Columns 27–35, site_purpose_tier1 … site_purpose_model_version, are reserved for a second
axis — what a site is for, in the same IAB v3.1 vocabulary — and are NULL on every row
at 1.12.0, with one exception: site_purpose_status (column 33) is never NULL. It reads
not_available on every row that has not been assessed for a purpose, which at 1.12.0 is every
live row, and undefined on rows that have no separate business purpose — the parked /
inactive / dead lanes and rows the parked detectors flagged — derived from the same status
evidence as domain_status, never from a model. labeled appears only once a purpose pass runs
(a later release; the manifest's site_purpose_model is null until then). Reserving the
columns now means one schema bump instead of two and lets you build your table once. Read
not_available as "not assessed", not as "no purpose".
Columns 36–38 are the policy_v1 policy-pack projection: policy_group, a fixed, versioned
rollup of the 704 IAB categories onto filtering-ready groups (adult, gambling, piracy,
drugs, weapons, hate, shopping, social_networking, …) in the vocabulary DNS-filter,
secure-web-gateway and endpoint-agent policies already use; policy_basis, the axis the group
was projected from (primary at 1.12.0; site_purpose once that axis is labeled); and
policy_pack_version (policy_v1@2026.09.1, constant within an artifact, carried per row so a
CSV row is self-describing). The manifest gains policy_pack: {id, version, sha256},
row_count_with_policy_group, site_purpose_model and row_count_by_site_purpose_status.
What the projection is: a pure function of the stored label. Attractions > Casinos & Gambling,
Shopping > Lotteries and Scratchcards, Sports > Poker and Professional Gambling and the
casino video-game genre all projected to gambling at this mapping version, so a blocking
integration no longer has to know that IAB scatters gambling across four branches. What it is
not: a correction of the stored label. A site whose stored label is wrong projects to the
wrong group (an adult site stored as Entertainment > Movies reads entertainment); per-group
accuracy has not been measured and no accuracy claim attaches to any group. The pack cannot
express what IAB v3.1 has no node for — there is no malware, phishing, botnet or anonymizer
group; threat intelligence is a separate product.
The three policy columns are NULL exactly where tier1 is NULL (every suppressed row) and where
the tier path does not resolve; none of the new columns is carried into the last_known_*
block. The policy columns are derived, not tracked: a pack version change is delivered by
the next full snapshot, never by a delta, because it touches every row. The first ≥ 1.12.0
artifact you ingest must therefore be a snapshot. Pin policy_pack.version in manifest.json
(or the per-row policy_pack_version). Group definitions and the open mapping decisions:
website-categorization-policy-pack-v1.md.
Ships with the first ≥ 1.11.0 snapshot set. Ensemble-family rows whose label the write side
branded parked-demoted — since 2026-08-22 the pipeline stamps is_parked on the category row
when its classify-time placeholder detector fires, and the crawl's own parked verdict on the
record being classified carries the same flag — now ship status-only: category columns NULL,
no last_known_* block, domain_status unchanged. The manifest gains a
demoted_label_suppression block with the count.
This closes the residual the 1.10.0 vetted-hash guard could not reach: the detector generalizes
across template variants while the hash list is exact-match, so a demoted row whose template is
not on the vetted list previously shipped its fabricated category — most visibly as placeholder
Technology & Computing > Computing labels. Rows the hash guards already suppress are billed to
those blocks; the suppression counts are disjoint. Consume the first ≥ 1.11.0 set as a full
per-partition replace — a delta stream cannot retro-apply the mask (same rule as the
1.8.0/1.10.0 guards). The count trends toward 0 as re-crawls under the fixed detector replace the
demoted labels' ground truth.
Ships with the first ≥ 1.10.0 snapshot set. Ensemble-family rows whose category was produced from
a known boilerplate template — the crawl captured a hosting provider's parking/coming-soon
page, a stock web-server or panel default, or another vetted template rather than owner content,
identified by content_hash membership on a vetted list — now ship status-only: category
columns NULL, no last_known_* block, domain_status unchanged. The manifest's
boilerplate_template_suppression block publishes the full hash list beside the count. At ship
time the list is 60 vetted templates, concentrated in but not limited to the former Web Hosting
tier-4 population: the crawl read the HOST's boilerplate, so the label described the hosting
provider, not the domain.
Consume the first ≥ 1.10.0 set as a full per-partition replace — a delta stream cannot
retro-apply the mask (same rule as the 1.8.0 guard). The count trends toward 0 as re-crawls land:
a re-crawl that captures real owner content changes the row's content_hash and releases it.
Context for the Web Hosting category specifically: independent of this guard, a graded audit
found the great majority of that tier-4 class to be placeholder inflation. This guard masks the
template-provable share; the residual inflation drains through re-crawls under the fixed
categorize-time detector. Until then, treat low-confidence Web Hosting leaf labels per the
calibrated-band guidance — the class runs DOUBTFUL-heavy.
A delta now includes status-only rows — inventory rows with no category row — whose inventory
moved since the cutoff, keyed on the same crawl_attempted_at cursor as the domain_status
branch. Previously these parked/placeholder rows changed silently between snapshots. Purely
additive: no column changes, snapshots unchanged, and the rows look like any other status-only
row (NULL category columns). The manifest's status_only_lane key is now part of a delta's
identity — re-publishing the same cutoff with the lane toggled is refused. The status-only
BASELINE still arrives only with a snapshot, and removals still require the full per-partition
replace — the snapshot-ingest obligation stands.
Ships with the first ≥ 1.8.0 snapshot set. Ensemble-family rows whose category was produced with
no captured page content (content_hash NULL, with the language guard conjunct) now ship
status-only: category columns NULL, no last_known_* block, domain_status unchanged. The
manifest's no_evidence_suppression block counts the suppressed rows. Consume the first ≥ 1.8.0
set as a full per-partition replace — a delta stream cannot retro-apply the mask to rows that
do not otherwise change. The count trends toward 0 as re-crawls land.
calib-map-v1 → calib-map-v2The map behind calibrated_confidence, calibrated_confidence_tier and calibration_segment
was refitted. The manifest's confidence_calibration_map_version moves from calib-map-v1 to
calib-map-v2. Raw columns are untouched. Three things change:
confidence_calibration_map_version,
treat v1 and v2 values as incomparable.model_version carries the
teacher_bf1 suffix (non-English Business-and-Finance re-judgments) previously shipped NULL
calibrated_* by design, because v1's curves were fitted on the pre-rescore confidence scale.
v2's fit sampled that population directly; those rows now publish calibrated values.
row_count_calibrated in the manifest rises accordingly.calibration_segment loses one value. NULL|rest no longer has its own curve — too few
judged rows at fit time against the 50-row per-segment floor — so rows with no recorded
language now publish __global__. No new segment values are introduced.Fit provenance is embedded in the artifact's own fitted_on field: blind-judged rows across
three stratified samples of the 2026-08-09 snapshot, design-weighted to the calibrated
population.
We are here to listen. For a quick response, please select your request type. By submitting a request, you agree to our Terms of Service and Privacy Policy.