# Create and manage financial instruments

Identity Management v3
This topic describes the v3 identity model. **Identity Management v3** is recommended for all new integrations.

The supported payment corridors are:

- US • USD
- EU • EUR
- MX • MXN
- BR • BRL
- CO • COP
- CA • CAD
- GB • GBP
- NG • NGN


This tutorial shows how to use the **Identity Management v3** financial instrument endpoints to:

- Create a financial instrument for an identity
- List an identity’s financial instruments
- Retrieve a financial instrument by ID
- Update financial instrument details
- Deactivate a financial instrument


It assumes you’ve read [Financial instruments](/products/payments-direct-2/api-docs/concepts/financial-instruments) for payments and [Payment identities](/products/payments-direct-2/api-docs/concepts/payment-identities).

Multiple instruments per identity
In Identity Management v3, an identity can have **one or more financial instruments**, allowing a single party (identity) to hold multiple bank accounts or payout rails.

## Before you begin

To follow these examples, you need:

- Base URL for the Ripple Payments Direct 2.0 API (for example,` https://{base-url}`)
- A valid OAuth2 access token with scopes:
  - `participants:create` – create financial instruments
  - `participants:read` – list and get financial instruments
  - `participants:update` – update and deactivate financial instruments
- An existing `identityId` (created via the identities endpoints)


In all examples, replace:

- `{base-url}` with your PII base URL
- `<access_token>` with a valid access token
- `{identity-id} `and `{financial-instrument-id}` with real IDs from your environment


## Create a financial instrument

Use `POST /v3/identities/{identity-id}/financial-instruments` to create a financial instrument for an existing identity.

You must provide:

- `financialInstrumentType` – the rail, such as `US_ACH`, `MX_SPEI`,` BR_PIX`
- `currency` – ISO 4217 currency code (for example, `USD`, `MXN`, `BRL`)
- Exactly one rail-specific object (for example, `usAch`, `mxSpei`, `brPix`) with the required account details
- Optional metadata such as `label`


### Example: Create a US ACH bank account


```bash
curl -X POST "https://{base-url}/v3/identities/{identity-id}/financial-instruments" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "financialInstrumentType": "US_ACH",
    "currency": "USD",
    "label": "US bank account",
    "usAch": {
      "bankName": "Bank of Example",
      "bankRoutingNumber": "266231608",
      "accountNumber": "60480",
      "accountType": "CHECKING"
    }
  }'
```

### Response (201 Created)


```json
{
  "financialInstrumentId": "2f4ac57f-c5ba-4051-b51f-b3565778717b"
}
```

You’ll use this `financialInstrumentId` when:

- Getting the instrument by ID
- Updating or deactivating the instrument
- Referencing it in payment creation flows


### Example: Create an MX SPEI bank account for the same identity


```bash
curl -X POST "https://{base-url}/v3/identities/{identity-id}/financial-instruments" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "financialInstrumentType": "MX_SPEI",
    "currency": "MXN",
    "label": "MXN SPEI account",
    "mxSpei": {
      "bankName": "Banco Ejemplo",
      "clabe": "032180000118359719"
    }
  }'
```

Now the same identity holds **two financial instruments** (one US ACH, one MX SPEI).

Missing or invalid fields
If required fields are missing or invalid (for example, CLABE length or routing code format), the API returns **400 Bad Request** with details about the failing fields.

If the `identity-id` does not exist, the API returns **404 Not Found**.

### Identity validation and `validatePayoutRails`

When you create a financial instrument, the associated identity must have all required PII for the specified `financialInstrumentType`.

**If the identity was created with `validatePayoutRails`:**

- The identity's PII was already validated against the specified payout rails at identity creation time.
- Creating a financial instrument for a rail listed in `validatePayoutRails` succeeds immediately (assuming the identity has the required PII).
- Creating a financial instrument for a rail **not** listed in `validatePayoutRails` triggers validation at instrument creation time. If required PII is missing, the request fails with **400 Bad Request**.


**If the identity was created without `validatePayoutRails`:**

- The identity's PII is validated when you create the financial instrument.
- If required fields are missing for the specified `financialInstrumentType`, the request fails with **400 Bad Request** and details about which fields are missing.


**Example: Validation failure due to missing PII**

If you try to create a `US_ACH` financial instrument for an identity that's missing required fields like `dateOfBirth` or `identityDocuments`, you'll receive:


```json
{
  "code": "VALIDATION_ERROR",
  "title": "Identity validation failed",
  "description": "The identity is missing required fields for the specified financial instrument type.",
  "details": [
    {
      "field": "individual.dateOfBirth",
      "message": "Date of birth is required for US_ACH beneficiary identities"
    },
    {
      "field": "individual.identityDocuments",
      "message": "At least one identity document (SSN or Tax ID) is required for US_ACH beneficiary identities"
    }
  ]
}
```

To fix this, update the identity with the missing PII fields using `PUT /v3/identities/{identity-id}`, then retry creating the financial instrument.

Upfront validation
Using `validatePayoutRails` when creating identities helps catch PII issues early, before you attempt to create financial instruments. For details, see [Validating identities for specific payout rails](/products/payments-direct-2/api-docs/concepts/payment-identities#validating-identities-for-specific-payout-rails).

## List financial instruments for an identity

Use `GET /v3/identities/{identity-id}/financial-instruments` to list financial instruments for an identity.

Query parameters:

- `version` – optional identity version. If omitted, the latest identity version is used.
- `limit` – maximum number of instruments to return (1–100, default 10).
- `next-token` – pagination token from a previous response.


### Example: List instruments for an identity


```bash
curl -X GET "https://{base-url}/v3/identities/{identity-id}/financial-instruments?limit=10" \
  -H "Authorization: Bearer <access_token>"
```

### Response (200 OK)


```json
{
  "data": [
    {
      "financialInstrumentId": "2f4ac57f-c5ba-4051-b51f-b3565778717b",
      "financialInstrumentType": "US_ACH",
      "currency": "USD",
      "label": "US bank account",
      "country": "US",
      "createdAt": "2025-10-01T18:46:47.430Z",
      "updatedAt": "2025-10-01T18:46:47.430Z"
    },
    {
      "financialInstrumentId": "9e267ec3-0f75-4e8b-93b2-bf7e2f2a9e0d",
      "financialInstrumentType": "MX_SPEI",
      "currency": "MXN",
      "label": "MXN SPEI account",
      "country": "MX",
      "createdAt": "2025-10-01T18:50:12.100Z",
      "updatedAt": "2025-10-01T18:50:12.100Z"
    }
  ],
  "nextToken": "eyJrZXkxIjoidmFsdWVfMSIsImtleTIiOiJ2YWx1ZTIifQ=="
}
```

- The data array can contain **multiple** instruments for the same identity.
- Only metadata is returned; rail-specific objects (such as `usAch` or `mxSpei`) are not included.
- If `nextToken` is present, pass it as `next-token` to fetch the next page:



```bash
curl -X GET "https://{base-url}/v3/identities/{identity-id}/financial-instruments?limit=10&next-token=eyJrZXkxIjoidmFsdWVfMSIsImtleTIiOiJ2YWx1ZTIifQ==" \
  -H "Authorization: Bearer <access_token>"
```

If the identity or instruments are not found, the API returns **404 Not Found**.

## Get a financial instrument by ID

Use `GET /v3/identities/{identity-id}/financial-instruments/{financial-instrument-id}` to retrieve full details for a specific financial instrument, including the rail-specific object.

###Example: Get a US ACH financial instrument


```bash
curl -X GET "https://{base-url}/v3/identities/{identity-id}/financial-instruments/{financial-instrument-id}" \
  -H "Authorization: Bearer <access_token>"
```

### Response (200 OK)


```json
{
  "financialInstrument": {
    "country": "US",
    "financialInstrumentId": "7f2bac05-42a3-4b26-89fd-333396fdba70",
    "createdAt": "2025-10-01T18:46:47.430Z",
    "updatedAt": "2025-10-01T18:46:47.430Z",
    "usAch": {
      "bankName": "Bank of Example",
      "bankRoutingNumber": "266231608",
      "accountNumber": "60480",
      "accountType": "CHECKING"
    },
    "currency": "USD",
    "label": "US bank account",
    "financialInstrumentType": "US_ACH"
  }
}
```

Errors
If either the `identity-id` or `financial-instrument-id` is invalid or does not exist, the API returns **400** or **404** respectively.

## Update a financial instrument

Use `PUT /v3/identities/{identity-id}/financial-instruments/{financial-instrument-id}` to update editable fields of an existing financial instrument.

**You can:**

- Update metadata such as label.
- Update rail-specific details, such as account number or PIX key.


**You cannot change:**

- `financialInstrumentType`
- `currency`


### Partial updates

The request supports partial updates:

- Fields you include are **overwritten**.
- Fields you omit remain **unchanged**.


### Example: Update only the label


```bash
curl -X PUT "https://{base-url}/v3/identities/{identity-id}/financial-instruments/{financial-instrument-id}" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "US bank account (primary)"
  }'
```

### Response (200 OK)


```json
{
  "financialInstrumentId": "7f2bac05-42a3-4b26-89fd-333396fdba70",
  "financialInstrumentType": "US_ACH",
  "currency": "USD",
  "label": "US bank account (primary)",
  "country": "US",
  "createdAt": "2025-10-01T18:46:47.430Z",
  "updatedAt": "2025-10-02T09:15:10.000Z"
}
```

Response
The update response returns the **instrument entry** (metadata + timestamps) but does **not** include the rail-specific object (such as `usAch`).

### Example: Update rail-specific details (US ACH)


```bash
curl -X PUT "https://{base-url}/v3/identities/{identity-id}/financial-instruments/{financial-instrument-id}" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "usAch": {
      "bankName": "Bank of Example",
      "bankRoutingNumber": "266231608",
      "accountNumber": "987654321",
      "accountType": "CHECKING"
    }'
```

Errors
If the update payload is invalid (for example, unsupported account type or invalid routing format), the API returns **400 Bad Request** with error details.

If the identity or instrument is not found, the API returns **404 Not Found**.

If there is a conflicting resource state (for example, concurrent update or other internal constraint), the API may return **409 Conflict**.

## Deactivate a financial instrument

Use `DELETE /v3/identities/{identity-id}/financial-instruments/{financial-instrument-id}` to deactivate a financial instrument.

**Deactivation:**

- Is **permanent** for that instrument.
- Prevents the instrument from being used for **new payments**.
- Keeps historical usage and data for audit and reconciliation.


Identity deactivation
Deactivating an identity (via the identities endpoints) also deactivates its financial instrument.

### Example: Deactivate a financial instrument


```bash
curl -X DELETE "https://{base-url}/v3/identities/{identity-id}/financial-instruments/{financial-instrument-id}" \
  -H "Authorization: Bearer <access_token>"
```

### Response (204 No Content)

The following errors may occur:

- **400 Bad Request** – malformed identity or instrument ID.
- **404 Not Found** – the identity or instrument does not exist.
- **409 Conflict** – the instrument is already deactivated or cannot be deactivated due to its current state.
- **500 Internal Server Error** – unexpected server-side error.


## Error handling

All financial instrument endpoints use the standard error response schema:

- **code** – machine-readable error code
- **title** – short description
- **description** – detailed explanation and remediation hints


Common scenarios:

- **400** – structural validation errors (missing required fields, pattern violations, invalid enum values).
- **404** – identity or financial instrument not found.
- **409** – conflicting state (for example, update or deactivate not allowed).
- **500** – internal processing error; if persistent, contact Ripple support.


Refer to the [Error handling reference](/products/payments-direct-2/api-docs/error-codes/api-errors) in the API docs for the complete list of error codes.

## Next steps

- To understand how instruments fit into the overall payout setup, see [Financial instruments](/products/payments-direct-2/api-docs/concepts/financial-instruments) for payments.
- To review the KYC / PII model that instruments attach to, see [Payment identities](/products/payments-direct-2/api-docs/concepts/payment-identities).
- To see how identities and financial instruments are used together when sending money, continue with the [Create a payment](/products/payments-direct-2/api-docs/tutorials/create-a-payment) tutorial.