Hub API
GET /v1/merkle/:artifactId
Retrieve a self-contained Merkle inclusion proof for an artifact.
Request
GET /v1/merkle/:artifactId| Parameter | Type | Description |
|---|---|---|
:artifactId | string | The 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
| Field | Type | Description |
|---|---|---|
artifact_id | string | The artifact this proof covers |
artifact_summary | object | Human-readable context about the artifact |
artifact_summary.actor | string | Actor URI that created the artifact |
artifact_summary.action | string | Label describing the action |
artifact_summary.timestamp | string | RFC 3339 timestamp of the artifact |
artifact_summary.key_id | string | Key ID of the signer |
inclusion_proof | object | Merkle inclusion proof from leaf to root |
inclusion_proof.leaf_index | integer | Position of the artifact in the tree |
inclusion_proof.leaf_hash | string | Hex-encoded SHA-256 hash of the artifact ID |
inclusion_proof.path | array | Sibling hashes forming the path from leaf to root |
inclusion_proof.path[].direction | string | Left or Right -- position of the sibling relative to the current node |
inclusion_proof.path[].hash | string | Hex-encoded hash of the sibling node |
inclusion_proof.algorithm | string | Merkle algorithm: sha256-rfc9162 (current) or sha256-duplicate-last (v1 legacy) |
checkpoint | object | Signed snapshot of the Merkle tree at proof time |
checkpoint.index | integer | Checkpoint sequence number |
checkpoint.root | string | Root hash in sha256:<hex> format |
checkpoint.tree_size | integer | Number of leaves in the tree |
checkpoint.height | integer | Tree height (ceil of log2 of tree_size) |
checkpoint.signed_at | string | RFC 3339 timestamp of the checkpoint |
checkpoint.signer | string | Key ID of the checkpoint signer |
checkpoint.public_key | string | Base64url-encoded Ed25519 public key |
checkpoint.signature | string | Base64url-encoded Ed25519 signature |
checkpoint.algorithm | string | Merkle algorithm used to build the tree |
How to verify
The proof is self-contained. To verify locally:
- Recompute the leaf hash:
sha256(artifact_id)and compare toinclusion_proof.leaf_hash - Walk the
inclusion_proof.pathfrom leaf to root, combining hashes at each level based ondirection - Compare the computed root against
checkpoint.root - Verify the checkpoint signature using the embedded
public_key
CLI verification
treeship merkle verify proof.jsonYou can also fetch and verify in one step:
treeship merkle proof art_f7e6d5c4b3a2f7e6 --verifyErrors
| Status | Body | Cause |
|---|---|---|
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