Appearance
Receipts & Reconciliation
Availability:
GET /v1/executions/{reference}/receiptis implemented and deployed. A receipt is issued when the payout reachescompleted; until then the endpoint returns409 receipt_not_issued. Partner traffic is still gated on a separate operational step, so a partner execution does not reachcompletedyet.GET /v1/reconciliationis still coming soon. Sandbox host rollout (api.ezys.sh) is in progress.The body below is frozen as
ezys-receipt-1. One field —payout.beneficiary— is specified but not yet returned; see Payout block.
Machine-readable proof of what an execution actually did — the FX conversion that happened (offramp block), the fiat delivery that happened (payout block), and the fees charged — plus an aggregate reconciliation view for joining many executions against your own ledger.
Payout partners each produce different receipt formats; Ezys normalizes them into the single schema below, so consumers never handle provider-specific shapes.
Get receipt
GET /v1/executions/{reference}/receiptReturns the normalized receipt for one completed execution. {reference} resolution follows the same rules as the Status API (refType — clientReference (default) or executionId). Track progress on the Status API until the receipt is issued.
| Status | Code | When |
|---|---|---|
404 | receipt_not_found | Unknown reference, or one belonging to another tenant |
409 | receipt_not_issued | The reference is valid but the execution has not reached completed — including one that terminally failed before completion. A partial body is never returned in its place |
500 | receipt_corrupt | The stored body failed to read back as a JSON object. Loud by design: a malformed receipt is never silently reshaped |
One execution carries exactly one receipt. This version covers a single payout leg; a reference that resolves to multiple legs (split fill) is rejected explicitly rather than answered with one of them.
Read endpoints are authenticated with an API key (
X-API-Key) — see Authentication.
Response
json
{
"_schemaVersion": "ezys-receipt-1",
"clientReference": "NR-2026-000123",
"provider": "hopnow",
"offramp": {
"fromAsset": "USDC",
"toAsset": "USD",
"fromAmountMinor": "10003250000",
"toAmountMinor": "1000004",
"fxRate": "0.99968",
"roundingRule": "floor",
"occurredAt": "2026-07-15T02:10:11.902Z"
},
"payout": {
"amountMinor": "1000004",
"currency": "USD",
"exponent": 2,
"bankRef": "FEDWIRE-20260715-00123",
"occurredAt": "2026-07-15T02:14:02.000Z"
},
"fees": [
{ "type": "protocol_fee", "amountMinor": "1500000", "currency": "USDC", "exponent": 6, "collection": "deducted" },
{ "type": "payout_fee", "amountMinor": "2500", "currency": "USD", "exponent": 2, "collection": "deducted" },
{ "type": "network_fee", "amountMinor": "0", "currency": "USDC", "exponent": 6, "collection": "absorbed" }
],
"issuedAt": "2026-07-15T02:14:05.113Z",
"txHash": "0x9c1d8f2e…",
"finality": {
"txHash": "0x9c1d8f2e…",
"contractAddress": "0x7b2f…c41a",
"chainId": 43113
}
}| Field | Type | Description |
|---|---|---|
_schemaVersion | string | Body-schema version — currently ezys-receipt-1. This is the freeze unit: it changes only when the field set changes, so branch your parser on it rather than on field presence |
clientReference | string | Your reference key, echoed verbatim — the join key against your ledger |
provider | string | Payout provider that executed the delivery (normalized identifier) |
offramp | object | null | The FX conversion that actually happened, or null on a pure-fiat rail where no conversion took place. See Offramp block |
payout | object | The fiat delivery that actually happened. See Payout block |
fees | array | Fees actually charged (or absorbed), itemized and kept separate from principal. An empty array means "explicitly no fees". See Fees |
issuedAt | string | When this normalized receipt was issued |
txHash | string | Anchoring transaction hash. Omitted until the anchor is confirmed |
finality | object | Ledger anchor — txHash, contractAddress, chainId. Present under the same condition as txHash. See Anchoring |
txHash and finality are response metadata, not body: the content hash that the anchor commits to is computed over the body without them, so the body is byte-identical before and after anchoring.
Offramp block
null when the execution had no FX conversion — a pure-fiat rail delivers without converting.
| Field | Type | Description |
|---|---|---|
fromAsset / toAsset | string | Conversion pair (e.g. USDC → USD) |
fromAmountMinor / toAmountMinor | string | Amounts in minor units, integer strings — see Money & Amounts |
fxRate | string | Executed conversion rate |
roundingRule | string | Rounding applied — floor, per the Money & Amounts contract (residuals are ledgered, never silently lost) |
occurredAt | string | Execution time of the conversion |
Payout block
| Field | Type | Description |
|---|---|---|
amountMinor | string | Amount delivered to the beneficiary, minor units |
currency | string | Delivery currency (ISO 4217) |
exponent | number | Decimal places of currency — apply it to read amountMinor |
bankRef | string | null | Rail-level reference of the credit (e.g. wire reference). null when the provider returns none |
occurredAt | string | Confirmation time of the delivery |
beneficiary is specified but not yet returned
A masked recipient block — nameMasked, accountLast4, accountHash; raw beneficiary data is never returned on this surface — is part of the intended contract but is not present in ezys-receipt-1. It arrives in a later body version, and _schemaVersion will change when it does. Do not code against it yet, and do not treat its absence as an error.
The accountHash will be a keyed, stable hash of the account alone: the same beneficiary account always yields the same hash, so consumers can group receipts by beneficiary without ever holding the raw account number. The key and its rotation handling are fixed with the implementation.
Fees
Each fees[] entry is one fee item:
| Field | Type | Description |
|---|---|---|
type | string | Closed set: protocol_fee — the Ezys network fee for executing the intent; payout_fee — the payout rail / provider charge; network_fee — on-chain gas for the settlement legs |
amountMinor | string | Amount in minor units of this item's currency — items may be denominated in different currencies, so sum only per currency |
currency | string | Currency / ticker of this item. Not always ISO 4217 — network_fee is denominated in the input asset, so a token ticker such as USDC is a valid value |
exponent | number | Decimal places of this item's currency — apply it to read amountMinor |
collection | string | Closed set of two: deducted — taken in-flow from the transferred asset (transaction-fee style); absorbed — fronted by Ezys and reported for transparency, not charged |
Two invariants:
- Fees are never baked into the principal amounts in the
offramp/payoutblocks — the R-11 fee-separation guarantee. - FX spread is not a fee item.
offramp.fxRateis the all-in executed rate; the spread is a component of that rate, not a separately charged fee. (A mid-rate reference for rate verification belongs to the quote surface, not the receipt.)
The protocol_fee is collected transaction-fee style — deducted from the input asset at settlement (collection: "deducted") — consistent with the network's decentralized execution model.
Anchoring
Each receipt's content hash is written to an on-chain anchor. The hash is computed over the receipt body excluding txHash and finality, so the body never has to reference the transaction that records it, and it stays byte-identical before and after the anchor confirms.
To verify a receipt independently: take the body as returned, drop txHash and finality, serialize it canonically (object keys sorted recursively; array order preserved), hash it, and compare against the anchor that finality points to.
Array order in fees is part of the hashed input — it is preserved exactly as issued.
Reconciliation
GET /v1/reconciliation?from={date}&to={date}Aggregate view over a date range for ledger reconciliation: one row per execution (keyed by clientReference) with amounts, fees, and terminal status, plus range totals. Pagination and a fixed daily-cutoff variant (/v1/reports/reconciliation/daily?date=) are part of the final spec, aligned with the daily evidence-export milestone.
Reconciliation rows are derived from the same store as receipts.
Notes
- All amounts are minor-unit integer strings with floor rounding — the full contract is Money & Amounts.
- Receipts are immutable once issued, and there is exactly one per execution — this version has no revision, correction, or credit-note mechanism. The execution's own history stays on the Status API event stream; the receipt is neither edited nor reissued.
- Each receipt is anchored on-chain —
txHashpoints to that transaction. What the chain records is the receipt's content hash, not the body itself, so no recipient data ever reaches the chain. Recomputing the hash from the returned body verifies the receipt without trusting the API response alone — see Anchoring. - Retention target for receipt/event backfill is 90 days (subject to agreement).
