> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crossmint.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage Payment Methods

> Save and manage your users' payout destinations as reusable payment methods

Before creating an offramp order, [save the destination bank account as a payment method](/offramp/api-reference/payment-methods/create-payment-method). Saved payment methods can be reused across Crossmint products, so users only need to enter their bank details once.

## Save a Payment Method

The request body depends on the payment method `type`. See [The Payment Method Object](/offramp/api-reference/payment-methods/payment-method-object) for the full list of types and fields.

The examples below use the staging URL, `https://vault.staging.crossmint.com`. In production, use `https://vault.crossmint.com`.

<Tabs>
  <Tab title="US">
    ```bash cURL theme={null}
    curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
      -H 'X-API-KEY: <YOUR_SERVER_API_KEY>' \
      -H 'Content-Type: application/json' \
      -d '{
        "type": "bank-account-us",
        "userLocator": "<USER_LOCATOR>",
        "label": "Wells Fargo Checking",
        "bankAccount": {
          "accountNumber": "<ACCOUNT_NUMBER>",
          "routingNumber": "091300010",
          "accountType": "checking",
          "entityType": "individual",
          "accountHolder": {
            "name": "Emily Carter"
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Europe (SEPA)">
    ```bash cURL theme={null}
    curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
      -H 'X-API-KEY: <YOUR_SERVER_API_KEY>' \
      -H 'Content-Type: application/json' \
      -d '{
        "type": "bank-account-sepa-iban",
        "userLocator": "<USER_LOCATOR>",
        "label": "My Wise EUR",
        "bankAccount": {
          "iban": "BE05100052384175",
          "bic": "TRWIBEB1XXX",
          "entityType": "individual",
          "accountHolder": {
            "name": "Lukas Meier"
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Colombia">
    ```bash cURL theme={null}
    curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
      -H 'X-API-KEY: <YOUR_SERVER_API_KEY>' \
      -H 'Content-Type: application/json' \
      -d '{
        "type": "bank-account-co",
        "userLocator": "<USER_LOCATOR>",
        "label": "Mi Bancolombia",
        "bankAccount": {
          "accountNumber": "2048817320",
          "institutionId": "co-bancolombia",
          "accountType": "checking",
          "entityType": "individual",
          "accountHolder": {
            "name": "Camila Restrepo",
            "document": { "type": "cc", "number": "1020304050" }
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Colombia (Bre-B)">
    ```bash cURL theme={null}
    curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
      -H 'X-API-KEY: <YOUR_SERVER_API_KEY>' \
      -H 'Content-Type: application/json' \
      -d '{
        "type": "bre-b",
        "userLocator": "<USER_LOCATOR>",
        "label": "Nequi Ahorros",
        "breB": {
          "key": "@MQ7RK2PLTA",
          "entityType": "individual",
          "accountHolder": {
            "name": "Santiago Vargas"
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Indonesia">
    ```bash cURL theme={null}
    curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
      -H 'X-API-KEY: <YOUR_SERVER_API_KEY>' \
      -H 'Content-Type: application/json' \
      -d '{
        "type": "bank-account-id",
        "userLocator": "<USER_LOCATOR>",
        "label": "My BCA account",
        "bankAccount": {
          "accountNumber": "1234567890",
          "institutionId": "id-bca",
          "entityType": "individual",
          "accountHolder": {
            "name": "Budi Santoso"
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Mexico (CLABE)">
    ```bash cURL theme={null}
    curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
      -H 'X-API-KEY: <YOUR_SERVER_API_KEY>' \
      -H 'Content-Type: application/json' \
      -d '{
        "type": "bank-account-mx-clabe",
        "userLocator": "<USER_LOCATOR>",
        "label": "Mi cuenta Nu",
        "bankAccount": {
          "clabe": "646180527394108255",
          "entityType": "individual",
          "accountHolder": {
            "name": "Andrea Fuentes Lopez"
          }
        }
      }'
    ```
  </Tab>
</Tabs>

The response returns the saved payment method, including its `paymentMethodId` and a masked `accountSuffix`. Account numbers, IBANs, CLABEs, and Bre-B keys never appear in full. Pass the `paymentMethodId` as the `recipient` when you [create an order](/offramp/quickstarts/rest-api). The examples show a payment method after its checks complete. A create response has `status: pending`.

<Tabs>
  <Tab title="US">
    ```json theme={null}
    {
      "type": "bank-account-us",
      "paymentMethodId": "57d99e28-8fad-4158-b825-75a693e44b10",
      "status": "active",
      "statusReason": null,
      "label": "Wells Fargo Checking",
      "bankAccount": {
        "accountSuffix": "<LAST_4_DIGITS>",
        "routingNumber": "091300010",
        "accountType": "checking",
        "currency": "usd",
        "country": "US",
        "entityType": "individual",
        "accountHolder": {
          "name": "Emily Carter"
        },
        "rails": {
          "supported": [
            "ach-credit",
            "same-day-ach-credit"
          ],
          "pending": [
            "rtp-credit"
          ]
        }
      },
      "createdAt": "2026-08-20T19:10:00.000Z",
      "updatedAt": "2026-08-20T19:10:00.000Z"
    }
    ```

    The full account number is never returned. The response carries `bankAccount.accountSuffix`, the last four digits, while `routingNumber` comes back in full because it is not secret. A create that passes the structural checks, such as the ABA routing check digit, returns `status: "active"` straight away. This does not establish that the account exists or who owns it; those checks run when you create an order. The available rails depend on the addresses you supply: `fedwire` appears in `rails.supported` only when both `accountHolder.address` and `bankAddress` are provided. `rtp-credit` starts in `rails.pending` because not every US bank participates in the RTP network, and it moves to `rails.supported` once the receiving bank's participation is confirmed. `institutionName` is never populated for US accounts.
  </Tab>

  <Tab title="Europe (SEPA)">
    ```json theme={null}
    {
      "type": "bank-account-sepa-iban",
      "paymentMethodId": "19ee530a-80b3-401c-9a53-d6886c22fdd4",
      "status": "active",
      "reason": null,
      "default": false,
      "displayName": "Wise ••4175",
      "display": {
        "label": "Wise ••4175"
      },
      "lastPayoutAt": "2026-09-15T22:00:51.733Z",
      "bankAccount": {
        "accountHolder": {
          "name": "Lukas Meier"
        },
        "bankName": "Wise",
        "accountSuffix": "4175",
        "currency": "eur",
        "country": "BE",
        "entityType": "individual",
        "bic": "TRWIBEB1XXX",
        "rails": {
          "status": "complete",
          "supported": [
            "sepa"
          ],
          "pending": []
        }
      },
      "createdAt": "2026-09-15T19:04:06.788Z",
      "updatedAt": "2026-09-15T22:00:55.633Z"
    }
    ```

    The full IBAN is never returned. The response carries `bankAccount.accountSuffix`, the last four characters. SEPA validation is structural only (the IBAN mod-97 check), so a create that passes it returns `status: "active"` straight away. This does not establish that the account exists or who owns it. `currency` and `country` are read-only and derived from the IBAN. `bankName` is display-only and provider-sourced, so it stays absent when unknown.
  </Tab>

  <Tab title="Colombia">
    ```json theme={null}
    {
      "type": "bank-account-co",
      "paymentMethodId": "6b489a5d-3867-4ec9-bb76-072a89e1319d",
      "status": "pending",
      "statusReason": null,
      "label": "Mi Bancolombia",
      "bankAccount": {
        "accountSuffix": "7320",
        "institutionId": "co-bancolombia",
        "accountType": "checking",
        "currency": "cop",
        "country": "CO",
        "entityType": "individual",
        "accountHolder": {
          "name": "Camila Restrepo",
          "document": { "type": "cc", "suffix": "4050" }
        },
        "rails": { "status": "pending", "supported": [], "pending": ["ach-credit"] }
      },
      "createdAt": "2026-08-21T22:10:00.000Z",
      "updatedAt": "2026-08-21T22:12:11.000Z"
    }
    ```

    The full account and document numbers are never returned. The response carries `bankAccount.accountSuffix` and `accountHolder.document.suffix`, the last four digits of each. Crossmint rejects an `institutionId` that is not on the [Bank Institution IDs](/offramp/guides/bank-institution-ids) page with `422`. `accountType` is `checking`, `savings`, or `electronic-deposit`, and `accountHolder.document.type` is one of `cc`, `nit`, `ce`, `pa`, `ppt`, `ti`, `rc`, `te`, or `die`. `accountHolder.name` is always echoed back exactly as sent, and ownership is evaluated when you create an order, not on the payment method.
  </Tab>

  <Tab title="Colombia (Bre-B)">
    ```json theme={null}
    {
      "type": "bre-b",
      "paymentMethodId": "7fdf72b9-f627-4b89-b7d7-5795893aaf12",
      "status": "active",
      "statusReason": null,
      "label": "Nequi Ahorros",
      "breB": {
        "accountHolder": {
          "name": "Santiago Vargas"
        },
        "accountSuffix": "PLTA",
        "currency": "cop",
        "country": "CO",
        "entityType": "individual",
        "keyType": "alphanumeric",
        "rails": {
          "supported": [
            "bre-b"
          ]
        }
      },
      "createdAt": "2026-09-17T14:27:20.725Z",
      "updatedAt": "2026-09-17T14:27:21.410Z"
    }
    ```

    The key itself is never returned. The response carries `breB.accountSuffix` instead: the last four characters of the key's distinguishing part, which for `alphanumeric` keys is everything after the leading `@` and for `email` keys is the local part before the `@`. `keyType` is derived from the key format. `institutionName` is optional display metadata from a reliable bank directory and is omitted when unknown.
  </Tab>

  <Tab title="Indonesia">
    ```json theme={null}
    {
      "type": "bank-account-id",
      "paymentMethodId": "7a3e5c91-2b4d-4f86-a1c0-9d8e7f6a5b42",
      "status": "active",
      "statusReason": null,
      "label": "My BCA account",
      "bankAccount": {
        "accountSuffix": "7890",
        "institutionId": "id-bca",
        "currency": "idr",
        "country": "ID",
        "entityType": "individual",
        "accountHolder": {
          "name": "Budi Santoso"
        },
        "rails": { "status": "complete", "supported": ["bi-fast"], "pending": [] }
      },
      "createdAt": "2026-09-23T09:00:00.000Z",
      "updatedAt": "2026-09-23T09:00:12.000Z"
    }
    ```

    The five types take the same body. Change `type`, and take the `institutionId` from the country's section of the [Bank Institution IDs](/offramp/guides/bank-institution-ids) page. Crossmint rejects an account number that does not match its type with `422`:

    | `type` | `accountNumber` | `currency` | Rails |
    | - | - | - | - |
    | `bank-account-id` | 7 to 18 digits | `idr` | `bi-fast` |
    | `bank-account-ng` | 10 digits (a NUBAN, the Nigeria Uniform Bank Account Number); the last is the check digit for the bank | `ngn` | `nip` |
    | `bank-account-ph` | 6 to 18 digits | `php` | `instapay`, `pesonet` |
    | `bank-account-th` | 7 to 12 digits | `thb` | `promptpay` |
    | `bank-account-vn` | 1 to 20 letters or digits | `vnd` | `napas-247`, `citad` |

    The request carries no `currency` or `country`: the type sets both. `accountType` is optional, `checking` or `savings`. `accountHolder.address` is optional; when you send it, `line1` and `city` are required, `line1` is at most 100 characters, and a PO box or private mailbox is rejected. The full account number is never returned. `accountHolder.name` is always echoed back exactly as sent, and ownership is evaluated when you create an order, not on the payment method.
  </Tab>

  <Tab title="Mexico (CLABE)">
    ```json theme={null}
    {
      "type": "bank-account-mx-clabe",
      "paymentMethodId": "ba4280b4-8fe3-41e5-9c38-f72d453c950f",
      "status": "active",
      "statusReason": null,
      "label": "Mi cuenta Nu",
      "bankAccount": {
        "accountHolder": {
          "name": "Andrea Fuentes Lopez"
        },
        "accountSuffix": "8255",
        "currency": "mxn",
        "country": "MX",
        "entityType": "individual",
        "rails": {
          "status": "complete",
          "supported": [
            "spei"
          ],
          "pending": []
        }
      },
      "createdAt": "2026-09-17T14:16:38.552Z",
      "updatedAt": "2026-09-17T14:16:41.026Z"
    }
    ```

    The full CLABE is never returned. The response carries `bankAccount.accountSuffix`, the last four digits, and no bank field, because the CLABE identifies the bank. A malformed CLABE check digit is rejected synchronously with `422` and nothing is stored. Crossmint also rejects a request that sends `bankCode`, `bankName`, or `institutionId` with `422`. `accountHolder.name` is always echoed back exactly as sent, and ownership is evaluated when you create an order, not on the payment method.
  </Tab>
</Tabs>

<Note>Saving a payment method does not require the user to be verified yet, but creating an order does. Before you offramp to it, verify the user and link the wallet they will pay from. See the [Quickstart](/offramp/quickstarts/rest-api).</Note>

For a complete list of fields and supported payment method types, see [The Payment Method Object](/offramp/api-reference/payment-methods/payment-method-object) in the API reference.

## Statuses

Every payment method type shares one lifecycle. `status` describes whether the payment method has passed the readiness checks required for its type and can proceed to order-level eligibility checks. It does not describe user approval, ownership, or guaranteed payout success.

| Status | Meaning |
| - | - |
| `pending` | Initial readiness checks are running. |
| `active` | Required readiness checks passed. Orders still run their own eligibility checks. |
| `rejected` | The destination cannot receive a supported payout. The initial checks ended without a usable record, a provider refused the account, or the bank returned a payout to it. To retry, create a new payment method. |
| `deleted` | Removed by you. Never reactivates. |

### Status Changes

Creation always returns `pending`. Poll the payment method to get its latest status. A US or SEPA account usually becomes `active` within seconds. A Mexican CLABE or a Bre-B key can take several minutes, because a check with the bank runs first.

The allowed transitions are:

* `pending` → `active`, `rejected`, or `deleted`
* `active` → `rejected` when a provider refuses the account or the bank returns a payout to it, or `deleted`
* `rejected` → `deleted`

An order refuses a payment method that is not `active` with the error code `payments:payment-method.not-active`. If a provider refuses the account, an `active` payment method changes to `rejected`. When this happens while you create or update the order, that request fails with the error code `payments:payment-method.rejected-by-provider`. If the payment method is deleted after the order is created and before the payout starts, the order fails. A refund of the deposit starts, except when a Colombian payout was set up before the check. An order that waits for deposit instructions also fails when its payment method is no longer `active`.

When the bank returns a payout because the account cannot receive it, every payment method for that account in your project changes to `rejected`, for all of your users. A deleted one stays `deleted`. `statusReason` gives the reason. A new payment method for that account in the same project then fails with the error code `payments:payment-method.destination-blocked`, and the error response carries the same `statusReason`:

```json theme={null}
{
    "error": true,
    "message": "This bank account cannot receive payouts. Use a different bank account.",
    "code": "payments:payment-method.destination-blocked",
    "statusReason": "destination-closed"
}
```

Ask the user for a different account.

A deleted method never reactivates.

Readiness checks start when the method is created. Fetching it, editing its label, or using it in an order does not restart those checks. A failed transfer changes the payment-method status only when the bank returns the payout because the account cannot receive it.

### Status Reasons

`statusReason` is `null` unless the payment method is `rejected`. A rejected method may include one of the following reason codes:

| Code | Meaning |
| - | - |
| `destination-not-found` | The account or key is confirmed not to exist. |
| `destination-closed` | The account is confirmed closed. |
| `destination-cannot-receive` | The destination cannot receive supported payouts. |
| `no-rail-available` | No supported payout rail reaches the destination. This is not a temporary outage. |

Branch on `status`, never on `statusReason`. New codes can appear at any time, so tolerate values you do not recognize. `statusReason` never carries provider errors, ownership, or compliance detail. Ownership is evaluated when you create an order, not on the payment method.

## Rails

`rails.supported` lists the rails available to reach the destination straight away, and `rails.pending` lists rails that may become available once confirmed. `rails.supported` can list rails while the method is still `pending`. Only an `active` method accepts an order. A valid account with no rail to reach it is `rejected` with `statusReason: "no-rail-available"`.

## List, Update, and Remove

Reads and non-sensitive updates go through the standard API. Use it to [list a user's payment methods](/offramp/api-reference/payment-methods/list-payment-methods), [fetch one](/offramp/api-reference/payment-methods/get-payment-method), [update the label or other non-sensitive metadata](/offramp/api-reference/payment-methods/update-payment-method), or [remove a payment method](/offramp/api-reference/payment-methods/delete-payment-method).

## Next Steps

<CardGroup cols={3}>
  <Card title="Quickstart" icon="bolt" href="/offramp/quickstarts/rest-api">
    Use a saved payment method in an offramp order
  </Card>

  <Card title="Institution IDs" icon="building-columns" href="/offramp/guides/institution-ids">
    Find the institutionId of a bank by country
  </Card>

  <Card title="Create Payment Method" icon="code" href="/offramp/api-reference/payment-methods/create-payment-method">
    Full request and response schema in the API reference
  </Card>
</CardGroup>
