Point-in-time attestation
Ask us what a network's metric was on any date — and get a proof you can check against Bitcoin without trusting us.
Messari, Dune, DefiLlama, Token Terminal hand you a number. None of them can prove they didn't change it afterwards. We can. Every night the DePIN API's published values are written to a batch file, each row is hashed, and the batch's Merkle root is committed to the Bitcoin blockchain via OpenTimestamps. The root is a one-way fingerprint: change one character of one row and every proof breaks.
A competitor cannot retrofit this. They would have to have been anchoring
since August 9. The 24 consecutive daily roots below are the capability, and each carries a
verify_url on every value so you can check the source too.
Verify us in 90 seconds (real transcript)
This is the worked example for
symbol=2Z metric=devices_total date=20260912. Every command and
output below was actually run against the live endpoints on 2026-09-13 — nothing on this
page is illustrative. Fetch the attestation:
$ curl -s "https://kairossignal.com/v1/attest?symbol=2Z&metric=devices_total&date=20260912" -H "X-API-Key: <your-key>"
{
"attested": true,
"query": {"symbol": "2Z", "metric": "devices_total", "date": "20260912"},
"value_as_published": {
"symbol": "2Z", "metric": "devices_total",
"value": "94", "unit": "devices",
"source": "doublezero_data_api",
"verify_url": "https://data.malbeclabs.com/api/dz/devices?limit=1000",
"as_of": "2026-09-12T00:24:01Z",
"row_sha256": "76c47d0a2292a1fe7ac283164babd03b943cc6057a88b0b85ed9a24b8ee0c566",
"row_tsv": "2Z\tdevices_total\t94\tdevices\tdoublezero_data_api\thttps://data.malbeclabs.com/api/dz/devices?limit=1000\t2026-09-12T00:24:01Z"
},
"batch": {
"stamp_day": "20260912", "rows": 4062807,
"merkle_root": "49daee474d17536e1ff1d20c5c2103c608f11f34120a3dd1cf417c3062050726",
"batch_sha256": "37fce7a1458a9b88dd3cb81ef12698bfa72f02197d9ac8252852196bd705036a",
"batch_tsv_url": "https://kairossignal.com/attestations/batch_20260912.tsv"
},
"merkle_inclusion_proof": {
"leaf_index": 22653, "leaf_sha256": "76c47d0a2292a1fe…",
"path": [ {"level":0,"index":22653,"sibling":"f0ea2e9af8d129e8…","position":"right"}, … 22 steps ],
"algorithm": "leaf = sha256(utf8 row); parent = sha256(left||right); an odd last node is hashed with itself"
},
"opentimestamps": {
"proof_url": "https://kairossignal.com/attestations/batch_20260912.root.ots",
"verify_command": "ots verify batch_20260912.root.ots",
"bitcoin_status": "bitcoin_confirmed",
"anchored_late": false, "anchor_utc": null,
"bitcoin_branches": [{"height": 966587, "verified": true,
"block_hash": "0000000000000000000226d5aa983ec34759419b1ff87636020bf8c30470fc90",
"block_time_utc": "2026-09-12T01:02:52Z"}],
"honesty": "SAME-DAY ANCHOR: the OpenTimestamps stamp was created on the stamp_day itself."
}
}
Three honest-miss examples, so you know the endpoint refuses to invent — each is a different kind of "no", and none of them is a made-up number:
?symbol=AKT&metric=price&date=20260912→"series AKT/price is not present in any anchored batch"(HTTP 404). We never published that series, so there is nothing to attest.?symbol=WXM&metric=circulating_supply&date=20260912→"…has rows in batches 20260809..20260911 (27 days) but none dated 20260912". The series is real; that particular day is not in it.?symbol=2Z&metric=devices_total&date=20260812→"batch 20260812 is anchored in Bitcoin but its row-level data is not retained, so no inclusion proof can be built for it". See retention below — we would rather tell you the evidence is gone than serve you a proof we cannot stand behind.
Step 1 — download the evidence (no key needed)
$ curl -s https://kairossignal.com/attestations/batch_20260912.root 49daee474d17536e1ff1d20c5c2103c608f11f34120a3dd1cf417c3062050726 $ curl -s https://kairossignal.com/attestations/batch_20260912.tsv -o batch_20260912.tsv $ wc -l batch_20260912.tsv && stat -c%s batch_20260912.tsv 4062806 batch_20260912.tsv 544254482 $ curl -s https://kairossignal.com/attestations/batch_20260912.root.ots -o batch_20260912.root.ots
.root (65 bytes) and the .root.ots (2.6 KB); the large download is
only needed to rebuild the tree yourself in steps 2 and 3.wc -l says 4,062,806 but the batch holds 4,062,807
rows: the file's last line has no trailing newline, and wc -l counts newline
characters. Count non-empty lines yourself — any verifier that "corrects" the row count by
trusting wc -l would be off by one. The attestation response's
batch.rows is the number the Merkle tree was built over.Step 2 — hash the exact row (mind the newline)
$ awk 'NR==22654{printf "%s",$0}' batch_20260912.tsv | sha256sum
76c47d0a2292a1fe7ac283164babd03b943cc6057a88b0b85ed9a24b8ee0c566 -
printf "%s",$0 (or head -c -1) matters:
sed -n 22654p | sha256sum hashes the row plus its trailing
newline and gives c5dd968e… — a different digest. The attested leaf is
sha256 of the row WITHOUT the newline, matching the engine's row_sha256. This is
the single most common way a first-time verifier concludes "the proof is wrong" when it isn't.
Note NR is 1-indexed and leaf_index is 0-indexed: line 22,654 is
leaf 22,653.Step 3 — rebuild the root from the proof (12 lines of Python)
# verify_row.py — reads the attestation + batch_20260912.tsv, trusts only sha256
import hashlib, json, urllib.request
url = "https://kairossignal.com/v1/attest?symbol=2Z&metric=devices_total&date=20260912"
att = json.load(urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "verify_row.py"})))
rows = [l.rstrip("\n") for l in open("batch_20260912.tsv") if l.strip()]
leaf = hashlib.sha256(rows[att["merkle_inclusion_proof"]["leaf_index"]].encode()).hexdigest()
h = bytes.fromhex(leaf)
for st in att["merkle_inclusion_proof"]["path"]:
s = bytes.fromhex(st["sibling"])
h = hashlib.sha256(h + s).digest() if st["position"] == "left" else hashlib.sha256(s + h).digest()
print("leaf: ", leaf)
print("root: ", h.hex())
print("merkle_root: ", att["batch"]["merkle_root"])
print("MATCH:", h.hex() == att["batch"]["merkle_root"])
$ python3 verify_row.py leaf: 76c47d0a2292a1fe7ac283164babd03b943cc6057a88b0b85ed9a24b8ee0c566 root: 49daee474d17536e1ff1d20c5c2103c608f11f34120a3dd1cf417c3062050726 merkle_root: 49daee474d17536e1ff1d20c5c2103c608f11f34120a3dd1cf417c3062050726 MATCH: True
User-Agent. Our CDN currently
rejects Python's default Python-urllib/3.x user agent with a 403 before the
request reaches us, while curl, requests and any explicit agent
string are served normally. That is our bug, not yours, and it is being removed; until then
the one-line Request(...) wrapper above is the workaround. We would rather
publish the workaround than ship you an example that fails.Step 4 — check the root against Bitcoin
$ ots verify batch_20260812.root.ots Calendar https://alice.btc.calendar.opentimestamps.org: Timestamped by transaction 490bf16704078d18f0f6d5fedc2aca2c51584314981bc41c5dba52670b1a20c3; waiting for 6 confirmations Calendar https://finney.calendar.eternitywall.com: Pending confirmation in Bitcoin blockchain Calendar https://bob.btc.calendar.opentimestamps.org: Pending confirmation in Bitcoin blockchain Calendar https://btc.calendar.catallaxy.com: Pending confirmation in Bitcoin blockchain
And for the one batch that already completed its Bitcoin confirmation (the Aug 9 batch, which was stamped same-day):
$ ots verify batch_20260809.root.ots Assuming target filename is 'batch_20260809.root' Hashing file, algorithm sha256 Got digest 406fd956b49aa59ebe4ee5d14a1ef4dd911b84362f017ba58e8e4e0beaea1955 Attestation block hash: 000000000000000000022e510406e099cec4e3ab77ed0629ec206592b8ca40cb Success! Bitcoin block 961777 attests existence as of 2026-08-09 UTC
ots normally talks to your own Bitcoin node; if you run one, drop the flag.
This server has no node, so our transcript pointed ots --bitcoin-node at a
shim that serves getblockhash/getblockheader from
blockstream.info/mempool.space public APIs — the attestation check
(attested digest == block header's merkle root) happens inside the real
ots client either way, and the block headers come from third parties, not from
Kairos.
The honesty model — read before you buy
anchored_late: true plus the anchor timestamp. From 2026-09-01 the nightly
stamping works again, and new batches are stamped the same night. We do not present a
backfilled anchor as a same-day one; that would be exactly the class of overstatement this
product exists to sell against..root files and
OpenTimestamps proofs survive, so those Merkle roots are still in Bitcoin and still mean
exactly what they meant: the batch existed, in those exact bytes, by that block. What is gone
is the row-level data the root commits to, so no inclusion proof can be rebuilt for
those days and /v1/attest returns an explicit miss saying so, naming the root and
the OTS proof that are still checkable. The 16 batches from 2026-08-29 onward are
complete. We are telling you this rather than letting you discover it: a proof you cannot
reproduce is not a proof, and the failure was ours.What a Bitcoin-confirmed root proves: the exact bytes of that day's batch existed, in that exact order, by the time the anchoring block was mined. Nobody — not us, not a attacker with our database — can alter one historical value without breaking the proof.
What it does NOT prove: that the upstream source told the truth that day.
That is what each value's verify_url is for — check the source yourself. And it
says nothing about trading performance; Kairos attests data integrity and point-in-time, not
signals.
The batch registry (measured, not declared)
Bitcoin status below is re-measured on every call by parsing the .ots files
and comparing attested digests against live block headers from public Esplora APIs — it is
not a label we set. Full data: /v1/attestations.
| stamp_day | rows | merkle root | anchor | Bitcoin status | evidence |
|---|---|---|---|---|---|
| 20260809 | 1,369 | 9cd92fde048419f2… | same-day | confirmed — block 961777 (2026-08-09) | .ots |
| 20260810 | 2,550 | 4417a5c1534ad1c7… | backfilled 09-01 | pending | .ots |
| 20260811 | 6,180 | 80477f0734fca0fd… | backfilled 09-01 | pending | .ots |
| 20260812 | 9,492 | 05a9f360a317d788… | backfilled 09-01 | pending | .ots |
| 20260813 | 12,962 | d4a66a81aac31ab2… | backfilled 09-01 | pending | .ots |
| 20260814 | 17,003 | 001cca2c03fd35be… | backfilled 09-01 | pending | .ots |
| 20260815 | 21,034 | 2e763d9560308173… | backfilled 09-01 | pending | .ots |
| 20260816 | 24,911 | 3234190fe599bd78… | backfilled 09-01 | pending | .ots |
| 20260817 | 28,949 | 5e432e34d9a48327… | backfilled 09-01 | pending | .ots |
| 20260818 | 39,114 | bfd3fa22181df17e… | backfilled 09-01 | pending | .ots |
| 20260819 | 75,193 | 3d04e19e87b48f6a… | backfilled 09-01 | pending | .ots |
| 20260820 | 104,189 | c5f215b774ba429b… | backfilled 09-01 | pending | .ots |
| 20260821 | 124,960 | 82d1f7f58ef7e474… | backfilled 09-01 | pending | .ots |
| 20260822 | 163,197 | 7d30f4e42e1d1b4b… | backfilled 09-01 | pending | .ots |
| 20260823 | 261,994 | 83facc0322113e9a… | backfilled 09-01 | pending | .ots |
| 20260824 | 365,240 | baedc9aa589c6b6d… | backfilled 09-01 | pending | .ots |
| 20260825 | 545,006 | da3033b3ceb2c4e0… | backfilled 09-01 | pending | .ots |
| 20260826 | 729,933 | 5b7b1a9b18f45fb2… | backfilled 09-01 | pending | .ots |
| 20260827 | 910,142 | 17ae8162be35ea87… | backfilled 09-01 | pending | .ots |
| 20260828 | 1,095,890 | 71b24a68ce054245… | backfilled 09-01 | pending | .ots |
| 20260829 | 1,281,974 | 567aa5dbaa114779… | backfilled 09-01 | pending | .ots |
| 20260830 | 1,467,337 | d5ef2a83a5f6997a… | backfilled 09-01 | pending | .ots |
| 20260831 | 1,652,447 | aa85b21cc1f815d6… | backfilled 09-01 | pending | .ots |
| 20260901 | 2,018,584 | 2d32689a65ecc941… | same-day | pending | .ots |
Every batch's full evidence is public, no key:
/attestations/batch_<day>.tsv (the rows),
/attestations/batch_<day>.root (the Merkle root),
/attestations/batch_<day>.root.ots (the OpenTimestamps proof). Nothing else
under that path is served.
API
GET /v1/attest?symbol=<SYM>&metric=<M>&date=<YYYYMMDD>
proof=last|all|none (default last: the freshest row that UTC day)
GET /v1/attestations (batch registry with measured Bitcoin status)
Responses are ed25519-signed
(X-Kairos-Signature/X-Kairos-Timestamp headers; key at
/.well-known/kairos-signing-key.json) so
what we served you, and when, is also provable after the fact.
Scope, stated plainly
Point-in-time data integrity for values the Kairos DePIN API published. Not a promise
about upstream accuracy (that's the verify_url on each value), not a signal, not
financial advice. Rows appear in a batch only if they were published that day; a
(symbol, metric, date) we cannot produce returns an honest miss — never a synthesised value,
never a zero-fill.