Public timestamps for your audit trail
Every day, GaaS stamps each organization's audit records into the Bitcoin blockchain through the free, public OpenTimestamps service. You can prove when your records existed, and that they have not changed since, without trusting GaaS.
What a timestamp proves
The hash chain and signatures show that no audit record was changed after it was written, as long as you trust the chain GaaS shows you. A public timestamp removes that last piece of trust. Once a day's stamp is in a Bitcoin block, nobody, GaaS included, can rewrite, add or remove a record of that day, or backdate one into it, without the proof failing. The block's time is independent evidence that the records existed no later than that moment.
| What is stamped | One digest per organization per UTC day that has audit records (how it is computed). |
| When | After the day ends, usually between 01:00 and 02:00 UTC the next day. If the calendars cannot be reached, GaaS tries again every hour for 7 days. |
| Where | Four public OpenTimestamps calendars: a.pool.opentimestamps.org, b.pool.opentimestamps.org, a.pool.eternitywall.com and ots.btc.catallaxy.com. At least two must accept a stamp. The calendars gather many stamps into one Bitcoin transaction. |
| What leaves GaaS | Only a 32-byte SHA-256 value computed from your digest and 16 random bytes, the same way the official ots stamp command does it. The calendars never see your digest, your records or your organization. |
| Whose data is in your proof | Only yours. Each organization's digest is stamped on its own. |
The digest
hash of every audit record of your organization whose created_at falls on that UTC day, one per line, sorted in ascending byte order, each line ending in a single newline (\n).
Sorting by hash rather than by time means you never parse timestamps or break ties: the same records always give the same file. And because the digest is simply the SHA-256 of that file, the official client checks the proof against the file itself. The API returns the recipe with every list of timestamps, as recipe.
Check a day yourself
1. Install the official client
pip install opentimestamps-client
This gives you the ots command. It reads and upgrades proofs without a Bitcoin node; the final check against a Bitcoin block needs one (step 5).
2. Export the day's records
The export needs an operator or admin key. Ask for the whole UTC day; end_date is inclusive.
DAY=2026-09-27
curl -s "https://api.gaas.is/v1/audit/export/stream?start_date=${DAY}T00:00:00Z&end_date=${DAY}T23:59:59.999999Z" \
-H "X-API-Key: $OPERATOR_API_KEY" > $DAY.jsonl
Keep this export. Records older than your retention period are deleted from GaaS, and a timestamp can only ever be checked against the records themselves.
3. Build the hash file
jq -r '.hash // empty' $DAY.jsonl | LC_ALL=C sort > $DAY.hashes
shasum -a 256 $DAY.hashes
No jq? The same in Python, standard library only:
python3 - "$DAY" <<'EOF'
import hashlib, json, sys
day = sys.argv[1]
hashes = sorted(r["hash"] for r in map(json.loads, open(f"{day}.jsonl")) if r.get("hash"))
data = "".join(h + "\n" for h in hashes).encode()
open(f"{day}.hashes", "wb").write(data)
print(hashlib.sha256(data).hexdigest())
EOF
The value printed must equal that day's digest in the list from step 4, and the number of lines (wc -l < $DAY.hashes) its record_count.
4. Download the proof
Any key of your organization can list and download proofs (viewer and up).
curl -s https://api.gaas.is/v1/audit/timestamps -H "X-API-Key: $VIEWER_API_KEY"
curl -s -o $DAY.hashes.ots https://api.gaas.is/v1/audit/timestamps/$DAY/ots \
-H "X-API-Key: $VIEWER_API_KEY"
ots info $DAY.hashes.ots
ots info starts with File sha256 hash: followed by the day's digest, then one branch per calendar that accepted the stamp. Until Bitcoin confirms it, each branch ends in a pending attestation:
File sha256 hash: 839275219773624b2e43907d801b406fdc7cee51599085da5043097f61940fa2
Timestamp:
append 4719bbf46102dc8ae5adb28bfe43c951
sha256
-> append 3e4e44c0cb7295fa
sha256
…
verify PendingAttestation('https://alice.btc.calendar.opentimestamps.org')
-> append 6d08014fab242085
…
5. Complete the proof and verify it
ots upgrade $DAY.hashes.ots
ots verify $DAY.hashes.ots
Before Bitcoin has confirmed the stamp, both commands say so, one line per calendar, and exit with an error. That is expected; try again later:
Calendar https://alice.btc.calendar.opentimestamps.org: Pending confirmation in Bitcoin blockchain
Calendar https://bob.btc.calendar.opentimestamps.org: Pending confirmation in Bitcoin blockchain
…
Failed! Timestamp not complete
- Once confirmed,
ots upgradewrites the path to the Bitcoin block into the proof (keeping the old file as$DAY.hashes.ots.bak) and reportsSuccess! Timestamp complete. An upgraded proof no longer needs any calendar. Keep it with your export. ots verifychecks the block itself, so it needs a Bitcoin Core node: a local one (a pruned node is fine) or one given withots --bitcoin-node URL verify …. Without a node,ots --no-bitcoin verify $DAY.hashes.otsprints the block height and the Merkle root to compare in any block explorer.- It checks the file first.
ots verifyfinds$DAY.hashesbecause the proof is named$DAY.hashes.ots; for another name, useots verify -f $DAY.hashes proof.ots. If a single record differs from what was stamped, it stops withFile does not match original!
How long it takes
- GaaS stamps a day between about 01:00 and 02:00 UTC the next day.
stamped_atin the list says exactly when. - The calendars then put the stamp into Bitcoin. In the OpenTimestamps client's words: "It takes a few hours for the timestamp to get confirmed by the Bitcoin blockchain; we're not doing one transaction per timestamp."
- So run
ots upgradea few hours or more afterstamped_at. If it still says pending, try again later; the calendars keep the stamp.
API
| Endpoint | Role | Returns |
|---|---|---|
GET /v1/audit/timestamps | viewer and up | Your organization's stamped days, newest first (limit 1–366, default 90), and the recipe. |
GET /v1/audit/timestamps/{day}/ots | viewer and up | That day's proof as application/octet-stream, saved as {day}.hashes.ots, with the digest in the X-GaaS-Digest header. A day without a stamp, or another organization's day, is 404. |
{
"timestamps": [
{
"day": "2026-09-27",
"digest": "839275219773624b2e43907d801b406fdc7cee51599085da5043097f61940fa2",
"record_count": 3,
"first_record_id": "aud_…",
"last_record_id": "aud_…",
"calendars": ["https://a.pool.opentimestamps.org", "https://b.pool.opentimestamps.org",
"https://a.pool.eternitywall.com", "https://ots.btc.catallaxy.com"],
"stamped_at": "2026-09-28T01:05:12.000000+00:00",
"proof_url": "/v1/audit/timestamps/2026-09-27/ots"
}
],
"total": 1,
"recipe": { "version": 1, "digest": "SHA-256 of a text file listing …", … }
}
calendars lists the calendars that accepted the stamp. first_record_id and last_record_id are the day's first and last records by time.
Good to know
- Days without audit records are not stamped: there is nothing to prove.
- A stamp covers the records stored when it was made.
record_count,first_record_idandlast_record_idlet you confirm you rebuilt the same set. - GaaS keeps the proof as the calendars returned it and does not upgrade it for you. Run
ots upgradeand keep the upgraded file. - Deleting your account deletes your timestamps along with your records.
Sources
The OpenTimestamps details on this page were checked on 2026-09-28 against:
- OpenTimestamps (the service and its public calendars)
- opentimestamps-client 0.7.2 README (installation, verifying needs a Bitcoin Core node and a pruned one is fine, "a few hours" to confirm, the
upgradeandverifyoutput) - opentimestamps-client
otsclient/cmds.py(the four default calendars, the random 16-byte nonce, at least two calendars, the--no-bitcoinmessage) andotsclient/args.py - python-opentimestamps 0.4.5
opentimestamps/calendar.pyandopentimestamps/core/(the calendar request and the proof format GaaS writes)
Related Pages
- Compliance — Hash chain, signatures and Governance Proof Tokens
- Advanced Features — Co-signing records with your own key
- The Record Is Free Forever — Audit creation, export and verification are never paywalled
- Authentication — API keys and roles