Treeship
Hub API

GET /v1/merkle/:artifactId

Retrieve a self-contained Merkle inclusion proof for an artifact.

Request

GET /v1/merkle/:artifactId
ParameterTypeDescription
:artifactIdstringThe artifact ID (e.g., art_f7e6d5c4b3a2f7e6)

No authentication required. Proofs are public so anyone can verify.

Response

Returns a self-contained ProofFile JSON. This includes everything needed to verify that the artifact is included in a specific Merkle tree checkpoint -- no further requests to Hub required.

{
  "artifact_id": "art_f7e6d5c4b3a2f7e6",
  "artifact_summary": {
    "actor": "agent://ci",
    "action": "test suite",
    "timestamp": "2026-03-28T14:30:00Z",
    "key_id": "ship_a1b2c3d4"
  },
  "inclusion_proof": {
    "leaf_index": 2048,
    "leaf_hash": "9d2fa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0",
    "path": [
      { "direction": "Right", "hash": "1a2b3c4d..." },
      { "direction": "Left", "hash": "5e6f7a8b..." },
      { "direction": "Right", "hash": "9c0d1e2f..." }
    ],
    "algorithm": "sha256-rfc9162"
  },
  "checkpoint": {
    "index": 42,
    "root": "sha256:7e3a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0",
    "tree_size": 4096,
    "height": 12,
    "signed_at": "2026-03-28T15:00:00Z",
    "signer": "ship_a1b2c3d4",
    "public_key": "base64url-encoded-ed25519-public-key",
    "signature": "base64url-encoded-ed25519-signature",
    "algorithm": "sha256-rfc9162"
  }
}

ProofFile fields

FieldTypeDescription
artifact_idstringThe artifact this proof covers
artifact_summaryobjectHuman-readable context about the artifact
artifact_summary.actorstringActor URI that created the artifact
artifact_summary.actionstringLabel describing the action
artifact_summary.timestampstringRFC 3339 timestamp of the artifact
artifact_summary.key_idstringKey ID of the signer
inclusion_proofobjectMerkle inclusion proof from leaf to root
inclusion_proof.leaf_indexintegerPosition of the artifact in the tree
inclusion_proof.leaf_hashstringHex-encoded SHA-256 hash of the artifact ID
inclusion_proof.patharraySibling hashes forming the path from leaf to root
inclusion_proof.path[].directionstringLeft or Right -- position of the sibling relative to the current node
inclusion_proof.path[].hashstringHex-encoded hash of the sibling node
inclusion_proof.algorithmstringMerkle algorithm: sha256-rfc9162 (current) or sha256-duplicate-last (v1 legacy)
checkpointobjectSigned snapshot of the Merkle tree at proof time
checkpoint.indexintegerCheckpoint sequence number
checkpoint.rootstringRoot hash in sha256:<hex> format
checkpoint.tree_sizeintegerNumber of leaves in the tree
checkpoint.heightintegerTree height (ceil of log2 of tree_size)
checkpoint.signed_atstringRFC 3339 timestamp of the checkpoint
checkpoint.signerstringKey ID of the checkpoint signer
checkpoint.public_keystringBase64url-encoded Ed25519 public key
checkpoint.signaturestringBase64url-encoded Ed25519 signature
checkpoint.algorithmstringMerkle algorithm used to build the tree

How to verify

The proof is self-contained. To verify locally:

  1. Recompute the leaf hash: sha256(artifact_id) and compare to inclusion_proof.leaf_hash
  2. Walk the inclusion_proof.path from leaf to root, combining hashes at each level based on direction
  3. Compare the computed root against checkpoint.root
  4. Verify the checkpoint signature using the embedded public_key

CLI verification

treeship merkle verify proof.json

You can also fetch and verify in one step:

treeship merkle proof art_f7e6d5c4b3a2f7e6 --verify

Errors

StatusBodyCause
400{"error": "missing artifact id"}No artifact ID in the URL path
404{"error": "proof not found"}No proof exists for this artifact

Example

curl https://api.treeship.dev/v1/merkle/art_f7e6d5c4b3a2f7e6