Which endpoint?
There are two sets of data. Pick the one that fits your question.
The census is one check of every website on each country’s name list (.co.nz for New Zealand, .com.au for Australia), counted once per domain. Use it to compare the two countries. The Australian scan is still running, so its figures are interim until scan_status is complete.
The NZ tracker re-checks, every hour, the New Zealand sites that showed an exact PHP version in the census. It shows change over time in New Zealand only. It is a different group of sites from the census, so don’t compare tracker figures with Australian census figures.
Errors come back as JSON with a status code: {"error": "…"}. A 400 means a parameter is wrong (the message lists the allowed values). A 404 means there is no data for that request yet.
GET /api/country?scope=nz_census|au_census
One country’s census
The full census for one country. Both scopes have the same fields, so code written for one works for the other. Fields that do not apply are null: the New Zealand census is complete, so its eta, progress and ingested_at are null.
| Parameter | Values |
scope | nz_census (fixed, complete) or au_census (updated hourly while the scan runs). Default au_census. |
Example
curl -s 'https://security.thecolab.ai/api/country?scope=nz_census'
Response (trimmed)
{
"generated": "2026-10-22T06:16:57Z",
"scope": "nz_census",
"country": "nz",
"scan_status": "complete",
"interim": false,
"crawled": 277875,
"responded": 253113,
"php_disclosing": 19122,
"patched_pct_of_disclosing": 9.1,
"exposure": {
"eol_le_7_4": 6425,
"unsupported_8_0_8_1": 2115,
"supported_8_2_plus": 10582,
"exposed_total": 8540,
"exposed_pct_of_disclosing": 44.7,
"on_latest_8_5_12": 175,
"patched_total": 1737,
"not_patched": 17385,
"not_patched_pct_of_disclosing": 90.9
},
"families": {
"eol_le_7_4": 6425,
"no_fix_8_0_8_1": 2115,
"behind_8_2_plus": 8845,
"fixed": 1737,
"pct": {
"eol_le_7_4": 33.6,
"no_fix_8_0_8_1": 11.1,
"behind_8_2_plus": 46.3,
"fixed": 9.1
}
},
"eta": null,
"progress": null,
"ingested_at": null,
/* … also: generated_utc, platform_fingerprinted, cdn_fronted_cloudflare, responded_pct_of_crawled, disclose_pct_of_responded, cloudflare_pct_of_responded, wordpress, platforms, cdn, php_versions, top_versions, provenance */
}
Australia, while the scan runs (trimmed)
curl -s 'https://security.thecolab.ai/api/country?scope=au_census'
{
"generated": "2026-10-22T15:45:01Z",
"scope": "au_census",
"country": "au",
"scan_status": "complete",
"interim": false,
"crawled": 1420115,
"responded": 1254985,
"php_disclosing": 192892,
"patched_pct_of_disclosing": 4.5,
"exposure": {
"eol_le_7_4": 70646,
"unsupported_8_0_8_1": 18141,
"supported_8_2_plus": 104105,
"exposed_total": 88787,
"exposed_pct_of_disclosing": 46,
"on_latest_8_5_12": 1072,
"patched_total": 8715,
"not_patched": 184177,
"not_patched_pct_of_disclosing": 95.5
},
"families": {
"eol_le_7_4": 70646,
"no_fix_8_0_8_1": 18141,
"behind_8_2_plus": 95390,
"fixed": 8715,
"pct": {
"eol_le_7_4": 36.6,
"no_fix_8_0_8_1": 9.4,
"behind_8_2_plus": 49.5,
"fixed": 4.5
}
},
"eta": {
"status": "complete",
"low": null,
"high": null,
"as_of": "2026-10-22T15:45:01Z",
/* … also: rate_per_hour_low, rate_per_hour_high, covers, method, completed_at */
},
"progress": {
"checked": 1420115,
"expected_live": 1435189,
"checked_pct_of_expected": 98.9,
"order": "names are checked in roughly alphabetical order, so interim figures are not a random sample",
/* … 13 more fields */
},
"ingested_at": "2026-10-22T15:46:15Z",
/* … also: generated_utc, platform_fingerprinted, cdn_fronted_cloudflare, responded_pct_of_crawled, disclose_pct_of_responded, cloudflare_pct_of_responded, wordpress, platforms, cdn, php_versions, top_versions, provenance */
}
Also in each report: generated_utc, platform_fingerprinted, cdn_fronted_cloudflare, responded_pct_of_crawled, disclose_pct_of_responded, cloudflare_pct_of_responded, wordpress (sites, disclosing, fixed), and the lists platforms, cdn, php_versions (by branch) and top_versions (exact releases, with a fixed flag). provenance says how domains were counted.
GET /api/compare
New Zealand and Australia in one call
Both census reports, in exactly the /api/country shape, plus metric_note, a sentence or two on how to read the comparison, and generated, the time this response was built. The fairest single number to compare is patched_pct_of_disclosing. While au.interim is true, say that the Australian figure is interim.
Example
curl -s 'https://security.thecolab.ai/api/compare'
Response (trimmed)
{
"nz": {
"scope": "nz_census",
"scan_status": "complete",
"interim": false,
"crawled": 277875,
"php_disclosing": 19122,
"patched_pct_of_disclosing": 9.1,
"families": {
"eol_le_7_4": 6425,
"no_fix_8_0_8_1": 2115,
"behind_8_2_plus": 8845,
"fixed": 1737,
"pct": {
"eol_le_7_4": 33.6,
"no_fix_8_0_8_1": 11.1,
"behind_8_2_plus": 46.3,
"fixed": 9.1
}
},
/* … 19 more fields */
},
"au": {
"scope": "au_census",
"scan_status": "complete",
"interim": false,
"crawled": 1420115,
"php_disclosing": 192892,
"patched_pct_of_disclosing": 4.5,
"families": {
"eol_le_7_4": 70646,
"no_fix_8_0_8_1": 18141,
"behind_8_2_plus": 95390,
"fixed": 8715,
"pct": {
"eol_le_7_4": 36.6,
"no_fix_8_0_8_1": 9.4,
"behind_8_2_plus": 49.5,
"fixed": 4.5
}
},
/* … 19 more fields */
},
"metric_note": "Like-for-like census comparison: the same check run once over each country's websites (.co.nz names for NZ, .com.au names for AU), one row per domain. Compare patched_pct_of_disclosing: the share of sites showing an exact PHP version that were on a release with the PHP2Shell fix when checked. The name lists differ by country, and the scans ran at different times. Both scans are complete. The hourly NZ tracker is a different sample and is not part of this comparison.",
"generated": "2026-10-22T17:48:32Z"
}
In a web page
const r = await fetch("https://security.thecolab.ai/api/compare").then((r) => r.json());
console.log(`NZ ${r.nz.patched_pct_of_disclosing}%, AU ${r.au.patched_pct_of_disclosing}%${r.au.interim ? " (interim)" : ""}`);
GET /api/latest?scope=census|baseline|tracked
One New Zealand snapshot
A full New Zealand snapshot: counts, the CDN, platform and PHP version lists, and an exposure summary. scope=census gives the newest full census; scope=tracked gives the newest hourly tracker re-check; scope=baseline gives the tracker’s starting point. Without scope you get the newest snapshot of any kind, so set it. Use ?id= with an id from /api/snapshots for one exact snapshot. Census snapshots carry no scope field; tracker snapshots also have tracked_domains.
| Parameter | Values |
scope | census, baseline, tracked, or a comma list of these |
id | A snapshot id (overrides scope) |
Example
curl -s 'https://security.thecolab.ai/api/latest?scope=census'
Response (trimmed)
{
"generated": "2026-10-22T03:26:03Z",
"crawled": 277875,
"responded": 253113,
"platform_fingerprinted": 113184,
"php_disclosing": 19122,
"cdn_fronted_cloudflare": 81016,
"cdn": [
{
"name": "Cloudflare",
"count": 81016,
"pct_of_responded": 32
},
{
"name": "nginx (origin)",
"count": 51117,
"pct_of_responded": 20.2
},
/* … 16 more */
],
"platforms": [
{
"name": "WordPress",
"count": 54133,
"pct_of_fingerprinted": 47.8
},
{
"name": "Squarespace",
"count": 17885,
"pct_of_fingerprinted": 15.8
},
/* … 10 more */
],
"php_versions": [
{
"version": "4.4",
"count": 5,
"pct_of_disclosing": 0,
"status": "eol"
},
{
"version": "5.1",
"count": 1,
"pct_of_disclosing": 0,
"status": "eol"
},
/* … 16 more */
],
"exposure": {
"eol_le_7_4": 6425,
"unsupported_8_0_8_1": 2115,
"supported_8_2_plus": 10582,
"exposed_total": 8540,
"exposed_pct_of_disclosing": 44.7,
"on_latest_8_5_12": 175,
"patched_total": 1737,
"not_patched": 17385,
"not_patched_pct_of_disclosing": 90.9
}
}
GET /api/history?metric=…&scope=…
One number over time (New Zealand)
One value per snapshot, oldest first. For the hourly tracker line use scope=baseline,tracked. Each point also gives php_disclosing, the number of sites the value is based on, which changes a little from hour to hour as some sites don’t answer.
| Parameter | Values |
metric | patched_pct, not_patched_pct, patched_total, not_patched, exposure_pct (default), exposed_total, on_latest, php_disclosing |
scope | As for /api/latest. Without it, every snapshot. |
Example
curl -s 'https://security.thecolab.ai/api/history?scope=baseline,tracked&metric=patched_pct'
Response (trimmed)
[
{
"taken_at": "2026-10-21T21:37:34Z",
"value": 8.7,
"scope": "baseline",
"php_disclosing": 18252
},
{
"taken_at": "2026-10-21T22:56:20Z",
"value": 9.9,
"scope": "tracked",
"php_disclosing": 18060
},
{
"taken_at": "2026-10-22T00:21:49Z",
"value": 11.5,
"scope": "tracked",
"php_disclosing": 17928
},
/* … 16 more */
]
GET /api/upgrades
Observed upgrades (New Zealand tracker)
How many distinct tracked sites were below the fixed release at the baseline and were later seen on a fixed release, with a per-check series in series.checks. definition gives the exact rule. A version number can’t prove a site is secure, so read these as upgrades seen, not as proof that a site is secure.
Example
curl -s 'https://security.thecolab.ai/api/upgrades'
Response (trimmed)
{
"tracking_started": true,
"baseline_at": "2026-10-21T21:37:34Z",
"tracked_sites": 18252,
"below_fix_at_baseline": 16658,
"observed_upgrades": 1277,
"still_on_fixed_release": 1276,
"last_check_at": "2026-10-22T17:21:27Z",
"definition": "Distinct websites whose PHP version was below the fixed release at the baseline and a later hourly re-check saw on a fixed release (8.2.35, 8.3.36, 8.4.27, 8.5.12, or 8.6+). A version number can’t prove a site is secure, and fixes backported by Linux distributions don’t show up.",
"series": {
"checks": [
{
"at": "2026-10-21T22:56:20Z",
"window_start": "2026-10-21T21:37:34Z",
"window_from": "baseline",
"window_minutes": 79,
"checks_without_site_data_in_window": 0,
"status": "complete",
"new_upgrades": 213,
"cumulative": 213
},
{
"at": "2026-10-22T00:21:49Z",
"window_start": "2026-10-21T22:56:20Z",
"window_from": "check",
"window_minutes": 85,
"checks_without_site_data_in_window": 0,
"status": "complete",
"new_upgrades": 272,
"cumulative": 485
},
/* … 16 more */
],
"earlier_checks_omitted": 0,
"earlier_upgrades": 0,
"unattributed_upgrades": 0,
"consistent": true
},
/* … also: baseline_label, baseline_complete, fixed_at_baseline, later_showed_older_version, version_hidden_at_last_check, first_upgrade_seen_at, latest_upgrade_seen_at */
}
GET /api/php · /api/platforms · /api/cdn
One list from a New Zealand snapshot
Just one list from a snapshot, as {snapshot_id, taken_at, rows}. They take the same scope and id parameters as /api/latest. pct is a share of a different total in each list: of sites showing a PHP version (php), of sites with an identified platform (platforms), and of sites that answered (cdn).
PHP branches
curl -s 'https://security.thecolab.ai/api/php?scope=census'
{
"snapshot_id": 7,
"taken_at": "2026-10-22T03:26:03Z",
"rows": [
{
"version": "4.4",
"count": 5,
"pct": 0,
"status": "eol"
},
{
"version": "5.1",
"count": 1,
"pct": 0,
"status": "eol"
},
{
"version": "5.2",
"count": 72,
"pct": 0.4,
"status": "eol"
},
/* … 15 more */
]
}
Platforms
curl -s 'https://security.thecolab.ai/api/platforms?scope=census'
{
"snapshot_id": 7,
"taken_at": "2026-10-22T03:26:03Z",
"rows": [
{
"name": "WordPress",
"count": 54133,
"pct": 47.8
},
{
"name": "Squarespace",
"count": 17885,
"pct": 15.8
},
{
"name": "Wix",
"count": 17868,
"pct": 15.8
},
/* … 9 more */
]
}
CDN or web server that answered
curl -s 'https://security.thecolab.ai/api/cdn?scope=census'
{
"snapshot_id": 7,
"taken_at": "2026-10-22T03:26:03Z",
"rows": [
{
"name": "Cloudflare",
"count": 81016,
"pct": 32
},
{
"name": "nginx (origin)",
"count": 51117,
"pct": 20.2
},
{
"name": "Apache (origin)",
"count": 32584,
"pct": 12.9
},
/* … 15 more */
]
}
GET /api/snapshots
Every New Zealand snapshot
All snapshots, newest first, with their id, time and scope (null for a census). Use an id with ?id= on the endpoints above.
Example
curl -s 'https://security.thecolab.ai/api/snapshots'
Response (trimmed)
[
{
"id": 21,
"taken_at": "2026-10-22T17:21:27Z",
"exposed_pct": 42.7,
"scope": "tracked"
},
{
"id": 20,
"taken_at": "2026-10-22T16:21:32Z",
"exposed_pct": 42.6,
"scope": "tracked"
},
{
"id": 19,
"taken_at": "2026-10-22T15:22:28Z",
"exposed_pct": 42.7,
"scope": "tracked"
},
/* … 18 more */
]
Field glossary
All percentages are rounded to one decimal place. Times are UTC (ending in Z). The fixed releases for PHP2Shell are 8.2.35, 8.3.36, 8.4.27 and 8.5.12, released on 21 October 2026.
crawled- Domains we checked, counted once each.
responded- Domains whose website answered.
platform_fingerprinted- Sites where we could identify the platform (WordPress, Shopify and so on).
php_disclosing- Sites whose response showed an exact PHP version. This is the total for every patch figure. Most servers don’t show their version, so all patch counts are a floor, not a total.
- Patched (
patched_total, families.fixed, patched_pct_of_disclosing) - On a release with the PHP2Shell fix: 8.2.35, 8.3.36, 8.4.27, 8.5.12 or newer on those branches, or PHP 8.6 or later.
not_patched is everything else that showed a version.
- Fix available, not applied (
behind_8_2_plus) - On PHP 8.2 or newer, but on a release older than the fix. A normal update fixes these.
- No official fix (
no_fix_8_0_8_1, unsupported_8_0_8_1) - On PHP 8.0 or 8.1. These branches get no fixed release. The PHP security team published source patches, but a host has to build and install them. The
status value unsupported means these branches.
- End-of-life (
eol_le_7_4, status eol) - On PHP 7.4 or older. No fix of any kind; the site needs a major PHP upgrade.
exposed_total, exposed_pct_of_disclosing- End-of-life plus no-official-fix: sites on PHP 8.1 or older.
on_latest_…- Sites on the newest PHP release. The key name includes that release, for example
on_latest_8_5_12.
families- The four groups above (end-of-life, no official fix, fix not applied, fixed). They add up to
php_disclosing; families.pct gives each as a percentage.
scan_status, interimscanning with interim: true while a scan is still running: the figures will change. Australian names are checked in roughly alphabetical order, so interim figures are not a random sample. complete with interim: false when it has finished.
eta- Our estimate of when a running scan will finish:
low and high are the early and late ends of a UTC window, worked out at as_of. status is estimate, estimating, paused or complete. method explains the sum. It is an estimate, not a promise. null for a finished census.
progress- How far a running scan has got:
checked names so far and checked_pct_of_expected of the live names we expect to check. null for a finished census.
generated, ingested_at- When the report was worked out, and when the API received it (
null for the New Zealand census, which ships with the API).
What these numbers can’t tell you
- A version number is what the server says about itself. Some Linux distributions fix PHP without changing the version number, so a few “not patched” sites may be fixed.
- A CDN or firewall can hide the version or block some attacks, but it isn’t a fix.
- The two countries’ name lists were built from different public sources, and the scans ran at different times. See the method notes.
Update times
| Data | Updates |
New Zealand census (nz_census, scope=census) | Fixed. Checked on 22 October 2026, 8:53 am to 12:45 pm NZ time. |
New Zealand tracker (scope=tracked, /api/history, /api/upgrades) | Every hour. The re-check starts at 17 minutes past the hour and the new snapshot appears a few minutes later. |
Australian census (au_census, /api/compare) | Every hour at 45 minutes past while the scan runs, then fixed once scan_status is complete. |
Responses carry Cache-Control: public, max-age=300. Polling more often than every few minutes gets you nothing new.
Credit and fair use
The API is free to use. Please:
- Credit “TheColab.ai — security.thecolab.ai” wherever you show the figures, with a link if you can.
- Cache responses on your side, and don’t request more often than every few minutes.
- Say when Australian figures are interim.
The data covers aggregate counts only. It does not list or name any individual website, and we won’t add that. Questions or corrections: adam@thecolab.ai.