> ## Documentation Index
> Fetch the complete documentation index at: https://docs.recovea.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# The hash-chained ledger

The recipe recovea-chain-v1: fourteen fields in order, joined by U+001F, hashed with SHA-256 from a genesis of 64 zeros, and how to re-derive one receipt.

Every receipt in your organization's ledger carries a hash, and every hash is computed from the hash before it. Change one field of one row and that row's hash no longer re-derives, and neither does any hash after it. The recipe is published so you can do the recomputation yourself, with no Recovea code.

## The recipe

The recipe's id is `recovea-chain-v1`.

1. Take fourteen values, in this order:

   ```
   1. recipe_id
   2. previous_hash
   3. request_id
   4. timestamp_ms
   5. provider
   6. model
   7. route
   8. tokens_in
   9. tokens_out
   10. tokens_cached
   11. cost_micro_usd
   12. price_list_version
   13. basis
   14. mandate_id
   ```

   The first two are the recipe id and the previous row's hash; the other twelve are the receipt's fields.
2. Join them with the unit separator, U+001F, and encode the result as UTF-8.
3. Hash the bytes with SHA-256. The row's hash is the digest in lowercase hex.

The first row of a chain uses sixty-four zeros as its previous hash:

```
0000000000000000000000000000000000000000000000000000000000000000
```

## How each value is written

- Integers are base-10, with no leading zeros.
- `timestamp_ms` is Unix epoch milliseconds.
- `cost_micro_usd` is integer microdollars; 1000000 is one dollar.
- `provider` is hashed as the export writes it: `openai`, `anthropic` or `openai_compatible`.
- `basis` is `measured` or `applied`.
- An absent `mandate_id` is the empty string, so the input then ends with the separator.
- A row's own hash is never one of its inputs, and neither is `lever`.

## Re-derive one receipt

Take the first two rows of the recipe's test vector. The first row's previous hash is the genesis:

```
request_id           req_9f2c41d87ab34e60
timestamp_ms         1750000000000
provider             openai
model                gpt-4.1
route                /v1/chat/completions
tokens_in            1200
tokens_out           340
tokens_cached        0
cost_micro_usd       4180
price_list_version   rpl-2026-06-01
basis                measured
mandate_id           (absent)
row_hash             2f542e00b0403c4e95403ef7fe6ac40d24c6598e0d100ee6cc6ec8a6d18152e2
```

In a shell, with `printf '\037'` as the separator:

```bash
S=$(printf '\037')
G=0000000000000000000000000000000000000000000000000000000000000000
printf '%s' "recovea-chain-v1${S}${G}${S}req_9f2c41d87ab34e60${S}1750000000000${S}openai${S}gpt-4.1${S}/v1/chat/completions${S}1200${S}340${S}0${S}4180${S}rpl-2026-06-01${S}measured${S}" | sha256sum
```

```
2f542e00b0403c4e95403ef7fe6ac40d24c6598e0d100ee6cc6ec8a6d18152e2  -
```

The second row's previous hash is the first row's hash, and nothing else links them:

```python
import hashlib

previous = "2f542e00b0403c4e95403ef7fe6ac40d24c6598e0d100ee6cc6ec8a6d18152e2"
fields = [
    "recovea-chain-v1", previous,
    "req_b81d5c2e40f97a13", "1750000000750", "anthropic", "claude-sonnet-4",
    "/v1/messages", "2048", "512", "1024", "15750", "rpl-2026-06-01",
    "measured", "",
]
print(hashlib.sha256("\x1f".join(fields).encode("utf-8")).hexdigest())
# 131c454755e3ea86a890f82dac42d1b61a5a336ef2b228ac33ef7d49ed378281
```

Change any one of those values, `tokens_out` from `512` to `513` for example, and the second row's hash changes, and with it the previous hash of every row after it.

## Re-derive your own export

Export a period from the platform's **Ledger** page ([Prove](https://docs.recovea.ai/products/prove/)). Each row of the export carries the fourteen values under camelCase names, in the recipe's order, followed by its `rowHash`:

1. For each row, join `recipe`, `previousHash`, `requestId`, `timestampMs`, `provider`, `model`, `route`, `tokensIn`, `tokensOut`, `tokensCached`, `costMicroUsd`, `priceListVersion`, `basis` and `mandateId` with U+001F, treating a null `mandateId` as the empty string, and compare the SHA-256 hex with `rowHash`.
2. Check that each row's `previousHash` equals the `rowHash` of the row before it.
3. Check that the last row's `rowHash` equals the `chainHead` in the export's header.

A period's first row carries its stored `previousHash`, the last hash of the period before, so one month re-derives on its own; only the first period of a chain starts from the genesis.

## What the chain shows

- **That a row has not changed since it was written, and none was reordered.** An edit anywhere breaks the hashes from that row on.
- **What the row recorded, not that it is true of the world.** The token counts in a receipt are the ones your provider reported on its own response.
- **Routed traffic only.** A call that never went through the gateway has no receipt to chain.
