Merkle consistency proofs
Publish client-computed consistency proofs and fetch the chain an auditor walks. The Hub stores and serves; it never generates or verifies.
A consistency proof shows that the Merkle tree at size to_size is an append-only extension of the tree at size from_size — nothing rewritten, nothing removed. Proofs are computed and signed client-side; the Hub stores and serves them verbatim.
POST /v1/merkle/consistency
DPoP authenticated. Body capped at 10 MB.
{
"signer": "key_9f8e7d6c",
"from_size": 4,
"from_root": "sha256:…",
"to_size": 9,
"to_root": "sha256:…",
"version": 2,
"proof_json": "[\"…hex node hashes…\"]",
"signed_at": "2026-07-16T17:00:00Z"
}200 response: { "id": 17, "hub_received_at": "2026-07-16T17:00:01Z" }
| Status | Body | Cause |
|---|---|---|
400 | {"error":"invalid JSON body"} | Malformed body |
400 | {"error":"missing required consistency fields"} | Empty signer / from_root / to_root / proof_json |
400 | {"error":"from_size must be > 0 and <= to_size"} | Size ordering violated |
401 | — | DPoP failed |
403 | {"error":"signer is not owned by this dock"} | The authenticated dock has never published a checkpoint signed by this signer |
500 | {"error":"failed to store consistency proof"} | Storage failure |
GET /v1/merkle/consistency
Public. Returns the consecutive proofs for a signer from a given tree size onward — the chain an auditor walks.
GET /v1/merkle/consistency?signer=key_9f8e7d6c&from=0| Parameter | Required | Description |
|---|---|---|
signer | Yes | Checkpoint signer key ID |
from | No | Starting tree size (default 0) |
200 response: { "signer", "from", "chain": [ … ], "note" } — each chain element:
{
"id": 17,
"signer": "key_9f8e7d6c",
"from_size": 4,
"from_root": "sha256:…",
"to_size": 9,
"to_root": "sha256:…",
"version": 2,
"proof_json": "[…]",
"signed_at": "2026-07-16T17:00:00Z",
"dock_id": "dck_…"
}Errors: 400 {"error":"missing signer"} / {"error":"invalid from size"} · 500.
The verifier walks the chain checking each proof offline and that each link's to_size/to_root matches the next link's from_size/from_root — any gap or mismatch is evidence of a rewritten log. See Merkle proofs.
POST /v1/merkle/consistency
Publish one consistency proof linking two checkpoint states. DPoP-authenticated;
treeship merkle publish calls it.
Body
| Field | Type | Description |
|---|---|---|
signer | string | Checkpoint signer key id |
from_size | integer | Tree size the proof starts from |
from_root | string | Root at from_size |
to_size | integer | Tree size the proof ends at |
to_root | string | Root at to_size |
version | integer | Proof format version |
proof_json | string | The serialized consistency proof |
signed_at | string | RFC 3339 timestamp from the signer |
Response 200 — { "id": 17, "hub_received_at": "2026-08-18T03:00:43Z" }
hub_received_at is when this hub received the proof. It is not evidence of when
the proof was made — the hub does not witness time, and signed_at is asserted by
the signer.
| Status | Cause |
|---|---|
400 | Invalid JSON or missing fields |
401 | Missing or invalid DPoP Authorization header |
500 | Storage failure |