# Export reference

Executive summary
**This page defines the behaviors shared by every export: authentication, access control, output formats, financial precision, operational limits, and error responses.**

- Requests use bearer-token authentication, and the service authorizes them against the target domain only.
- Reports are returned as CSV or JSON, with all financial values as strings.
- Per-request limits protect the service and keep exports predictable.


## Authentication

Every request must include a valid JSON Web Token (JWT) in the `Authorization` header:

```
Authorization: Bearer <jwt>
```

The service uses a **user-based** authentication model. There's no separate machine credential flow: you must register even bot and service accounts as users with the appropriate roles in each domain they need to access. This ensures every export traces back to a specific, verifiable identity.

The token subject identifies the requesting user in `providerId:loginId` form. The service records this identity as the request author in every export's metadata.

## Access control

Authorization is **target-domain-only**. To generate a report for a domain, all of the following must be true:

1. You have a profile in the exact target domain.
2. Your user account is unlocked.
3. The target domain is unlocked.
4. One of your roles grants read access to the relevant entity type in that domain.


The entity type depends on the report:

| Report | Required read access |
|  --- | --- |
| Position Report | `accounts` |
| Omnibus Position Report | `accounts` |
| Movement Report | `transactions` |


The service verifies domain access *before* running any data query. If you lack access, it contacts neither the balance source nor the database.

### No inheritance from parent domains

A profile in a parent domain does **not** grant access to its children. You must hold a profile with a matching role in each domain you want to export. This prevents broad, unintended access through high-level parent profiles.

Export access can differ from UI access
The export service's access check can be stricter than the access you appear to have in the Ripple Custody UI. Even where the UI shows you a domain's data, the export service requires a profile with a matching role in that exact domain. If an export returns `403` or omits domains you expect, check your per-domain profiles rather than what the UI displays.

### Including child domains

When you set `includeChildDomains` to `true`, the service resolves the full domain tree and checks each child domain independently, using the same target-domain-only rule. It includes children you can access and silently omits children you can't access. You receive a valid report containing only the domains you're entitled to, with no error about the domains it excluded.

## Common behaviors

### Synchronous delivery

Each request returns the complete report file in one synchronous response. The service does not paginate results. To keep reports within the [operational limits](#operational-limits), narrow the scope with filters or a shorter date range and split large exports into several requests.

### Request method and content type

All export endpoints accept `POST` requests with a `Content-Type` of `application/json`.

### Response headers

| Header | Description |
|  --- | --- |
| `Content-Type` | `text/csv; charset=utf-8` for CSV, or `application/json` for JSON. |
| `Content-Disposition` | `attachment` with a generated filename based on the report type, domain, and range. |
| `X-Export-Id` | The UUID of the export, for support tracing and audit. |


### Duplicate request protection

If you submit the same parameters again within 60 seconds while a previous export is still processing, the service returns `429`. This prevents an accidental double-submission from triggering redundant work. Duplicate detection is scoped to the report type, so a Position Report and an Omnibus Position Report with identical parameters don't collide.

### Concurrency limit

You can have at most **2** exports processing at any time. A third concurrent request returns `429`. Wait for an in-flight export to finish, then retry.

## Omnibus support

Omnibus support in the export service is enabled automatically for SaaS environments that use an omnibus structure, and stays off everywhere else. It controls the whole omnibus package:

| Behavior | Omnibus support off | Omnibus support on |
|  --- | --- | --- |
| Position and Movement Reports, domain without an omnibus structure | Unchanged | Unchanged |
| Position Report, omnibus domain | Virtual account balances appear as rows typed `vault` with empty name and vault fields, and inflate the control totals | Omnibus wallet and deposit wallets typed `omnibus` and `deposit wallet`, virtual account rows excluded, totals cover on-chain custody only |
| Movement Report, omnibus domain | Omnibus accounts typed `vault` | Omnibus accounts typed `omnibus` and `deposit wallet`, same rows |
| Omnibus Position Report | `404` | Report generated, or `422` when the domain has no omnibus structure |


When omnibus support is on, the service calls the Omnibus service while it builds a report for an omnibus domain. If that service is unavailable, the affected request fails with an error rather than emitting misclassified rows. When omnibus support is off, the service makes no omnibus calls.

For SaaS environments with an omnibus structure, omnibus support is enabled automatically from version 1.42 — no activation request is needed; it stays off for SaaS environments without omnibus. For on-premises deployments, the export service reads the `OMNIBUS_API_ENABLED` environment variable, set through the `export.omnibus.enabled` Helm value with a fallback to `global.omnibus.enabled`. Any value other than `true` disables it. The Omnibus Position Report also requires an Omnibus service version that provides the bulk structure API. With an older Omnibus service, the Position and Movement Report changes work and the Omnibus Position Report returns `502`.

## Output formats

Select the format with the `format` field in the request body.

### CSV

The service encodes CSV files as UTF-8 with a byte order mark (BOM) for spreadsheet compatibility, and uses `\r\n` (CRLF) line endings. The structure is:

| Line | Content |
|  --- | --- |
| 1 | The metadata header, prefixed with `# metadata:` followed by a JSON object. |
| 2 | The column headers, matching the report's column contract. |
| 3+ | One data row per record. |


All financial values are quoted strings (for example, `"0.000000000000000001"`). This prevents spreadsheet software and CSV parsers from silently truncating high-precision decimals.

### JSON

JSON responses wrap the data in a metadata envelope:

```json
{
  "metadata": {
    "exportId": "uuid",
    "requestAuthor": "providerId:loginId",
    "generatedAt": "ISO-8601",
    "filters": { },
    "recordCount": 1234,
    "controlTotals": { }
  },
  "data": [ ]
}
```

The metadata also carries the report's time parameters: `asOfTimestamp` for the Position and Omnibus Position Reports, and `dateRangeStart` and `dateRangeEnd` for the Movement Report. In the Omnibus Position Report, `controlTotals` is nested per account type (`omnibus`, `depositWallet`, `virtualAccount`), each with its own `recordCount`. See [Omnibus Position Report > Control totals](/pt-br/products/custody/v1.42/data-export/omnibus-position-report#control-totals).

Financial values in JSON are strings, never bare numbers. Always parse them as strings.

## Financial precision

The service exports every financial value as a decimal string in the asset's native unit, with precision matching the asset's decimal places. It never exports raw blockchain denominations (except the Movement Report's explicit `rawTransactionValue` column) or scientific notation.

| Asset | Raw value (stored) | Decimals | Exported value |
|  --- | --- | --- | --- |
| XRP | `3000000` | 6 | `3.000000` |
| ETH | `50000000000000000` | 18 | `0.050000000000000000` |
| BTC | `100000000` | 8 | `1.00000000` |


Always parse financial values as strings
Financial values are strings in both CSV and JSON. Never parse them as floating-point numbers. Standard double-precision numbers lose accuracy beyond roughly 15 significant digits, and some assets carry many more decimal places—ETH has 18, and some ledgers support far more. Parsing as a number silently corrupts the value.

The service rounds down, consistent with banking practice: an export never reports more value than actually exists.

## Operational limits

| Limit | Value |
|  --- | --- |
| Maximum date range (Movement Report) | 30 days |
| Maximum filter array size (`vaultIds`, `tickerIds`, `accountIds`, `tenantIds`) | 1,000 entries per array |
| Maximum transfers per Movement Report | 100,000 rows |
| Duplicate detection window | 60 seconds |
| Maximum concurrent exports per user | 2 |
| Maximum rows per Position Report | 100,000 rows |
| Maximum rows per Omnibus Position Report | 100,000 rows |
| Maximum accessible domains per request (with `includeChildDomains`) | 100 domains |
| Stale export release window | 5 minutes |


## Error responses

Errors return a standard HTTP status code with a client-safe JSON body. Error messages never expose internal details such as query fragments, stack traces, service names, or file paths. The service logs detailed diagnostics on the server against the export ID for correlation.

| Code | Meaning | When it occurs |
|  --- | --- | --- |
| `400` | Bad Request | A validation failure: a missing required field, a malformed UUID or timestamp, a future timestamp, a date range over 30 days, an end before start, an empty status array, or a filter array over 1,000 entries. |
| `401` | Unauthorized | The JWT is missing or invalid. |
| `403` | Forbidden | You lack access to the target domain: no profile, a locked user, a locked domain, or an insufficient role. |
| `404` | Not Found | Omnibus support isn't enabled for this environment (Omnibus Position Report only). See [Omnibus support](#omnibus-support). |
| `413` | Payload Too Large | The request scope exceeds a report limit — either more than 100 accessible domains, or a result that would exceed 100,000 rows. Narrow the range or add filters and retry. |
| `422` | Unprocessable Entity | The domain has no omnibus structure (Omnibus Position Report only). The service never returns an empty file in this case. |
| `429` | Too Many Requests | A duplicate request within 60 seconds while a previous export is still processing, or you already have 2 exports processing. |
| `502` | Bad Gateway | The upstream balance source returned an error (Position and Omnibus Position Reports), or the Omnibus service is unavailable while omnibus support is on. |
| `504` | Gateway Timeout | The export exceeded the service's internal time budget. |


### Error response body

Every error body carries a numeric `statusCode`, a short `error` label, and a `message`:

```json
{
  "message": "Access denied",
  "error": "Forbidden",
  "statusCode": 403
}
```

For `400` validation failures, `message` is an array of one or more client-safe validation strings that name the offending field:

```json
{
  "message": ["asOfTimestamp must not be in the future"],
  "error": "Bad Request",
  "statusCode": 400
}
```

Every `message` value is client-safe: it never exposes internal details such as query fragments, stack traces, service names, or file paths.

## Next steps

| Page | Description |
|  --- | --- |
| [Data export overview](/pt-br/products/custody/v1.42/data-export/overview) | Review report types, export metadata, and control totals. |
| [Position Report](/pt-br/products/custody/v1.42/data-export/position-report) | Generate a point-in-time balance snapshot. |
| [Movement Report](/pt-br/products/custody/v1.42/data-export/movement-report) | Generate a transaction history for a date range. |
| [Omnibus Position Report](/pt-br/products/custody/v1.42/data-export/omnibus-position-report) | Generate a snapshot of an omnibus structure, including virtual account balances. |