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

# Making a withdrawal

> Use the Transaction Intent API to preview an exchange rate and then commit a manual withdrawal from a Meridian balance.

A withdrawal moves funds out of a customer's Meridian balance to a destination instrument, such as an external wallet.

Withdrawals use the Transaction Intent API, a two-step flow. You first create an intent, which returns a preview with the exchange rate and fees but moves no money. You then commit that intent, which creates the actual transaction.

## Prerequisites

Your program must be configured with the `RETAIN` routing policy.

The routing policy determines what happens to funds when they arrive in a balance, and it is set during enrollment:

* **`RETAIN`** — deposited funds stay on the balance until you move them. This is what makes manual withdrawals possible.
* **`SWEEP`** — deposited funds are automatically forwarded to a preconfigured credit instrument. Meridian initiates the withdrawal for you, so there is nothing to trigger manually.

The policy is submitted as a `routing_policy` entry in the Information Request during onboarding:

```json theme={null}
{
  "requirementKey": "routing_policy",
  "entryId": "routing_policy:0",
  "data": {
    "routingPolicy": "RETAIN"
  }
}
```

The withdrawal destination is also established during onboarding. For an external wallet destination, submit it alongside the routing policy:

```json theme={null}
{
  "requirementKey": "destination_external_wallet_account_number",
  "entryId": "destination_external_wallet_account_number:0",
  "data": {
    "accountNumber": "5551112222"
  }
}
```

<Warning>
  If your program is configured with `SWEEP`, funds leave the balance automatically and this flow does not apply. Contact your Solutions Engineer if you need a `RETAIN` program configured.
</Warning>

You also need:

* an enrollment in `ACTIVE` status — see [Onboarding your customer](/products/meridian-accounts/guides/onboarding-your-customer)
* an access token for the program context, with the `transactions:create` scope
* a funded balance to withdraw from
* the payment instrument IDs for both sides of the movement (see below)

## Identifying the two instruments

Every intent moves funds from a **debit instrument** to a **credit instrument**. Both are identified by payment instrument ID.

| Side                 | What it is                                                                                        | Requirements                            |
| :------------------- | :------------------------------------------------------------------------------------------------ | :-------------------------------------- |
| `debitInstrumentId`  | The Meridian balance funds leave from                                                             | Must be a balance, and must be `ACTIVE` |
| `creditInstrumentId` | The destination that receives the funds, such as the external wallet configured during enrollment | Must be `ACTIVE`                        |

### Debit instrument: the account ID

The debit instrument ID is the account `id`. Call `GET /v1/accounts` to list the customer's accounts, or `GET /v1/accounts/{accountId}` for a single one, and pass that `id` as `debitInstrumentId`:

```json GET /v1/accounts theme={null}
{
  "data": [
    {
      "id": "uspay-pyj5tywsw7v0pzuyjirxvso2",
      "status": "ACTIVE",
      "currency": "USD",
      "displayName": "USD Balance",
      "createdAt": "2026-08-01T10:00:00Z",
      "updatedAt": "2026-08-01T10:00:00Z",
      "statusReason": null,
      "depositReconciliationId": null
    }
  ],
  "pageIndex": 0,
  "pageSize": 25,
  "totalRecords": 1
}
```

<Note>
  The debit instrument must be a balance. Passing any other instrument type returns `400` with the message `Debit instrument must be a balance`. Both instruments must also belong to the authenticated caller, or the request returns `403`.
</Note>

### Credit instrument: provided by Meridian after onboarding

The credit instrument ID is provided by Meridian once your program has been onboarded.

Your withdrawal destination is registered as part of program configuration — for an external wallet, from the `destination_external_wallet_account_number` entry you submit in the Information Request. Meridian creates a payment instrument for that destination and gives you its ID once the program is configured.

Unlike the debit side, this ID is not returned by the accounts API. A `RETAIN` account reports only the policy type, with no destination:

```json GET /v1/accounts/{accountId} theme={null}
{
  "id": "uspay-pyj5tywsw7v0pzuyjirxvso2",
  "status": "ACTIVE",
  "currency": "USD",
  "displayName": "USD Balance",
  "routingPolicy": {
    "type": "RETAIN"
  },
  "availableBalance": "100.00",
  "actualBalance": "100.00",
  "depositInstructions": []
}
```

Store the destination instrument ID with your program configuration so your integration can reference it on every withdrawal. Contact your Solutions Engineer if you need it confirmed or you add a new destination.

## Making a withdrawal

<Steps>
  <Step title="Create the transaction intent">
    Call `POST /v1/transaction-intents` with both instruments and the amount. The response is a preview: it returns the exchange rate, the converted amount, and any fees, but no funds move yet.
  </Step>

  <Step title="Review the quoted rate">
    Show the customer the quoted `exchangeRate`, `creditAmount`, and `fees` from the intent so they can confirm before committing.
  </Step>

  <Step title="Commit the intent">
    Call `POST /v1/transaction-intents/{transactionIntentId}/commit` to execute the movement. This creates the transaction and returns the intent with a `relatedTransactionId`.
  </Step>

  <Step title="Track the transaction">
    Use the returned `relatedTransactionId` with `GET /v1/transactions/{transactionId}`, and consume [transaction webhooks](/products/meridian-accounts/webhooks/webhooks-transaction), to follow the withdrawal to completion.
  </Step>
</Steps>

### Step 1: Create the intent

`fixedSide` tells Meridian which side of the conversion your `amount` refers to. Use `DEBIT` to specify how much leaves the balance, or `CREDIT` to specify how much should arrive at the destination.

```json Request theme={null}
{
  "debitInstrumentId": "uspay-pyj5tywsw7v0pzuyjirxvso2",
  "creditInstrumentId": "uspay-m7duekqg6biutaxkvg3mxqm6",
  "fixedSide": "DEBIT",
  "amount": 100.0,
  "idempotencyKey": "withdrawal-2026-08-05-0001",
  "memo": "Manual withdrawal"
}
```

The response is the intent preview, in `CREATED` status:

```json Response (201) theme={null}
{
  "id": "usrem-b2k3e9qijzs3hl83265bmuue",
  "type": "WITHDRAWAL",
  "status": "CREATED",
  "createdAt": "2026-08-05T14:20:00Z",
  "updatedAt": "2026-08-05T14:20:00Z",
  "statusReason": null,
  "relatedTransactionId": null,
  "debitAmount": "100.00",
  "debitCurrency": "USD",
  "debitMemo": "Manual withdrawal",
  "debitInstrument": {
    "instrumentType": "ACCOUNT",
    "account": {
      "id": "uspay-pyj5tywsw7v0pzuyjirxvso2",
      "displayName": "USD Balance",
      "currency": "USD"
    }
  },
  "creditAmount": "5600.00",
  "creditCurrency": "PHP",
  "creditInstrument": {
    "instrumentType": "EXTERNAL_WALLET",
    "externalWallet": {
      "id": "uspay-m7duekqg6biutaxkvg3mxqm6",
      "displayName": "Partner Wallet",
      "currency": "PHP",
      "mask": "2222"
    }
  },
  "exchangeRate": "56.00",
  "fees": [
    {
      "type": "PROCESSING",
      "label": "Processing fee",
      "amount": "1.00",
      "currency": "USD"
    }
  ]
}
```

No funds have moved at this point. Keep the returned `id` — you need it to commit.

### Step 2: Commit the intent

Commit with the **same** `idempotencyKey` you used to create the intent:

```json Request theme={null}
{
  "idempotencyKey": "withdrawal-2026-08-05-0001"
}
```

```json Response (200) theme={null}
{
  "id": "usrem-b2k3e9qijzs3hl83265bmuue",
  "type": "WITHDRAWAL",
  "status": "COMMITTED",
  "createdAt": "2026-08-05T14:20:00Z",
  "updatedAt": "2026-08-05T14:20:45Z",
  "relatedTransactionId": "ustxn-9fk2mqp4v7yc3xr8swld6bne",
  "debitAmount": "100.00",
  "debitCurrency": "USD",
  "creditAmount": "5600.00",
  "creditCurrency": "PHP",
  "exchangeRate": "56.00",
  "fees": [
    {
      "type": "PROCESSING",
      "label": "Processing fee",
      "amount": "1.00",
      "currency": "USD"
    }
  ]
}
```

The `relatedTransactionId` is the transaction Meridian created. Committing the same intent again returns the same result rather than creating a second transaction.

## Intent status values

| Status      | Meaning                                                 |
| :---------- | :------------------------------------------------------ |
| `CREATED`   | The preview exists and can be committed                 |
| `COMMITTED` | The intent was committed and a transaction was created  |
| `EXPIRED`   | The quoted rate is no longer valid; create a new intent |
| `CANCELED`  | The intent was canceled and cannot be committed         |
| `FAILED`    | The intent could not be processed; check `statusReason` |

An intent holds a quoted exchange rate, so it does not stay valid indefinitely. If a customer takes too long to confirm, the intent reaches `EXPIRED` and committing it returns an error — create a new intent to get a fresh rate.

The intent does not return an expiry timestamp, so treat the quote as short-lived and commit promptly after the customer confirms.

## Idempotency

The `idempotencyKey` is required on both calls, and the key you commit with must match the key the intent was created with. Use one unique key per withdrawal attempt — generated from your own reference for the withdrawal — and reuse it if you need to retry either call. This makes retries safe: a repeated request returns the original result instead of creating a duplicate withdrawal.

## Recommended integration behavior

Your integration should:

* confirm the program uses `RETAIN` before exposing withdrawal functionality in your UI
* show the quoted `exchangeRate`, `creditAmount`, and `fees` to the customer before committing
* treat the intent as short-lived, and request a new one when an intent reaches `EXPIRED`
* store the `idempotencyKey` with your own withdrawal record so retries reuse it
* track completion through the `relatedTransactionId` and the [transaction webhook](/products/meridian-accounts/webhooks/webhooks-transaction), not the intent itself
