Treeship
Hub API

Merkle Checkpoints

Retrieve and publish Merkle tree checkpoints. Checkpoints are signed snapshots of the append-only artifact tree.

Merkle checkpoints are a new addition. The endpoint is live but the response format may evolve before stabilizing.

Get a specific checkpoint

GET /v1/merkle/checkpoint/:id
ParameterTypeDescription
:idintegerThe checkpoint ID (auto-incrementing integer, e.g., 42)

No authentication required.

Get the latest checkpoint

GET /v1/merkle/checkpoint/latest

Accepts an optional hub_id query parameter to scope the result to a specific hub connection.

GET /v1/merkle/checkpoint/latest?hub_id=hub_abc123

Publish a checkpoint

POST /v1/merkle/checkpoint

Requires DPoP authentication. The CLI publishes a locally-signed checkpoint to Hub.

Request body

{
  "root": "sha256:7e3a...",
  "tree_size": 4096,
  "height": 12,
  "signed_at": "2026-03-25T10:00:00Z",
  "signer": "ship_abc123",
  "signature": "base64url-encoded-ed25519-signature",
  "public_key": "base64url-encoded-public-key",
  "rekor_index": 12345,
  "index": 3
}
FieldTypeRequiredDescription
rootstringYesSHA-256 root hash of the tree in sha256:<hex> format
tree_sizeintegerYesNumber of leaves in the Merkle tree at this checkpoint
heightintegerNoHeight of the Merkle tree
signed_atstringYesRFC 3339 timestamp of when the checkpoint was signed
signerstringYesKey ID of the signer
signaturestringYesBase64url-encoded Ed25519 signature of the canonical form
public_keystringYesBase64url-encoded public key bytes
rekor_indexinteger or nullNoRekor transparency log index, if anchored
indexintegerNoCheckpoint sequence number

Publish response

{
  "id": 42,
  "root": "sha256:7e3a...",
  "hub_received_at": "2026-03-25T10:00:01Z"
}

GET response

Both GET endpoints return the same format -- the MerkleCheckpoint object stored by Hub:

{
  "id": 42,
  "root": "sha256:7e3a...",
  "tree_size": 4096,
  "height": 12,
  "signed_at": "2026-03-25T10:00:00Z",
  "signer": "ship_abc123",
  "signature": "base64url-encoded-ed25519-signature",
  "public_key": "base64url-encoded-public-key",
  "rekor_index": 12345,
  "hub_id": "hub_abc123"
}
FieldTypeDescription
idintegerAuto-incrementing checkpoint ID
rootstringSHA-256 root hash of the tree in sha256:<hex> format
tree_sizeintegerNumber of leaves in the Merkle tree at this checkpoint
heightintegerHeight of the Merkle tree
signed_atstringRFC 3339 timestamp of when the checkpoint was signed
signerstringKey ID of the signer
signaturestringBase64url-encoded Ed25519 signature over the canonical form
public_keystringBase64url-encoded public key bytes
rekor_indexinteger or nullRekor transparency log index (null if not anchored)
hub_idstring or nullHub connection that published this checkpoint

How checkpoints work

The CLI builds a local Merkle tree from all artifacts, signs a checkpoint over the root, and publishes it to Hub via POST /v1/merkle/checkpoint. The canonical form signed is: {index}|{root}|{tree_size}|{height}|{signer}|{signed_at}.

You can verify that:

  1. A checkpoint signature is valid using the included public_key
  2. The tree root matches the claimed tree size
  3. An artifact's Merkle proof resolves against a specific checkpoint's root

Errors

StatusBodyCause
400{"error": "invalid checkpoint id"}The :id parameter is not a valid integer
400{"error": "missing required checkpoint fields"}POST request is missing root, tree_size, signed_at, signer, signature, or public_key
401DPoP errorPOST request failed DPoP authentication
404{"error": "checkpoint not found"}No checkpoint with this ID
404{"error": "no checkpoints found"}No checkpoints have been created yet

Examples

# Get a specific checkpoint by integer ID
curl https://api.treeship.dev/v1/merkle/checkpoint/42

# Get the latest checkpoint
curl https://api.treeship.dev/v1/merkle/checkpoint/latest

# Get the latest checkpoint for a specific hub connection
curl "https://api.treeship.dev/v1/merkle/checkpoint/latest?hub_id=hub_abc123"