API

# Receipts and verification

Every settled request gets an Ed25519 signed receipt with its token counts, provider, price version, markup and charge. Anyone can check it against the gateway's public key and the published prices.

**On this page**

- [Fields](https://inference.dagon.so/docs/receipts#fields)
- [Fetch and list](https://inference.dagon.so/docs/receipts#fetch-and-list)
- [Verify with the gateway](https://inference.dagon.so/docs/receipts#verify-with-the-gateway)
- [Verify it yourself](https://inference.dagon.so/docs/receipts#verify-it-yourself)

## Fields

A receipt is `{"payload": {...}, "signature": "<hex>"}`. The signature covers the payload as canonical JSON (keys sorted, no whitespace).

| Field | Meaning |
| --- | --- |
| `v` | Receipt version. `2` names the provider; older `1` receipts were all served by abliteration.ai. |
| `id` | Receipt id (`rcpt_…`). |
| `gateway` | Id of the signing key; equals `gateway_id` at `/v1/identity`. |
| `key_id` | The key charged (its id, not the secret). |
| `created` | When it was signed. |
| `model` | The model id you asked for. |
| `provider` | The provider that served the request. |
| `endpoint` | The endpoint called. |
| `usage` | `input_tokens`, `cached_input_tokens`, `output_tokens`. |
| `usage_source` | `upstream` (reported by the provider), `estimated` or `reserved` (no final report arrived). |
| `price_version` | The price schedule the charge was computed from. |
| `markup_bps` | Markup in basis points (1000). |
| `charged_micros` | What the key paid, in micro-USD. |
| `held_micros` | Only when the price arithmetic exceeded the hold: the charge was capped at this amount. |
| `output_sha256` | SHA-256 of the output. The text itself is never stored. |
| `upstream_request_id` | The provider's request id, or null. |

## Fetch and list

| Request | Result |
| --- | --- |
| `GET /v1/receipts/:id` | One receipt of this key. Another key's receipt is `404 receipt_not_found`. |
| `GET /v1/receipts?limit=&before=` | This key's receipts, newest first: `{data, next_before}`. `limit` up to 200 (default 50); pass `next_before` as `before` for the next page. |
| `GET /v1/receipts?scope=account&key_id=` | A root key or passkey session: the whole account, every child included, with `keys` for a filter. `limit` up to 100. |

Non-streaming responses also carry the receipt in the `x-gateway-receipt` header (base64url JSON).

## Verify with the gateway

POST `/v1/receipts/verify`

Public, no key. Send the receipt as the body. The answer is `{"valid": true}` or `{"valid": false, "reason": "..."}`. It checks the signature, that the gateway signed it, and that `charged_micros` equals the price arithmetic of the price version it names (or, with `held_micros`, that the arithmetic exceeded the hold and the charge equals it). Every price version ever active is kept, so old receipts still verify.

curl

```curl
curl https://inference.dagon.so/v1/receipts/$RECEIPT_ID -H "Authorization: Bearer $DAGON_KEY" > receipt.json
curl https://inference.dagon.so/v1/receipts/verify -H "Content-Type: application/json" -d @receipt.json
```

## Verify it yourself

`GET /v1/identity` returns `gateway_id`, `algorithm` (`ed25519`), `public_key` (32 bytes, hex), `price_version` and `markup_bps`. Check the signature locally:

**TypeScript**

```ts
import { createPublicKey, verify } from "node:crypto";

// Canonical JSON: object keys sorted, no whitespace, undefined fields dropped.
const canonical = (v) =>
  v === null || typeof v !== "object" ? JSON.stringify(v)
  : Array.isArray(v) ? `[${v.map(canonical).join(",")}]`
  : `{${Object.keys(v).filter((k) => v[k] !== undefined).sort().map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`;

const { public_key, gateway_id } = await (await fetch("https://inference.dagon.so/v1/identity")).json();
const key = createPublicKey({
  key: Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), Buffer.from(public_key, "hex")]),
  format: "der", type: "spki",
});
const ok = receipt.payload.gateway === gateway_id &&
  verify(null, Buffer.from(canonical(receipt.payload)), key, Buffer.from(receipt.signature, "hex"));
```

**Python**

```python
# pip install pynacl requests
import json, requests
from nacl.signing import VerifyKey

ident = requests.get("https://inference.dagon.so/v1/identity").json()
canonical = json.dumps(receipt["payload"], sort_keys=True, separators=(",", ":"), ensure_ascii=False)
VerifyKey(bytes.fromhex(ident["public_key"])).verify(canonical.encode(), bytes.fromhex(receipt["signature"]))
assert receipt["payload"]["gateway"] == ident["gateway_id"]
```

What a receipt proves

A receipt proves what the gateway charged for the usage it saw. It does not prove that the provider ran the model honestly, and `model` is the id you asked for, not proof of the weights that ran.

Last updated 2026-11-07

[Previous **Streaming**](https://inference.dagon.so/docs/streaming) [Next **Endpoint reference**](https://inference.dagon.so/docs/endpoints)
