Six calls. A tracker uses them, and so can you.
A tracker asks its sources through exactly the interface it offers a reader. There is no privileged path into a source, which is why a source can sit on another machine and nothing about the answer changes.
ON A TRACKER, AS JSON, ON THE SAME PORT AS THE PAGES
Everything the pages show,
and nothing they do not.
THE CALLS
/api/describeThe tracker, its sources, the kinds they hold, their views and the state of each one's last update/api/searchq,view,kind,sort,limit,offset/api/thing/…One thing by scheme and value: every source's words, what they mean here, whether the sources disagree, and every claim with its receipt/api/facetA property, and its counts under the current query/api/changessince, which is a mark, not a timestamp/api/markThe mark to hand back next time
A source on its own answers the same six, with
/api/fetch?id=… in place of the thing, because a source has claims and a
tracker has things. An address naming no call is answered with the list of calls and a
404, rather than with a search of everything. On a published tracker the calls are for
subscribers; what its overview and its thing pages show anyone is also at
/demo.json, open to any site, which is where the front page of this one
takes its live example from.
ONE BINARY
The same answer
in a terminal.
Every page on a tracker has a call behind it and every call has a command behind that. Nothing is computed for the web that is not computed for the command line, so a number on a page is a number you can reproduce.
$ zetlyn tracker things trackers/cve "conflict:cvss and has:kev"
cve:cve-2025-39682
cve:cve-2025-39964
cve:cve-2026-53266
$ curl -H "Authorization: Bearer zk_…" \
"localhost:8080/api/thing/cve/CVE-2025-39682"
{
"identifier": {"scheme": "cve", "value": "CVE-2025-39682"},
"properties": {
"cvss": {"by": {"zetlyn/cve-nvd": ["9.8"],
"zetlyn/cve-redhat": ["7"]},
"conflict": true},
"exploited": {"by": {"zetlyn/cve-kev": ["yes"]},
"conflict": false},
// … and every other property, with what it means here
},
"claims": [
{"source": "zetlyn/cve-kev", "kind": "vulnerability",
"known": "2026-09-18"},
{"source": "zetlyn/cve-redhat", "known": "2025-09-05",
"url": "https://access.redhat.com/security/cve/CVE-2025-39682"},
// … NVD's, with its receipt
]
}
Two counts,
and the answer says which.
Without a filter the total is claims, summed over the sources. With one it is
things, counted after the things were assembled, because a question like
exploited=yes and severity>=high is answered by no source alone. A filtered
query reads each selecting source twenty thousand candidates deep; past that the count is
a floor and at_least says so.
A WATCH
Told what changed,
four ways.
DELIVERY
page/changes, the tracker's inbox by day, with what is new since your last visitfeedAtom: every change, one thing's, a question's, a watch'swebhookJSON, for a systemcommandThe report on standard input, for everything else
Mail is the command. Zetlyn holds no SMTP credentials and ships no mail
client: name mail, msmtp or whatever already knows how to reach
you, and a thing that goes wrong there goes wrong in one place you already understand.
What a tracker notices is a numbered signal: a new thing, a source speaking about one for the first time, a value changed, a disagreement appearing or ending, a source's health. A watch keeps the number of the last one it was told about — numbers only grow, a rebuild included — and the mark only advances after every delivery succeeded.
ZETLYN