# 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/data-export/reference#authentication). |
| A profile in the target domain with a role that grants read access to transactions | See [Access control](/products/custody/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. |
| `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/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, currently `vault` or `gas station`. Empty for external senders; pipe-separated for multi-sender transfers. |
| 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, currently `vault` or `gas station`. Empty for external recipients. |
| 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/data-export/reference#financial-precision).

### 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`.

## 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/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/data-export/reference#error-responses).

## Next steps

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