# GET /v1/merkle/:artifactId
Source: https://docs.treeship.dev/api/merkle-proof

> 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.

```json
{
  "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:

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

```bash
treeship merkle verify proof.json
```

`merkle proof` emits the proof and `merkle verify` checks it; there is no
single-step `--verify` flag. Pipe one into the other:

```bash
treeship merkle proof art_f7e6d5c4b3a2f7e6 > proof.json
treeship merkle verify proof.json
```

## Errors

| 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

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

## `POST /v1/merkle/proof`

Publish an inclusion proof binding an artifact to a checkpoint. DPoP-authenticated
— `treeship merkle publish` calls this; you rarely call it by hand.

**Body**

| Field           | Type      | Required | Description                            |
| --------------- | --------- | -------- | -------------------------------------- |
| `artifact_id`   | `string`  | Yes      | Artifact the proof is for              |
| `checkpoint_id` | `integer` | Yes      | Checkpoint the artifact is proved into |
| `leaf_index`    | `integer` | No       | Index of the leaf in the tree          |
| `leaf_hash`     | `string`  | No       | Hash of the leaf                       |
| `proof_json`    | `string`  | Yes      | The serialized inclusion proof         |

**Response** `200` — `{ "artifact_id": "art_…", "stored": true }`

The caller must own **both** the artifact and the checkpoint. This is object-level
authorization, not a formality: because proofs are stored per
`(artifact_id, checkpoint_id)` and the highest checkpoint wins, any authenticated
dock that could write a proof it did not own could shadow another dock's proof and
make anchored artifacts read as un-anchored. {/* claims:transparency-log-client-reverified */}

| Status | Cause                                                                   |
| ------ | ----------------------------------------------------------------------- |
| `400`  | Invalid JSON, or `artifact_id` / `checkpoint_id` / `proof_json` missing |
| `401`  | Missing or invalid DPoP `Authorization` header                          |
| `403`  | The checkpoint or the artifact belongs to a different dock              |
| `404`  | Checkpoint or artifact not found                                        |