# Movement Report

Executive summary
**The Movement Report is a transaction history for a date range. Each row reports one transfer, including amounts, fees, on-chain details, and counterparties.**

- Send a `POST` request to `/v1/exports/movement` with your target domain and a date range of up to 30 days.
- By default, the report includes only settled transactions. Filter by vault, ticker, account, and status.
- The service returns 39 fixed columns in CSV or JSON, plus a value control total.


Use the Movement Report to reconcile activity for specific audit periods, remediate reconciliation breaks, and satisfy tax reporting requirements. Each row represents one transfer. Transaction-level fields repeat across the rows that belong to the same transaction.

## Prerequisites

To generate a Movement Report, you need the following:

| Prerequisite | Additional information |
|  --- | --- |
| A valid bearer token (JWT) | See [Authentication](/products/custody/v1.42/data-export/reference#authentication). |
| A profile in the target domain with a role that grants read access to transactions | See [Access control](/products/custody/v1.42/data-export/reference#access-control). |
| The target domain's ID | The domain you want to report on. |


## Endpoint

```
POST /v1/exports/movement
```

The service generates a transaction history for the target domain within the date range you specify.

## Request fields

Send the request body as `application/json`.

| Field | Type | Required | Default | Description |
|  --- | --- | --- | --- | --- |
| `domainId` | string (UUID) | Yes | — | The target domain. You must have read access to transactions in this domain. |
| `dateRange.start` | string (ISO 8601) | Yes | — | The start of the date range. Can't be in the future. |
| `dateRange.end` | string (ISO 8601) | Yes | — | The end of the date range. Can't be before `start`. The range can't exceed 30 days. |
| `includeChildDomains` | boolean | No | `false` | Include transfers from descendant domains. The service access-checks each child individually. |
| `vaultIds` | string[] (UUID) | No | — | Include only the listed vaults. Maximum 1,000 entries. |
| `tickerIds` | string[] (UUID) | No | — | Include only the listed assets. Maximum 1,000 entries. The report includes every asset in scope — including locked or unvetted assets — so use this filter to restrict the output to the assets you need. |
| `accountIds` | string[] (UUID) | No | — | Include only the listed accounts. Maximum 1,000 entries. |
| `custodyStatus` | string[] | No | `["Completed"]` | Filter by custody status. See [Status filtering](#status-filtering). |
| `ledgerStatus` | string[] | No | `["Confirmed"]` | Filter by ledger status. See [Status filtering](#status-filtering). |
| `format` | `"CSV"` | `"JSON"` | Yes | — | The output format. |


### Status filtering

By default, the Movement Report includes only **settled** transactions—those with a custody status of `Completed` and a ledger status of `Confirmed`. This default is designed for reconciliation, where you want only finalized activity.

To include other statuses, pass the full set of values you want:

| Field | Accepted values |
|  --- | --- |
| `custodyStatus` | `Broadcasting`, `Completed`, `Failed`, `Interrupted`, `Pending`, `Prepared`, `Preparing`, `Reserved` |
| `ledgerStatus` | `Detected`, `Confirmed`, `Expired`, `Replaced` |


To retrieve every status, pass all values explicitly. Omit a field to keep its settled-only default. Sending an empty array (`[]`) returns a `400` error—each status filter must contain at least one value when provided.

## Example request

```bash
curl -X POST "https://{host}/v1/exports/movement" \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "domainId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "dateRange": {
      "start": "2026-06-01T00:00:00Z",
      "end": "2026-06-15T23:59:59Z"
    },
    "includeChildDomains": false,
    "custodyStatus": ["Completed"],
    "ledgerStatus": ["Confirmed"],
    "format": "CSV"
  }'
```

The response is returned as a single file with a `Content-Disposition` attachment header and an `X-Export-Id` header for tracing. For the response structure of each format, see [Output formats](/products/custody/v1.42/data-export/reference#output-formats).

## Output columns

Each row represents one transfer. Column order is a fixed contract. The report-level `domainId` and `domainName` come from your request. The per-transfer `senderDomainId` and `recipientDomainId` identify the domains of the counterparties.

| # | Column | Type | Nullable | Description |
|  --- | --- | --- | --- | --- |
| 1 | `domainId` | string (UUID) | No | The report-level domain from your request. |
| 2 | `domainName` | string | No | The report-level domain's display name. |
| 3 | `registeredAt` | string (ISO 8601) | No | When the service registered the transfer. |
| 4 | `transactionId` | string (UUID) | No | The transaction identifier. Repeated for all transfers in the same transaction. |
| 5 | `onLedgerTransactionId` | string | Yes | The on-chain transaction hash. Empty until the transaction is confirmed. |
| 6 | `transferId` | string (UUID) | No | The transfer identifier, unique per row. |
| 7 | `transferKind` | string | No | The transfer type. One of `Transfer`, `Fee`, or `Recovery`. |
| 8 | `enrichedTransactionValue` | string (decimal) | No | The human-readable decimal value, such as `1.5` XRP. |
| 9 | `rawTransactionValue` | string | No | The raw ledger-native value, such as `1500000` drops. No decimal conversion. |
| 10 | `ledgerName` | string | No | The blockchain network name as configured by the operator, such as `XRPL Testnet August 2024` or `Ethereum Mainnet`. |
| 11 | `tickerId` | string (UUID) | No | The asset identifier. |
| 12 | `tickerName` | string | No | The full asset name. |
| 13 | `tickerSymbol` | string | Yes | The asset symbol. Empty if not set. |
| 14 | `quarantined` | boolean | No | Whether this transfer is quarantined. |
| 15 | `custodyStatus` | string | No | The custody processing status. |
| 16 | `ledgerStatus` | string | No | The ledger confirmation status. |
| 17 | `ledgerTimestamp` | string (ISO 8601) | Yes | When the service last updated the ledger status. |
| 18 | `blockTime` | string (ISO 8601) | Yes | The on-chain block time. Empty until confirmation. |
| 19 | `senderAddress` | string | Yes | The on-chain sender address. |
| 20 | `senderDomainId` | string (UUID) | Yes | The sender account's domain. Empty for external senders. |
| 21 | `senderMemoTag` | string | Yes | The XRPL source tag. Empty for non-XRPL chains. |
| 22 | `senderInternal` | boolean | No | `true` if the sender is within the platform. |
| 23 | `senderName` | string | Yes | The sender account's display name. |
| 24 | `senderId` | string (UUID) | Yes | The sender account identifier. |
| 25 | `senderAccountType` | string | Yes | The sender account type. One of `vault`, `gas station`, `external` (held at a sub-custodian provider), `omnibus`, or `deposit wallet`. Distinct from `senderVaultType`, where `external` means an off-platform counterparty. Empty for external senders; pipe-separated for multi-sender transfers. See [Omnibus flows](#omnibus-flows). |
| 26 | `senderVaultType` | string | Yes | The sender vault's backing type passed through from the core system, such as `MPC` or `HSM`, or `external` if the sender is off-platform. |
| 27 | `senderVaultId` | string (UUID) | Yes | The sender vault identifier. Empty for external senders. |
| 28 | `senderVaultName` | string | Yes | The sender vault's display name. Empty for external senders. |
| 29 | `recipientAddress` | string | Yes | The on-chain recipient address. |
| 30 | `recipientDomainId` | string (UUID) | Yes | The recipient account's domain. Empty for external recipients. |
| 31 | `recipientMemoType` | string | Yes | The memo type for Stellar transfers (`Text`, `Id`, `Hash`, `Return`). Empty for XRPL and Hedera. |
| 32 | `recipientMemoTag` | string | Yes | The XRPL destination tag, Stellar memo value, or Hedera memo string. |
| 33 | `recipientInternal` | boolean | No | `true` if the recipient is within the platform. |
| 34 | `recipientName` | string | Yes | The recipient account's display name. Empty for external recipients. |
| 35 | `recipientId` | string (UUID) | Yes | The recipient account identifier. Empty for external recipients. |
| 36 | `recipientAccountType` | string | Yes | The recipient account type. One of `vault`, `gas station`, `external` (held at a sub-custodian provider), `omnibus`, or `deposit wallet`. Distinct from `recipientVaultType`, where `external` means an off-platform counterparty. Empty for external recipients. See [Omnibus flows](#omnibus-flows). |
| 37 | `recipientVaultType` | string | Yes | The recipient vault's backing type passed through from the core system, such as `MPC` or `HSM`, or `external` if the recipient is off-platform. |
| 38 | `recipientVaultId` | string (UUID) | Yes | The recipient vault identifier. Empty for external recipients. |
| 39 | `recipientVaultName` | string | Yes | The recipient vault's display name. Empty for external recipients. |


### Value columns

The report includes both a converted and a raw value for each transfer:

- `enrichedTransactionValue` is the human-readable decimal value in the asset's native unit, such as `1.5` XRP. Use this for reconciliation and reporting.
- `rawTransactionValue` is the raw ledger-native value in the smallest denomination, such as `1500000` drops. The service doesn't convert it to decimal.


Both are strings. Always parse them as strings. See [Financial precision](/products/custody/v1.42/data-export/reference#financial-precision).

### Fee transfers

Network fees appear as their own rows with a `transferKind` of `Fee`. The report doesn't link a fee row to the transfer that incurred it — if your reconciliation needs fees attributed to specific transfers, match them downstream using the `transactionId` or `onLedgerTransactionId` columns.

### Multi-sender transfers

When a transfer has multiple senders—for example, on UTXO-model chains—the service separates all sender-side fields with a pipe (`|`) within the cell, in a consistent order across every sender field. This lets you align the sender attributes positionally.

### External counterparties

When a sender or recipient is off-platform, the service uses a consistent external convention: the counterparty's `...VaultType` is `external`, its `...DomainId` and ID fields are empty, and its `...Internal` flag is `false`.

### Omnibus flows

When the target domain has an [omnibus structure](/products/custody/v1.42/accounts-and-assets/omnibus/overview) and omnibus support is on for the environment, the sender and recipient account type columns label the omnibus wallet as `omnibus` and the deposit wallets as `deposit wallet`. A sweep reads as `deposit wallet` to `omnibus`, and a tenant withdrawal reads as `omnibus` to its destination.

These are labels only. The report contains the same rows with or without omnibus support, because custody transfer history never includes virtual movements. Transfers between virtual accounts are off-chain and don't appear in the Movement Report. For virtual account positions, use the [Omnibus Position Report](/products/custody/v1.42/data-export/omnibus-position-report).

## Control totals

The metadata header includes the following control total, a sum across every row in the report. Use it to verify that your ingested file is complete and uncorrupted. For how to verify, see [Control totals](/products/custody/v1.42/data-export/overview#control-totals).

| Field | Description |
|  --- | --- |
| `valueSum` | The sum of all `enrichedTransactionValue` values. |


## Errors

If the service returns an error, the response body is a client-safe JSON object and never exposes internal details. Common cases for the Movement Report include:

- **`400`**: A required field is missing, a UUID or timestamp is malformed, `dateRange.end` is before `start`, the range exceeds 30 days, a status array is empty, or a filter array exceeds 1,000 entries.
- **`401`**: The JWT is missing or invalid.
- **`403`**: You lack access to the target domain.
- **`413`**: The request scope exceeds a report limit — either more than 100 accessible domains, or a result that would exceed 100,000 transfers.
- **`429`**: A duplicate request within 60 seconds while a previous export is still processing, or you already have 2 exports processing.


A single Movement Report can return at most 100,000 transfers. If your scope exceeds this, the service returns a `413` error after resolving your requested scope, before generating the file. Narrow the date range or add filters, then request again.

For the full list, see [Error responses](/products/custody/v1.42/data-export/reference#error-responses).

## Next steps

| Page | Description |
|  --- | --- |
| [Position Report](/products/custody/v1.42/data-export/position-report) | Generate a point-in-time balance snapshot. |
| [Omnibus Position Report](/products/custody/v1.42/data-export/omnibus-position-report) | Generate a snapshot of an omnibus structure, including virtual account balances. |
| [Export reference](/products/custody/v1.42/data-export/reference) | Look up output formats, financial precision rules, access control, operational limits, and error responses. |
| [Data export overview](/products/custody/v1.42/data-export/overview) | Review report types, export metadata, and control totals. |