Skip to main content

Investor statements, contract notes and trade confirmations

Set statement policy, generate a statement for a final date range, and download statements, daily contract notes and trade confirmations for your own wallets.

Overview

A statement is one wallet's record of its holdings, movements, fees and profit and loss over a period, in CSV, XLSX or PDF, which an investor app downloads instead of rebuilding the figures itself. The platform produces scheduled statements per wallet and network, at the cadence set by the organisation's policy, and you can also request one for a specific date range. The policy selects which sections appear: opening and closing positions, the portfolio at close, movements, fees, realised and period-end unrealised profit and loss per security, custody deposits and withdrawals, and corporate actions.

Listing and downloading follow the same permission as generating. A caller sees the statements of their own wallets in the request's organisation and network, and a statement for another wallet answers 404 rather than revealing that it exists. An organisation owner or a platform administrator sees every holder's statements in the request's organisation and network, so the statement they generate for another holder is listed and downloads for them as well as for the holder. A platform administrator can also read a stored statement file through the private file route.

Read or replace the statement policy

An organisation owner or a platform administrator can read the organisation's policy with GET and replace it with PUT. Other members receive 404.

curl "https://your-instance.example.com/api/v2/reporting/statements/policy" \
  -H "X-Api-Key: <your api key>"

The cadence is daily, weekly, monthly, or quarterly. The sections are a non-empty selection of positions, portfolio, movements, pnl, fees, deposits-withdrawals, and corporate-actions. The formats are a non-empty selection of csv, xlsx, and pdf. Without a saved policy, the platform uses monthly statements with all sections in CSV and PDF.

PUT replaces the complete policy. For example, this request selects weekly statements with positions and movements in CSV only.

curl -X PUT "https://your-instance.example.com/api/v2/reporting/statements/policy" \
  -H "X-Api-Key: <your api key>" \
  -H "Content-Type: application/json" \
  -d '{
    "cadence": "weekly",
    "sections": ["positions", "movements"],
    "formats": ["csv"]
  }'

Both policy routes return the policy in data.

When a scheduled statement appears

By default the platform checks once an hour for the last three closed periods at the organisation's chosen cadence, with periods counted in UTC. The schedule does not write an older period, so generate it on demand. It writes statements only after the network is final past the period's end. A scheduled statement covers a wallet that held an opening or closing balance, moved units, paid fees, made a custody deposit or withdrawal, or held a corporate action entitlement during the period. The last two count only when the policy selects their section. Other wallets are skipped.

Generate a statement for a date range

A caller can generate a statement for one of their own wallets. An organisation owner or a platform administrator can also generate one for another holder within the request's organisation and network. Other members receive 404 if holder is not one of their wallets.

curl -X POST "https://your-instance.example.com/api/v2/reporting/statements" \
  -H "X-Api-Key: <your api key>" \
  -H "Content-Type: application/json" \
  -d '{
    "holder": "0x1234567890abcdef1234567890abcdef12345678",
    "start": "2026-09-10T00:00:00.000Z",
    "end": "2026-09-20T00:00:00.000Z"
  }'

start is inclusive and end is exclusive. The start must precede the end, and the range can cover at most 366 days. Otherwise the request returns 400. The example covers 10 to 19 September 2026 in UTC. The stored period and response use an inclusive periodEnd of 2026-09-19T23:59:59.999Z.

A successful request returns 200 with the statement summary in data:

{
  "data": {
    "id": "0198f3a1-4b2c-7d5e-8f60-1a2b3c4d5e6f",
    "chainId": 11155111,
    "account": "0x1234567890abcdef1234567890abcdef12345678",
    "periodStart": "2026-09-10T00:00:00.000Z",
    "periodEnd": "2026-09-19T23:59:59.999Z",
    "openingBlock": "8123456",
    "closingBlock": "8187021",
    "reconciled": true,
    "formats": ["csv", "pdf"],
    "createdAt": "2026-09-21T08:14:03.000Z"
  },
  "links": { "self": "/api/v2/reporting/statements" }
}

Generation uses the policy's sections and formats and produces a statement even when the wallet has no activity. Repeating the same holder and range within the same organisation and network returns the existing statement. It does not regenerate it under a changed policy.

Error
HTTP status
Meaning
DALP-0754
422
StatementRangeRefused with reason range-not-final: the requested period end is not final on the network. Wait for finality before retrying.
DALP-200387
422
StatementRowCeilingExceeded: the range holds more rows than one statement can hold. data.rows is the count the range produced and data.ceiling the most a statement holds. Request a shorter range.

The error's periodEnd uses the same inclusive end as the stored statement. The refused request writes no statement.

List the caller's statements

curl "https://your-instance.example.com/api/v2/reporting/statements" \
  -H "X-Api-Key: <your api key>"

The list includes scheduled and on-demand statements. Each entry includes the statement id, the account it covers, periodStart and inclusive periodEnd, the openingBlock and closingBlock the balances were read at, the available formats, and reconciled. reconciled is true when, for every security whose balances the platform can read, the opening balance plus the movements listed equals the closing balance. A registered external token that the indexer has stopped following is left out of this check, so it does not make reconciled false. Treat a statement with reconciled: false as incomplete and raise it with the operator.

Download one edition

curl "https://your-instance.example.com/api/v2/reporting/statements/<statementId>/download?format=pdf" \
  -H "X-Api-Key: <your api key>"

format is csv, xlsx, or pdf and must be one of the statement's available formats. Requesting an edition that was not generated returns 404. The response holds a downloadUrl that stays valid for 15 minutes, the file's mimeType and contentLength, and digest, the SHA-256 of the file:

{
  "data": {
    "statementId": "0198f3a1-4b2c-7d5e-8f60-1a2b3c4d5e6f",
    "format": "pdf",
    "downloadUrl": "https://storage.example.com/statements/0198f3a1-4b2c-7d5e-8f60-1a2b3c4d5e6f.pdf?X-Amz-Expires=900&X-Amz-Signature=5f2c...",
    "expiresAt": "2026-09-21T08:29:03.000Z",
    "digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "contentLength": 48213,
    "mimeType": "application/pdf"
  },
  "links": { "self": "/api/v2/reporting/statements/0198f3a1-4b2c-7d5e-8f60-1a2b3c4d5e6f/download" }
}

Fetch the file from downloadUrl and compare its SHA-256 with digest before you store or forward it. Request a new link when one expires. A scheduled or on-demand statement's files never change once it is written. A contract note written again after a change keeps its statement id and gets new files, so a later link for that id downloads the amended note, and the earlier note is no longer available from this route. Store the digest with every file you keep.

What a statement contains

Position rows carry a closing valuation at the period's inclusive end and indexed closing block. CSV adds price, priceCurrency, valuationSource, valuationSourceId, priceTimestamp, valuationDate, marketValue, valuationBaseCurrency, baseCurrencyValue, fxSourceIds, fxValuationDate, valuationStatus, valuationFlags, and valuationReason after reconciled. PDF positions show compact price, market value, base value, and valuation columns. Market value is in the historical quote currency. Base-currency value uses the historical base currency and FX route at the same point. Market value, base-currency value and the totals are rounded half away from zero to the minor unit of their currency, such as two decimal places for EUR and none for JPY. The total adds the unrounded values before it is rounded. The request has no separate valuation-date override.

When no valid historical price exists, the row is unpriced and its price and value cells are empty. Those rows are left out of the base-currency total. The total closes the positions section. The PDF shows it as a Total row and the CSV shows it as a position-total row. Both say how many positions they left out. The PDF shows each price time to the minute and the other valuation dates as dates. The CSV carries the full timestamps. Stale token prices or FX routes remain visible with stale-price or stale-fx. When the FX route is missing, market value remains available and the row is missing-fx with an empty base-currency value.

When selected, the portfolio section states every holding at the closing block: its quantity, weighted-average cost and the cost basis currency, its valuation price with its source, market value, base-currency value, unrealised profit and loss with its currency, and its share of the base-currency total. The share is truncated to four decimal places and is calculated over the section's own total. A holding whose cost basis still counts an acquisition without a price shows no average cost, and one whose price or FX route is missing shows no share and is left out of the table total. The PDF prints the section as a portfolio table with a per-currency total and states the portfolio value in its summary. The CSV writes one portfolio row per holding, with averageCost, averageCostCurrency, and shareOfTotalPercent as its own columns.

When selected, profit and loss uses weighted-average cost. The platform skips that calculation when the policy omits the section. Trading fees are excluded from profit and loss and appear only when the Fees section is selected. A figure is marked incomplete when the cost basis or an indexed price is missing, such as a held position with no cost-basis history. A line has no unrealised figure when the period-end holding has no cost basis or no indexed price, or when the indexer has stopped following a registered external token so its closing amount is unknown: the CSV cell is empty, the PDF reads n/a, and the line is marked incomplete.

The deposits and withdrawals section lists the wallet's custody deposits and withdrawals in the period: date, asset, amount, fee, status, transaction hash, and the sending or receiving address. A withdrawal that had not reached a final state by the end of the period is listed as pending. The section appears only when custody operations are switched on.

The corporate actions section lists each corporate action that entitled the wallet in the period: the event type, record and payment dates, entitlement, amount paid, status, and the corporate action's reference. When the policy selects this section, a mint or burn that a corporate action settled carries the same reference in the movements: the CSV puts it in the reference column and the PDF labels the movement with the event.

The platform produces only the formats selected in the policy. CSV, XLSX, and PDF editions contain the same figures. XLSX uses one worksheet per selected section, from positions, portfolio, movements, fees, profit and loss, deposits and withdrawals, and corporate actions. Its cells are typed numbers, and it supports exports above 1,000 rows. The CSV keeps full addresses and transaction hashes, which the PDF shortens to fit the page. The same figures are also available as CSV from GET /api/v2/user-pnl/export. A statement with more than 120,000 rows across its sections is not generated in any format: an on-demand request for it answers 422 DALP-200387, and the refused request writes no statement.

The PDF is rendered inside the platform with no external calls, using the organisation's logo, colours and fonts from Organisation theme. The PDF footer prints the minute the file was generated, so a PDF rendered at another minute has another digest, even for the same statement and theme.

Daily contract notes

A contract note lists one holder wallet's trades on the organisation's trading venue for one trading day. A trading day runs from the first session open to the last session close of the organisation's venue instruments on that day. The hourly statement pass that follows the last session close writes a note for each holder wallet that traded that day, and each pass reads the three most recent closed trading days. A wallet with no trade that day gets no note. The note is a statement with its own layout, so it appears in the statements list and downloads through the same route as any other statement.

A note comes in CSV and PDF. The CSV has one row per trade with tradeDate, tradeId, time, instrument, side, quantity, price, value, feeType, fee, vat, netAmount, settlementStatus, transaction and amended, then one total row per value currency with the total value and the total net amount. A total row puts the currency symbol in the transaction column. Dates and times are in the instrument's time zone. The PDF is titled “Contract note” with the status “Final”.

When a trade on a note changes after the note was written, for example when it settles or is busted, and the trading day is still one of the three the pass reads, the next pass writes the note again under the same statement id with the trade's new state. A note with a busted trade reads “Amended contract note” with the status “Amended”, and its rows carry amended as yes. A rewrite whose files differ from the earlier ones emails the holder again. The statements list does not label a statement as a contract note. A note's periodStart and periodEnd are the first session open and the last session close of its trading day, while a scheduled daily statement runs from midnight to midnight UTC.

List and download trade confirmations

Each confirmation covers one final token movement on one of the caller's wallets: a mint, a burn or a transfer, including each leg of a settled venue trade. A movement appears only once its block is at or below the network's final indexed block, so a movement a chain reorganisation can still undo has no confirmation.

curl "https://your-instance.example.com/api/v2/reporting/confirmations?transactionHash=<transactionHash>" \
  -H "X-Api-Key: <your api key>"

The list returns the caller's confirmations, newest first. Each entry carries the confirmation id, the chainId, the account, the kind, which is mint, burn or transfer, the direction, which is in, out or self, the counterparty, the token, its symbol and decimals, the amount in base units, the fee lines with VAT, the blockNumber, logIndex, transactionHash and occurredAt. transactionHash narrows the list to one transaction, such as the one that settled a venue fill, which can hold several movements. To read the next page, pass the meta.nextCursor value of the previous page as page[cursor]. GET /api/v2/reporting/confirmations/<confirmationId> reads one confirmation.

curl "https://your-instance.example.com/api/v2/reporting/confirmations/<confirmationId>/artifacts?format=xlsx" \
  -H "X-Api-Key: <your api key>" \
  -o confirmation.xlsx

format is csv or xlsx, and the response is the file itself. Both carry the confirmation ID, account, chain ID, event type, direction, token, symbol, quantity, amount in base units, fee sides, types, amounts and VAT amounts, decimals, counterparty, UTC timestamp, block, transaction hash and log index. The quantity is the amount scaled by the token's decimals.

Every confirmation read is limited to the caller's own wallets in the request's organisation and network. A confirmation of any other wallet answers 404, for organisation owners and platform administrators too. In the Console, the “Confirmations” tab of Activity lists the same confirmations with “Download confirmation (CSV)” and “Download confirmation (XLSX)” in each row's menu.

Was this page helpful?