Public, anonymous, read-only aggregate facts about the ATProto network, published with the coverage, freshness and observation adequacy that qualify them.
publicanonymous read-onlyno API key CORS enabled
Several independent instruments watch the ATProto network and reduce what they see into aggregate facts. This API publishes those facts. It is a thin publication layer: it reads artifacts the instruments have already generated and serves them under a versioned contract. It does not run queries against any instrument's operational database.
The distinguishing property is that every fact carries the limits of the observation that produced it. How much of the estate was actually watched, how fresh the reading is, and whether the observation had enough standing to support a negative claim all travel with the data rather than being lost at the boundary. If an instrument could not see something, you are told that, instead of receiving a confident zero.
curl -sS -i https://api.neutral.zone/atproto/v1/instruments
curl -sS -i https://api.neutral.zone/atproto/v1/labelwatch/overview
curl -sS -i https://api.neutral.zone/atproto/v1/weatherwatch/conditions
curl -sS -i https://api.neutral.zone/atproto/v1/weatherwatch/metrics
Inspect the HTTP status before using the JSON. No key or query parameters are needed.
The instrument index reports readability; PRESENT does not guarantee that
Labelwatch can serve a current reading. Check its freshness_state, then fetch the endpoint.
Artifact-backed fact responses carry ETag and Last-Modified.
Copy the ETag exactly, including its quotes, into If-None-Match on a later request.
A matching validator returns 304 with no body; changed age buckets can produce a
new validator even with unchanged artifact bytes. The index has no validators.
If-Modified-Since is not evaluated by this service.
curl -sS -i -H 'If-None-Match: "COPY_ETAG_FROM_RESPONSE"' \
https://api.neutral.zone/atproto/v1/labelwatch/overview
| Instrument | Observes | Published here |
|---|---|---|
| Weatherwatch | Aggregate network activity from a Jetstream vantage: event rates by collection, and the health of the observation itself. | Yes |
| Labelwatch | Observed label application and removal events in a rolling epoch, with acquisition time, source coverage and collection freshness. | Yes |
| Driftwatch | Claim propagation, per subject. | No. Its observations are per-subject and are deliberately private. See what is not published. |
| Path | Returns |
|---|---|
GET /atproto/v1/instruments |
Which instruments contribute and whether each is readable right now. |
GET /atproto/v1/labelwatch/overview |
Labeler activity in the current rolling 30-day epoch: acquisition time,
window coverage (warm-up and recorded gaps with reasons), event and
labeler counts, and the most active labelers over 7 days. Served only
while fresh or warming up; otherwise 503. |
GET /atproto/v1/weatherwatch/conditions |
Plain-language reading of current aggregate activity, with freshness and interval coverage. |
GET /atproto/v1/weatherwatch/metrics |
Per-collection event totals and mean rates, with observed and unobserved window counts. |
GET /atproto/openapi.json |
Machine-readable contract (OpenAPI 3.1). |
GET /atproto/health |
Liveness of this publication service. Says nothing about the instruments. |
GET and HEAD retrieve resources; HEAD omits the body.
OPTIONS returns 204 for CORS preflight.
POST, PUT, PATCH and DELETE return 405. There are no write, admin, or per-subject routes, and there is
nothing to authenticate as.
The three instrument fact endpoints share this outer shape. The instruments index instead contains contract, retrieved_at and instruments.
| Field | Meaning |
|---|---|
contract |
Stable identifier for the fact contract and its major version, e.g.
atproto.facts.labelwatch.overview/v1. Pin against this. |
instrument | Which instrument produced the facts. |
generated_at |
When the instrument produced the underlying artifact. This is the timestamp that matters for how current the facts are. |
retrieved_at |
When this service read that artifact to answer you. Always recent; not a measure of data freshness. |
age_seconds |
retrieved_at minus generated_at. How stale the
facts are, in one number. |
facts | The observations themselves. |
caveats |
Limits that are part of the fact, not legal boilerplate. They state what the instrument structurally cannot see. |
provenance |
How the facts were obtained, and an explicit
does_not_measure list where the instrument publishes one. |
freshness |
Present on Weatherwatch and Labelwatch responses: the freshness state
and the threshold it was judged against. Labelwatch states are
fresh, warming_up (served), and
stale, held, unavailable
(refused with 503). |
All timestamps are UTC, ISO 8601. Three different times appear and they are not interchangeable:
facts.window.start / .end; Labelwatch: facts.window_days, facts.acquisition and facts.coverage). Aggregate counts and
rates belong to this interval and to no other.Windows are preserved exactly as the instrument produced them and are never re-cut at serialization. Weatherwatch rates divide by observed duration, not nominal window width; unobserved windows are omitted from rates rather than interpolated across.
For Labelwatch, both artifact publication and accepted collection must be current.
freshness.age_seconds measures artifact age; collection_age_seconds
measures age of facts.coverage.observed_through. The default bounds are 7200 seconds,
but use the response's max_age_seconds and collection_max_age_seconds.
basis identifies the clock responsible for a refusal. A future timestamp more than
300 seconds ahead or an unusable collection timestamp is unavailable.
fresh and warming_up permit a response; neither establishes complete
coverage. Read facts.coverage.status, observed_days, warmup,
gaps and sources. No recorded gaps does not prove complete source coverage.
Rolling-epoch alert counts are null because no alert pipeline runs there, and
current_label_state is unknown. Weatherwatch freshness is copied from
its artifact; this API does not apply Labelwatch's refusal policy to Weatherwatch.
This is the part worth reading before you use the numbers.
facts.window.coverage_ratio is observed seconds divided by the span
of the window. A ratio below 1 means the instrument was not watching for part of
the interval. Counts are of what was observed, never an estimate of what
occurred.
The rolling epoch does not publish the legacy observation or network_weather fields.
facts.observation.adequacy is one of adequate,
partial, or unobserved, and it qualifies the entire
reading. It exists because a window that saw nothing cannot report that nothing
happened.
partial
or unobserved, network_weather.signals will not contain
calm. It reports under-observed or
unobserved instead, and
network_weather.supports_negative_claim is false.
Positive findings still stand — an observed spike was observed — but the absence
of a finding is not published as evidence of absence.
Practically: if supports_negative_claim is false, do not
read a quiet result as "nothing is happening". Read it as "not enough was seen to
say".
caveats and provenance.does_not_measure describe
structural blind spots. Weatherwatch, for instance, counts events and not
accounts, so participant turnover is unavailable by construction rather than
merely unimplemented.
at:// URIs of the people or records an instrument observed. No
stable identity hashes, and no groupings small enough to resolve to
individuals.Labeler DIDs are published, deliberately. A labeler is a public moderation service with a discoverable endpoint, not a person, and the instruments already publish these identifiers. That is the one identity-shaped thing here, and it is a service identity.
Publication is an allow-list: each field is selected by name. A new field appearing inside an instrument cannot reach this API without someone adding it on purpose.
Deliberately modest.
/atproto/v1, the meaning of an existing field does not
silently change./atproto/v2.contract string in each response carries the same major
version as the path. Pin to it.
/atproto/ is one namespace on a host that may carry unrelated APIs.
Nothing here claims the root, and paths outside /atproto/ are not
part of this contract.
Instruments restate facts. A reducer gets fixed, or a window is recomputed as
late data arrives. There is no separate correction feed: a re-read of the same
endpoint returns the current best reading, and
generated_at tells you which computation you have.
For two responses describing the same window, the one with the later
generated_at supersedes the earlier. ETag changes
whenever the underlying artifact changes, so a 304 means the current artifact-and-age-bucket validator matched. Freshness refusal is checked before a conditional response. Facts are not immutable and should not be
cached indefinitely as though they were.
Errors use {"error":{"status":503,"code":"source_unavailable","message":"…"}}. Labelwatch freshness refusals also include error.freshness; missing or unreadable artifacts may omit it. Preserve the error instead of substituting zero counts or a prior successful reading.
| Status | Meaning |
|---|---|
304 | Your If-None-Match matched; reuse your cached body within its stated limits. |
404 | No such path. |
405 | POST, PUT, PATCH or DELETE is unsupported; Allow lists GET, HEAD, OPTIONS. |
503 |
The instrument's artifact could not be read
(source_unavailable), or, for Labelwatch, it is older than
its freshness bound (source_stale) or the instrument is held
(source_held). Sent with Cache-Control: no-store.
This is absence of a current reading, not evidence that the instrument
observed nothing. |
500 | internal_error: the service could not build the fact. |
A missing documentation asset uses 503 asset_unavailable; it does not carry the fact source no-store policy. Unknown paths use 404 not_found.
GET https://api.neutral.zone/atproto/v1/labelwatch/overview
Illustrative fixture at a fixed retrieval time, not a live reading.
{
"contract": "atproto.facts.labelwatch.overview/v1",
"instrument": "labelwatch",
"generated_at": "2026-10-09T12:00:00.000000Z",
"retrieved_at": "2026-10-09T12:01:00Z",
"age_seconds": 60.0,
"freshness": {
"state": "warming_up",
"age_seconds": 60.0,
"basis": "artifact_generated_at",
"max_age_seconds": 7200,
"collection_age_seconds": 180.0,
"collection_max_age_seconds": 7200
},
"facts": {
"labeler_count": 97,
"alert_count_total": null,
"alert_count_window": null,
"window_days": 30,
"acquisition": {
"generation_id": "0f3c2a8e-5b7d-4c1e-9a6f-2d8b4e7c1a90",
"started_at": "2026-10-08T18:00:00.000000Z",
"basis": "local_acceptance_clock"
},
"coverage": {
"status": "unknown",
"observed_days": 0,
"observed_through": "2026-10-09T11:58:00.000000Z",
"gap_overflow": false,
"warmup": {
"start": "2026-09-09T12:00:00.000000Z",
"end": "2026-10-08T18:00:00.000000Z",
"reason": "not_observed"
},
"gaps": [
{
"start": "2026-10-09T03:00:00.000000Z",
"end": "2026-10-09T03:20:00.000000Z",
"reason": "source_unavailable"
}
],
"sources": {
"enrolled": 580,
"seeded": 24,
"discovered": 556,
"unstarted": 120,
"probing": 31,
"established": 212,
"refused": 217,
"pre_epoch_skipped": 1834201,
"undated_skipped": 12,
"late_skipped": 4410,
"cursor_order_unverified": 3,
"undated_only": 2,
"provider_rejects": 5,
"unavailable": 7,
"future_skipped": 9
}
},
"counts": {
"events_24h": 41234,
"events_7d": 41234,
"events_window": 41234,
"labelers_active_7d": 97,
"labelers_active_window": 97
},
"top_labelers_7d": [
{
"labeler_did": "did:plc:ar7c4by46qjdydhdevvrndac",
"count": 30211
},
{
"labeler_did": "did:plc:e4elbtctnfqocyfcml6h2lf7",
"count": 5120
}
],
"current_label_state": "unknown"
},
"caveats": [
"Counts cover only the current epoch's window; intervals before acquisition are not observed.",
"Counts are of Labelwatch's own observations, not of everything that happened. A labeler Labelwatch could not reach contributes nothing.",
"Recorded gaps are disclosed with a reason. No recorded gaps does not prove complete source coverage.",
"Labelwatch records observed application and removal events; current label state is unknown.",
"labeler_count counts labelers with at least one observation in the current window of this epoch.",
"Labeler DIDs identify public moderation services, not people. No subject identities are published."
],
"provenance": {
"instrument": "labelwatch",
"method": "Keeps a rolling window of label application and removal events observed from known labeler endpoints, stamped with the local acceptance clock; older observations expire.",
"artifact_schema_version": "labelwatch.recent-overview.v1"
}
}
OpenAPI 3.1 at https://api.neutral.zone/atproto/openapi.json.