---
title: "Get an inclusion proof for a participant"
method: POST
path: "/por/proofs/{asset}/{snapshot}"
tags: ["Proof of Reserves"]
---

# Get an inclusion proof for a participant

`POST /por/proofs/{asset}/{snapshot}`

## Request

- Base URL: `https://api.cert.zerohash.com`
- URL: `https://api.cert.zerohash.com/por/proofs/{asset}/{snapshot}`
- Auth: API key in header `X-SCX-API-KEY` and API key in header `X-SCX-PASSPHRASE`

Return the Merkle inclusion proof for one participant in your platform's tree for an asset: the leaf preimage, the sibling path, and the signed root the path resolves to.

This is a POST because the participant code is enumerable and sensitive; keeping it in the body keeps it out of URLs, proxy access logs, and browser history. The response is per-participant and is not cacheable.

The tree is always your own — the platform comes from your authenticated identity. Only `participant_code` is read from the body. Send nothing else: a stray field is normally rejected with 400, and where request validation runs at the edge instead it is ignored — but a platform identifier that is not your own is denied with 403 rather than honoured.

To verify: recompute the leaf hash from `leaf`, walk `proof_path` applying each sibling on the side given by `direction` and adding sums with checked u128 arithmetic, then require the resulting hash and sum to equal the values inside the verified `commitment_cbor` of `root`. Bind the check to the expected platform, asset, and `spec_version` — a mathematically valid root for the wrong tree still fails the assertion you care about.

This document describes the wire contract, not the hash construction. The exact preimage bytes — hash function, domain separation, field encoding and ordering, the participant-slot derivation, sentinel padding, the internal-node preimage, and the full set of signed CBOR entries — are defined in the versioned Proof of Reserves verification spec issued with the reference verifier at onboarding. You need that document to implement a verifier; this one is not sufficient on its own. If the two ever disagree, treat it as a defect on our side and raise it — do not pick one and proceed.

Note that a Merkle Sum Tree proof conceals which participants the siblings represent, but not their aggregated balances: each sibling's subtree sum is disclosed.

No rate limit is applied on these routes by this API. Edge limits may still apply, so handle `429` and honour `Retry-After` rather than assuming an unlimited budget.

## Path parameters

- `asset` string, required
- `snapshot` string, required

## Headers

- `X-SCX-SIGNED` string, required
- `X-SCX-TIMESTAMP` string, required
- `X-Request-Id` string, uuid

## Request body

- PostPorProofsBody
  - `participant_code` string, required: Participant code whose inclusion proof is requested, within your own platform tree. This is the only field read. Send nothing else: a stray field is normally rejected with 400, and where request validation is handled at the edge instead it is ignored — but supplying a platform identifier that is not your own is denied with 403 rather than honoured.

## Response `200`

Inclusion proof and the signed root it resolves to

- PostPorProofsResponse
  - `leaf` PorProofLeaf, required: The four inputs to the leaf hash. They are **not** hashed in the form shown here — each is encoded first (the strings are length-prefixed, the participant code is reduced to a fixed-width slot, and the balance becomes a fixed-width integer). Use the exact byte layout given in the Proof of Reserves verification spec issued with the reference verifier at onboarding; do not infer it from this field order. Phase 1 leaves carry no per-participant salt.
    - `platform_code` string, required
    - `asset` string, required
    - `participant_code` string, required: The participant code as requested. It is not carried into the hash in the clear — it is reduced to a fixed-width slot first.
    - `balance` string, required: Balance in **base units** as a decimal string (`display value × 10^decimals`), not display units. Convert it to a 16-byte big-endian u128 before hashing.
  - `proof_path` PorProofStep[], required: Sibling path from the leaf to the root, ordered leaf-first. Never empty: the leaf level is padded to a power of two with a floor of two, so even a tree holding a single participant returns one step. The path length is therefore the log base 2 of the padded leaf count, not of the number of participants.
    - `sibling_hash` string, required: Sibling node hash, 32 bytes as lowercase hex.
    - `sibling_sum` string, required: Sum of the sibling subtree in base units, as a decimal string. Note this is decimal while `root_sum` is hex. Use checked u128 addition at every step — an overflow is a verification failure, not a wrap.
    - `direction` 'LEFT' | 'RIGHT', required: Which side the **sibling** sits on at this level. `RIGHT` means the running node is the left input.
  - `root` PorRootCommitment, required: A signed Merkle Sum Tree root for one `(platform, asset)` pair. **Only `commitment_cbor` and `signature` are authoritative.** Verify the signature over the raw `commitment_cbor` bytes, then decode those bytes and read the values from them. The JSON fields below are an unsigned convenience view and must not be trusted on their own. The two are not a one-to-one mapping: the signed payload commits to fields that have no JSON counterpart here, and `signing_key_id` is not inside the signed payload at all — it identifies which key to check the signature with, so treat it as a hint and reject the response if the signature does not verify.
    - `platform_code` string, required: Platform the tree belongs to. Always your own platform — it is taken from your authenticated identity, never from the request.
    - `asset` string, required: Chain-qualified asset identifier, used in every leaf exactly as returned here — apply no normalization of your own.
    - `spec_version` string, required: Version of the Proof of Reserves commitment schema. A change here can change the leaf preimage or the signed payload layout.
    - `publication_seq` string, required: Publication counter for this `(platform, asset)` series, rendered as a decimal **string** (not a JSON number) because it is a 64-bit integer. Monotonically increasing — a higher value supersedes a lower one for the same tree. Use it to detect a replayed or withheld root.
    - `db_snapshot_id` string, required: Content identifier of the whole normalized balance snapshot this root was built from, as lowercase hex. It is computed across every platform and asset in that snapshot, of which this tree is one subset — so trees built in the same run share one value, and it does not by itself identify this tree. See the `snapshot` path parameter for how to address a specific root.
    - `snapshot_ts` string, date-time, required: Time the balance snapshot was taken, RFC 3339 UTC. Currently emitted as the Unix epoch (`1970-01-01T00:00:00Z`) — treat it as unset and do not use it for freshness checks until it carries a real value.
    - `root_hash` string, required: Merkle root, 32 bytes as lowercase hex.
    - `root_sum` string, required: Sum of every leaf balance in the tree, as a 16-byte **big-endian u128 in lowercase hex** — the same bytes that are hashed into the tree. Note this differs from `balance` and `sibling_sum`, which are decimal. Phase 1 sums `available` balances only.
    - `commitment_cbor` string, byte, required: The exact bytes that were signed, canonical CBOR (RFC 8949 §4.2.1), base64-encoded for transport. Verify `signature` over the decoded bytes — never over a re-encoding of the JSON fields above. Decode it as a whole map rather than reading fixed offsets: it commits to more entries than are mirrored in this response, and the entry set is tied to `spec_version`. The authoritative field list for a given `spec_version` is in the Proof of Reserves verification spec issued with the reference verifier at onboarding. In the example below three signed entries are still placeholders and must not be relied on: `asset_precision` is `0`, `evidence_pkg_sha256` is 32 zero bytes, and `asset_config_version` is the empty string. **Note:** the example value below must be regenerated after the BG-5368 proto rename (`platform_id` → `platform_code` in the signed CBOR) is deployed. The current bytes encode the old key name and will not match the live service once that spec bump lands.
    - `signature` string, byte, required: Detached Ed25519 signature over the decoded `commitment_cbor` bytes, base64-encoded.
    - `signing_key_id` string, required: The key that produced `signature`. Look it up by `key_id` in `GET /por/keys` and verify against that entry's `public_key`. Do not gate on the key validity window in this release — see `GET /por/keys` for why it is not yet checkable.

## Other responses

- `400`: Bad Request
- `403`: Authorization failed. Three distinct causes share this status: - authentication rejected (missing, malformed, or expired API-key headers or signature) — the body is a plain string served as `text/html`, not JSON; - device verification could not resolve your platform, or your platform requires device identification headers that were not supplied; - a platform other than your own was named on the request. The body shape depends on which check rejected the call, so branch on the response content type rather than assuming JSON.
- `404`: No proof is available. Either Proof of Reserves is not yet enabled for your platform, no tree exists for that asset and snapshot, or the participant is not a leaf in that tree. While the feature is disabled for your platform the response is a plain-text `Not Found` with no body schema; once enabled, a genuine miss returns the JSON error below. Both cases are 404 by design — the feature is invisible until enabled.
- `500`: Internal Server Error
- `503`: Service Unavailable

## Changes

- **2026-11-03** `47cf70ae701c`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/zerohash/apis/zerohash-api/changes/por/proofs/:asset/:snapshot/post.md)

---

[API](https://skmtc.dev/zerohash/apis/zerohash-api.md) - [All operations](https://skmtc.dev/zerohash/apis/zerohash-api/llms.txt) - [OpenAPI document](https://skmtc.dev/zerohash/apis/zerohash-api/revisions/47cf70ae701c?raw)
