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

# Statements

> Assemble a customer statement from transaction and payment reads, and correct it after a period closes.

**There is no statement endpoint.** Stableyard records movements; a statement is a document you assemble, date and own, because its period, boundary, presentation currency and rounding are your decisions.

These reads authenticate with your app credentials, so a statement is assembled in your backend. A customer's browser cannot build one.

## Build it from four reads

| Read | What it gives a statement |
| - | - |
| `GET /v2/accounts/{accountId}/transactions` | The lines. One row per financial leg, paged newest first |
| `GET /v2/accounts/{accountId}/transactions/{transactionId}` | Ledger entries with a `createdAt`, and the frozen payout detail |
| `GET /v2/payments` | Your own `externalReference`, and the dated fields to bound a period on |
| `GET /v2/payments/{paymentId}` | The frozen fee snapshot behind a line |

`GET /v2/accounts/{accountId}/balances` is not a closing balance. It reports `custodyScope: "not_a_custody_balance"` and can stay positive after value has left, so use it to sanity-check your totals, never as a figure on the document. See [Transactions](/payments/transactions#balances-are-reporting-not-custody).

## Give each line five things

Each transaction row already carries them:

* **Direction** from that account's point of view, read from `perspective.direction`.
* **Amount and asset**, as an atomic string plus `asset.decimals`.
* **What produced it**, from `sourceType` and `sourceId`, with `paymentId` when it belongs to a Payment.
* **Which leg it is**, from `financialLegKind`, so collection, settlement and fee do not collapse into one figure.
* **Status**, so a `pending` line is not presented as settled.

Fees arrive as their own rows with `sourceType: "fee"`. Keep them as separate lines: a finance team needs gross, fee and net to tie out independently.

## Print only stable fields

| Print | Where it comes from |
| - | - |
| Line identifier | Transaction `id` |
| Direction, amount, asset | `perspective.direction`, `amount.amount`, `amount.asset` |
| Reference the customer recognises | Payment `externalReference` |
| Payout recipient and rate | `offrampDetails.receiver` and `offrampDetails.exchangeRate` |
| Local-currency figure on a payout | `offrampDetails.fiatAmount` |
| On-chain evidence | `sourceTransfer.txHash`, `settlement.txHash` |

`offrampDetails` is frozen at quote time and already redacts bank account numbers to their last four characters, which makes it safe to put in front of a customer. Fields the provider did not return are `null`, so render an absence rather than an empty string. A `txHash` can be `null` while a transfer is unconfirmed: print the line without it rather than holding the statement.

Fee amounts come from the Payment's `fees` snapshot. Print them only when `fees.pricingStatus` is `quoted`; under `quote_pending` the amounts are still `null`, and `fees` itself can be `null` where the terms are not yours to read. A genuine zero comes back as an explicit `0`, and **an absent fee is not a free movement**. Reconcile against the snapshot, not your current rates.

## Do the arithmetic in integers

Amounts are exact atomic strings. Do the arithmetic in integers, scale by `asset.decimals` once, and format at the edge; a statement that totals in floating point will disagree with itself.

Totals are per chain and asset. There is no cross-chain total in the API, and producing one means choosing rates, which is your policy. The only rate Stableyard freezes is on a payout, in `offrampDetails.exchangeRate`; it values that one movement, so do not reuse it for other lines.

## Bound a period from your own store

The transaction list has no `from` or `to`, ordering is newest first, and the cursor is opaque. You cannot ask for March.

<Steps>
  <Step title="Capture continuously, do not query retrospectively">
    Read transactions on a schedule, and again when a webhook prompts you, and write each row into your own store keyed on its `id`. Periods then close against your store.
  </Step>

  <Step title="Date each line from dated evidence, not from when you read it">
    Use the Payment's `createdAt`, `acceptedAt` or `succeededAt`, a Refund's `createdAt`, `broadcastAt` or `confirmedAt`, or the `createdAt` on a ledger entry from the transaction detail read.
  </Step>

  <Step title="Pick one date per line kind and keep it">
    A statement whose lines are dated inconsistently cannot be reconciled against itself, and the inconsistency only shows up at a period boundary.
  </Step>

  <Step title="Record the boundary with the statement">
    Store the exact instant and timezone the period closed on. Every later question about a line near the edge is answered by that value.
  </Step>
</Steps>

A row whose status is not yet terminal at the boundary belongs on the statement as pending, or not at all. Choose one rule and apply it to every period.

## Never rewrite a closed statement

A transaction can become `reversed`, a settled deposit can become `reversed`, and a delivered payout can be returned downstream. Issue each correction as its own line in the open period, referencing the original line and the statement it came from.

| What happened | What is true | What to do |
| - | - | - |
| A transaction or deposit is `reversed` | Compensating entries were written. This is an accounting correction, not an on-chain clawback, and the funds have usually already moved | Reverse the credit you gave your customer. Do not tell them money is coming back |
| A settlement is returned downstream | The Payment keeps its truthful financial status and is marked as returned. There is no automatic replacement payout and no automatic customer credit | Post a correction and decide, deliberately, whether to pay again |
| A refund confirms late | The Refund carries its own `confirmedAt` | Date it by `confirmedAt` and put it in the period it confirmed in |
| Late evidence arrives on a terminal payment | A terminal financial result does not reopen. Late evidence changes `operationalState`, not `status` | Leave the line alone and handle it operationally |

`statusVersion` on a Payment tells you whether your stored copy is stale. `operationalState` tells you a movement stopped for review without its financial result changing, which is exactly where a statement would otherwise drift. A webhook is the prompt to re-read, never the change itself.

<Card title="Next: Reconciliation" icon="scale-balanced" href="/payments/reconciliation">
  Match Stableyard records to your own.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.