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

> ## Agent Instructions
> If you are an AI assistant operating Infinite for a user, read the skill first: https://docs.infinite.dev/agent-skill.md (setup per host, tool reference, rules for moving money, journeys).
> When operating Infinite for a user, use the MCP server. Sign in at https://bot.api.infinite.dev/mcp (production, acts as the user) or send `Authorization: Bearer <key>` to https://bot.api.infinite.dev/mcp/sse. Prefer its tools over raw HTTP; use REST only for steps no tool covers, and check `get_endpoint_schema` before guessing a request shape.
> Net-new Infinite Connect (org_connect) is operator-assisted: do not grant end users shared org_connect Auth Kit membership or the shared platform org API key — those credentials are org-wide on /v1. BYO org keys onboard under that customer's org id. Operator sessions should show org_connect in get_organization_info.
> The key decides the tools. Every organization key (`sk_sandbox_…` / `sk_live_…`) gets docs, onboarding, `get_account_balance`, and request logs for your own organization. BYO organizations add payees an agent will pay with `create_payee` (payable once ACTIVE). Platform Connect sessions instead get bound-customer payment lookups (`get_quote`, `get_transfer`, `list_transfers`, `list_account_balances`), payee drafts (`create_payee_draft` / `get_payee_draft`; screening only, never payable), and transfer drafts that wait for Infinite approval (`create_transfer_draft` / `get_transfer_draft`; never POST /v1/transfers). Where offered, platform `create_transfer` files a real transfer that Infinite holds in review before funding; it cannot be submitted from DRAFT.
> An agent key (`sk_sandbox_agent_…` / `sk_live_agent_…`) moves money within that agent's limits: `create_transfer` → `submit_transfer` → `wait_for_transfer`.
> On sandbox organization-key sessions, `sandbox_approve_customer` (makes the capabilities you pass READY, and related ones may turn READY too; `request_capabilities` is not needed), `sandbox_fund_account`, and `sandbox_complete_transfer` stand in for verification, funding, and settlement, so a whole flow runs from MCP. BYO keys can pass `customerId` on each call without binding; platform sessions must bind a customer first (`create_business_customer` auto-binds, or send `X-Infinite-On-Behalf-Of`).
> When searching documentation without a key, use the docs MCP at https://docs.infinite.dev/mcp.
> Start in sandbox (https://sandbox.api.infinite.dev). Do not use production unless the user explicitly asks.
> Confirm amount, currency, and payee with the user before any payment is submitted. Pass the invoice number as `reference` on `create_transfer`; the summary says what the payee will see.
> REST amounts are integer minor units (`"4000"` = 40.00 USD). MCP payment tools take exact decimal strings (`"40.00"`). Never mix them.
> Agent denials return `403 { denial: { code, message, window, limit, used, remaining, attemptedAmount, currency }, hint }` with decimal amounts; per-transfer denials omit `used` and `remaining`. Report them to the user; do not work around them.

# Fixed Routes

> Standing deposit mailboxes that auto-sweep. Unique virtual bank accounts, USD balance credits, and crypto deposit wallets, plus sandbox simulate-deposit

Fixed routes are standing payment routes: a dedicated deposit account or wallet address that is permanently wired to a destination. Once a route is created, every deposit that lands on it is automatically converted (if needed) and delivered to the configured destination -- no per-payment API call required.

## Overview

A fixed route binds together:

* A **source account** provisioned by Infinite -- either a virtual bank account (for fiat deposits) or a deposit wallet address (for crypto deposits)
* A **sweep target** -- the destination the funds are delivered to (a wallet, an external bank account, or a USD balance account)
* The **currencies and rails** for the conversion (e.g. USD via wire in, USDC on Base out)

Share the deposit details with your customer once. From then on, each incoming deposit triggers an automatic sweep to the destination, for any amount, indefinitely.

```mermaid theme={null}
flowchart LR
  Dep[Deposit lands on<br/>source account] --> Route[Fixed route<br/>configuration]
  Route --> Convert[Convert currency<br/>if needed]
  Convert --> Payout[Deliver to<br/>sweep target]
```

## Route Types

The route type is inferred from the source and sweep target account types:

| Type | Source | Sweep target | Example |
| - | - | - | - |
| `FIAT_TO_CRYPTO` | Virtual bank account | Wallet | USD wire in, USDC on Base out |
| `FIAT_TO_FIAT` | Virtual bank account | Your balance account | USD wire in, held as a USD balance (no crypto leg) |
| `CRYPTO_TO_FIAT` | Deposit wallet address | External bank account | USDC on Ethereum in, USD via ACH out |
| `CRYPTO_TO_CRYPTO` | Deposit wallet address | Wallet on another chain/currency | USDT on Ethereum in, USDT on Polygon out (same-asset bridge), or USDT on Tron in, USDC on Solana out |
| `FIAT_TO_CRYPTO_TO_FIAT` | Virtual bank account | External bank account | USD in, cross-border fiat payout out |

`FIAT_TO_FIAT` is inferred when the sweep target is the customer's USD balance account. `destinationRail` is `BALANCE`, and there is no crypto leg. An external bank account as the sweep target is `FIAT_TO_CRYPTO_TO_FIAT` (fiat in, convert, fiat payout) -- not a balance credit. See [Fiat deposits into a USD balance](#fiat-deposits-into-a-usd-balance).

## Fixed Routes vs Transfers

Fixed routes and [transfers](/concepts/transfers) move money through the same underlying engine -- the difference is who initiates each payment and when.

| | Transfers | Fixed Routes |
| - | - | - |
| Initiation | You create one per payment via `POST /v1/transfers` | Created once; deposits initiate payments automatically |
| Amount | Fixed at creation | Whatever amount is deposited |
| Destination | Chosen per transfer | Fixed at route creation |
| Deposit address | May differ per transfer | Permanent and reusable |
| Best for | One-off payments, programmatic payouts | Recurring inbound flows: on-ramps, off-ramps, settlement addresses |

They are similar in every other way:

* **Each swept deposit creates a transfer.** When a deposit lands on a fixed route, Infinite creates a transfer behind the scenes. It appears in `GET /v1/transfers`, has the same statuses and legs, and emits the same `transfer.*` [webhooks](/guides/webhooks) -- so your reconciliation and tracking logic is identical for both.
* **Same compliance gates.** The customer must be verified and `ACTIVE` before a fixed route can be created, just like transfers.
* **Same fee model.** Developer fees (`developerFeeFixed`, `developerFeePercent`) configured on the route are applied to every sweep. `FIAT_TO_FIAT` routes are the exception: the deposit settles into your balance at face value, so developer fees are rejected on create and on update.

## Creating a Fixed Route

There are two creation endpoints depending on the source side. Both require the customer to be verified, and both support the `Idempotency-Key` header for safe retries.

### Fiat deposits (virtual bank account)

Creates a virtual bank account in the customer's name. The example below converts ACH deposits to USDC on Base. To credit a USD balance with no conversion, see [Fiat deposits into a USD balance](#fiat-deposits-into-a-usd-balance).

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.infinite.dev/v1/fixed-routes/virtual-bank-accounts',
      headers={
          'Authorization': 'Bearer sk_live_...',
          'Content-Type': 'application/json',
      },
      json={
          'customerId': 'cus_abc123',
          'sweepTargetAccountId': 'ewa_abc123',
          'sourceRail': 'ACH',
          'sourceCurrency': 'USD',
          'destinationRail': 'BASE',
          'destinationCurrency': 'USDC',
      },
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    'https://api.infinite.dev/v1/fixed-routes/virtual-bank-accounts',
    {
      method: 'POST',
      headers: {
        Authorization: 'Bearer sk_live_...',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        customerId: 'cus_abc123',
        sweepTargetAccountId: 'ewa_abc123',
        sourceRail: 'ACH',
        sourceCurrency: 'USD',
        destinationRail: 'BASE',
        destinationCurrency: 'USDC',
      }),
    },
  );
  ```

  ```bash CLI theme={null}
  export INFINITE_API_KEY=sk_live_...
  infinite api POST /v1/fixed-routes/virtual-bank-accounts --body '{
    "customerId": "cus_abc123",
    "sweepTargetAccountId": "ewa_abc123",
    "sourceRail": "ACH",
    "sourceCurrency": "USD",
    "destinationRail": "BASE",
    "destinationCurrency": "USDC"
  }'
  ```

  ```text MCP theme={null}
  Create a virtual bank account for cus_abc123 that converts USD ACH deposits to USDC on Base and sweeps to ewa_abc123.

  # Infinite MCP is read-only today — run the CLI tab to execute.
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.infinite.dev/v1/fixed-routes/virtual-bank-accounts" \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "customerId": "cus_abc123",
      "sweepTargetAccountId": "ewa_abc123",
      "sourceRail": "ACH",
      "sourceCurrency": "USD",
      "destinationRail": "BASE",
      "destinationCurrency": "USDC"
    }'
  ```
</CodeGroup>

The response includes `depositInstructions` on the source account -- bank name, account number, and routing number your customer uses to send fiat.

USD ACH, wire, and SWIFT pay-ins can set `depositMatching` on create:

* `DEDICATED` — unique account number. The sender does not include a payment reference, and `depositInstructions.reference` is omitted.
* `REFERENCE` — shared account number. The sender must put `sourceAccount.depositInstructions.reference` on the payment (wire memo or ACH addenda). A payment without that reference is not applied to the route, even when it is the only route on the number.
* Omit `depositMatching` to keep the default for the rails you send.

Send `"depositMatching": "DEDICATED"` for a unique account number, or `"depositMatching": "REFERENCE"` when the sender will include the reference. Retry `REFERENCE` with the same `Idempotency-Key` within 24 hours to get the same reference back.

### Fiat deposits into a USD balance

Use the same endpoint when the sweep target is the customer's USD [balance account](/guides/balance-accounts) (`vba_…`, `holdsBalance: true`). Each deposit on the new virtual bank account credits that balance in USD. Nothing is converted to a stablecoin, and nothing is paid out to an external bank. Create infers `type: FIAT_TO_FIAT`.

Open the balance account first ([create a balance account](/api-reference/balance-accounts/create-a-balance-account)) and use its `id` as `sweepTargetAccountId`. The customer must be `ACTIVE`. The balance account must belong to that same customer.

The new pay-in and the balance must be held by the same banking partner, on an account number that is not already on the balance. `depositMatching` follows the rules above. The customer must be approved for that bank pay-in: `USD_ACH_PAYIN` and `USD_WIRE_PAYIN`, plus `SWIFT_PAYIN` when the list includes SWIFT. [`USD_BALANCE`](/guides/balance-accounts) ready is what opens the balance. It does not by itself make this route available, and those pay-in capabilities can also become ready through a partner that cannot settle into the balance. When the pay-in and the balance are not on the same partner with dedicated coordinates, create returns `This pay-in route cannot settle into a balance account`. Listing more than one inbound rail without that bank pay-in approval returns `Multiple inbound rails are only available for USD bank pay-in on a single deposit account. Include SWIFT only when you want international wires.`

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.infinite.dev/v1/fixed-routes/virtual-bank-accounts',
      headers={
          'Authorization': 'Bearer sk_live_...',
          'Content-Type': 'application/json',
          'Idempotency-Key': '550e8400-e29b-41d4-a716-446655440000',
      },
      json={
          'customerId': 'cus_abc123',
          'sweepTargetAccountId': 'vba_balance789',
          'sourceCurrency': 'USD',
          'sourceRails': ['ACH', 'WIRE'],
      },
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    'https://api.infinite.dev/v1/fixed-routes/virtual-bank-accounts',
    {
      method: 'POST',
      headers: {
        Authorization: 'Bearer sk_live_...',
        'Content-Type': 'application/json',
        'Idempotency-Key': '550e8400-e29b-41d4-a716-446655440000',
      },
      body: JSON.stringify({
        customerId: 'cus_abc123',
        sweepTargetAccountId: 'vba_balance789',
        sourceCurrency: 'USD',
        sourceRails: ['ACH', 'WIRE'],
      }),
    },
  );
  ```

  ```bash CLI theme={null}
  export INFINITE_API_KEY=sk_live_...
  infinite api POST /v1/fixed-routes/virtual-bank-accounts \
    --header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
    --body '{
    "customerId": "cus_abc123",
    "sweepTargetAccountId": "vba_balance789",
    "sourceCurrency": "USD",
    "sourceRails": ["ACH", "WIRE"]
  }'
  ```

  ```text MCP theme={null}
  Create a virtual bank account for cus_abc123 that accepts USD ACH and wire deposits and credits balance account vba_balance789. Do not convert to crypto.

  # Infinite MCP is read-only today — run the CLI tab to execute.
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.infinite.dev/v1/fixed-routes/virtual-bank-accounts" \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
    -d '{
      "customerId": "cus_abc123",
      "sweepTargetAccountId": "vba_balance789",
      "sourceCurrency": "USD",
      "sourceRails": ["ACH", "WIRE"]
    }'
  ```
</CodeGroup>

#### Request fields

| Field | Required | Balance sweep |
| - | - | - |
| `customerId` | Yes | Customer who owns both accounts |
| `sweepTargetAccountId` | Yes | The USD balance account (`vba_…`, `holdsBalance: true`). Not a wallet, not an external bank account (`eba_…`), and not a stablecoin balance (`pba_…`) |
| `sourceCurrency` | No | `USD`. Must match the balance currency. Defaults to `USD` when omitted |
| `sourceRails` | No | Inbound rails on this deposit account. See below |
| `sourceRail` | No | Legacy single rail. Prefer `sourceRails` |
| `destinationRail` | No | Omit. The server sets `BALANCE` |
| `destinationCurrency` | No | Omit. The server sets the balance currency (`USD`) |
| `developerFeeFixed`, `developerFeePercent` | No | Omit. A non-zero fee returns `Developer fees are not supported when the destination is a balance account` |

`destinationRail` and `destinationCurrency` on this request do not select a chain or a stablecoin. Create replaces them with `BALANCE` and the balance currency, and stores `type` as `FIAT_TO_FIAT`. On [`PATCH /v1/fixed-routes/{id}`](/api-reference/fixed-routes/update-a-fixed-route), sending either field while the sweep target is a balance account is rejected (`destinationRail and destinationCurrency cannot be set when the destination is a balance account`).

[`POST /v1/fixed-routes/validate`](/api-reference/fixed-routes/validate-if-a-fixed-route-can-be-created) does not infer the route type. Pass `fixedRouteType: "FIAT_TO_FIAT"`. An omitted `destinationRail` then defaults to `BALANCE`. Validate does not apply the banking-partner check that create does, so a successful validate is not proof the pay-in can settle into the balance. Read `type` and `destinationRail` on the create response.

A pay-in currency that does not match the balance returns `Balance account currency must match the pay-in currency`. A different banking partner, or deposit coordinates shared with the balance account, returns `This pay-in route cannot settle into a balance account`. Crypto deposit routes (`POST /v1/fixed-routes/virtual-wallets`) cannot credit a balance account (`Balance accounts can only receive same-currency deposits from a fiat-to-fiat route.`).

You can point an existing fiat pay-in (`FIAT_TO_CRYPTO`) at the balance later: [`PATCH /v1/fixed-routes/{id}`](/api-reference/fixed-routes/update-a-fixed-route) with `sweepTargetAccountId` set to the balance account, and without `destinationRail` or `destinationCurrency`. The route type flips to `FIAT_TO_FIAT`, `destinationRail` becomes `BALANCE`, and the deposit account number stays the same. The change is rejected while the route has transfers in flight. Crypto deposit routes and fiat sandwich routes cannot retarget to a balance account.

#### `sourceRail` vs `sourceRails`

One USD bank pay-in account can accept more than one inbound rail. Domestic wire is `WIRE`.

| You send | The deposit account accepts |
| - | - |
| Neither `sourceRail` nor `sourceRails` | ACH and wire. Response `sourceRails` is `["ACH", "WIRE"]` and `sourceRail` is `ACH` |
| `sourceRails: ["ACH", "WIRE"]` | Same as omitting both |
| `sourceRail: "WIRE"` and no `sourceRails` | Wire only. Response `sourceRails` is `["WIRE"]` and `sourceRail` is `WIRE` |
| `sourceRails: ["ACH", "WIRE", "SWIFT"]` | ACH, wire, and SWIFT. SWIFT is included only when listed |
| Both, and `sourceRail` is one of `sourceRails` | `sourceRails` wins |
| Both, and `sourceRail` is not in `sourceRails` | `400` -- `sourceRail must be included in sourceRails.` |

More than one rail is only for a USD bank pay-in on a single deposit account. Anything else returns `Multiple inbound rails are only available for USD bank pay-in on a single deposit account. Include SWIFT only when you want international wires.` A list may only include `ACH`, `WIRE`, and `SWIFT` (`Multiple inbound rails can only include ACH, WIRE, and SWIFT`). A USD balance sweep does not accept other rails.

`sourceRail` on the response is the representative rail: ACH, then WIRE, then SWIFT. Deposit instructions include a routing number when ACH or wire is listed. When SWIFT is listed they also include `swiftBic` and the customer's legal address as `beneficiaryAddress`. A SWIFT-only list omits the routing number.

A deposit is not swept when its inbound rail is missing or is not in `sourceRails`. An on-us credit onto that same account number (a transfer that stays inside the banking partner, not an external ACH, wire, or SWIFT) is the exception: it is accepted on every enforced list and is not added to `sourceRails`.

#### How this differs from a convert route

| | USD balance sweep | Wallet onramp | Crypto-to-fiat | Fiat sandwich |
| - | - | - | - | - |
| Create | `POST /v1/fixed-routes/virtual-bank-accounts` | Same | `POST /v1/fixed-routes/virtual-wallets` | `POST /v1/fixed-routes/virtual-bank-accounts` |
| Sweep target | USD balance `vba_…` | Wallet (`ewa_…` or `inw_…`) | External bank `eba_…` | External bank `eba_…` |
| `type` | `FIAT_TO_FIAT` | `FIAT_TO_CRYPTO` | `CRYPTO_TO_FIAT` | `FIAT_TO_CRYPTO_TO_FIAT` |
| `destinationRail` | `BALANCE` | Chain, such as `BASE` | Payout rail, such as `ACH` | Payout rail |
| Movement | USD in, USD credited to the balance | Fiat converted to USDC or USDT | Stablecoin converted to a fiat payout | Fiat converted, then paid out as fiat |
| Developer fees | Not supported | Supported | Supported | Supported |

The swept deposit still creates a transfer you can read from [`GET /v1/transfers`](/api-reference/transfers/list-transfers). `flow` is `FIAT_TO_FIAT`, `destination.paymentRail` is `BALANCE`, and source and destination currency are both `usd`. That transfer can show `COMPLETED` before the balance moves. Confirm the credit with [`balance.credited`](/guides/webhooks), or by reading the balance. Pay the balance out later with a transfer whose source rail is `BALANCE` -- see [Balance accounts](/guides/balance-accounts).

#### Response

```json theme={null}
{
  "id": "vba_payin123",
  "customerId": "cus_abc123",
  "type": "FIAT_TO_FIAT",
  "status": "ACTIVE",
  "enabled": true,
  "sourceAccount": {
    "id": "vba_payin123",
    "type": "VIRTUAL_BANK",
    "sourceCurrency": "USD",
    "sourceRail": "ACH",
    "sourceRails": ["ACH", "WIRE"],
    "depositInstructions": {
      "bankName": "JPMorgan Chase",
      "accountNumber": "1234567890",
      "routingNumber": "021000021"
    }
  },
  "destinationCurrency": "USD",
  "destinationRail": "BALANCE",
  "sweepTargetAccountId": "vba_balance789",
  "sharesBalanceAccountCoordinates": false,
  "createdAt": "2026-01-15T10:30:00Z",
  "updatedAt": "2026-01-15T10:30:00Z"
}
```

`id` is the new deposit account, not the balance account. Share `depositInstructions` with the sender. Read it back with [`GET /v1/fixed-routes/{id}`](/api-reference/fixed-routes/get-a-fixed-route-by-id) or [`GET /v1/customers/{customerId}/fixed-routes`](/api-reference/customers/list-fixed-routes-for-a-customer). `sharesBalanceAccountCoordinates` is `false` on this route: the deposit account number is dedicated, so it can sit alongside the balance account. When a pay-in would share the balance account's deposit coordinates, create is rejected instead.

#### Sandbox

[`POST /v1/sandbox/fixed-routes/{id}/simulate-deposit`](/api-reference/sandbox/simulate-a-fixed-route-source-deposit) takes this route's source account id (`vba_payin123`). On a route that lists `sourceRails`, pass `rail` (`ACH`, `WIRE`, or `SWIFT`) to choose the inbound rail. Omit `rail` to book the stored rail -- ACH when the route accepts ACH and wire. A rail the route does not accept is rejected (`rail must be one of …`). Use a new `reference` for each rail.

```json theme={null}
{
  "amount": "1000.00",
  "currency": "USD",
  "rail": "WIRE",
  "reference": "sandbox_wire_1"
}
```

`amount` is in major units. The call creates the sweep and credits the balance (`balance.credited`). Reusing `reference` returns the existing sweep and keeps the rail recorded the first time; a later `rail` on that reference is ignored. [Account fund-in](/api-reference/sandbox/simulate-account-funding) on the balance account credits that account directly and does not run this route.

### Crypto deposits (deposit wallet)

Creates a dedicated Infinite Wallet (`inw_`, `walletType: VIRTUAL`) -- a crypto mailbox, not a `vwa_` virtual wallet and not a treasury vault. Crypto sent to that address sweeps to an external bank account (crypto-to-fiat) or another wallet (crypto-to-crypto):

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.infinite.dev/v1/fixed-routes/virtual-wallets',
      headers={
          'Authorization': 'Bearer sk_live_...',
          'Content-Type': 'application/json',
      },
      json={
          'customerId': 'cus_abc123',
          'sweepTargetAccountId': 'eba_xyz789',
          'chainType': 'EVM',
          'sourceRail': 'BASE',
          'sourceCurrency': 'USDC',
          'destinationRail': 'ACH',
          'destinationCurrency': 'USD',
      },
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    'https://api.infinite.dev/v1/fixed-routes/virtual-wallets',
    {
      method: 'POST',
      headers: {
        Authorization: 'Bearer sk_live_...',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        customerId: 'cus_abc123',
        sweepTargetAccountId: 'eba_xyz789',
        chainType: 'EVM',
        sourceRail: 'BASE',
        sourceCurrency: 'USDC',
        destinationRail: 'ACH',
        destinationCurrency: 'USD',
      }),
    },
  );
  ```

  ```bash CLI theme={null}
  export INFINITE_API_KEY=sk_live_...
  infinite api POST /v1/fixed-routes/virtual-wallets --body '{
    "customerId": "cus_abc123",
    "sweepTargetAccountId": "eba_xyz789",
    "chainType": "EVM",
    "sourceRail": "BASE",
    "sourceCurrency": "USDC",
    "destinationRail": "ACH",
    "destinationCurrency": "USD"
  }'
  ```

  ```text MCP theme={null}
  Create an EVM virtual wallet for cus_abc123 that converts USDC on Base to USD by ACH and sweeps to eba_xyz789.

  # Infinite MCP is read-only today — run the CLI tab to execute.
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.infinite.dev/v1/fixed-routes/virtual-wallets" \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "customerId": "cus_abc123",
      "sweepTargetAccountId": "eba_xyz789",
      "chainType": "EVM",
      "sourceRail": "BASE",
      "sourceCurrency": "USDC",
      "destinationRail": "ACH",
      "destinationCurrency": "USD"
    }'
  ```
</CodeGroup>

The response includes the `walletAddress` on the source account -- this is the permanent deposit address for the route. The public fixed-route id is that source account id (`vba_…` for fiat, `inw_…` for crypto), not a separate `fr_` id.

### Response

```json theme={null}
{
  "id": "vba_abc123",
  "customerId": "cus_abc123",
  "type": "FIAT_TO_CRYPTO",
  "status": "ACTIVE",
  "enabled": true,
  "sourceAccount": {
    "id": "vba_abc123",
    "type": "VIRTUAL_BANK",
    "sourceCurrency": "USD",
    "sourceRail": "ACH",
    "depositInstructions": {
      "bankName": "JPMorgan Chase",
      "accountNumber": "1234567890",
      "routingNumber": "021000021"
    }
  },
  "destinationCurrency": "USDC",
  "destinationRail": "BASE",
  "sweepTargetAccountId": "ewa_abc123",
  "createdAt": "2026-01-15T10:30:00Z",
  "updatedAt": "2026-01-15T10:30:00Z"
}
```

The fixed route ID is the same as the source account ID.

### Validating Before Creation

Use [`POST /v1/fixed-routes/validate`](/api-reference/fixed-routes/validate-if-a-fixed-route-can-be-created) to check whether a route configuration is supported for a customer -- currencies, rails, and customer eligibility -- before creating it. For a USD balance sweep, pass `fixedRouteType: "FIAT_TO_FIAT"`. Create infers that type; validate does not. See [Fiat deposits into a USD balance](#fiat-deposits-into-a-usd-balance).

## Managing Routes

* **`enabled`** -- when set to `false` (via [`PATCH /v1/fixed-routes/{id}`](/api-reference/fixed-routes/update-a-fixed-route)), deposits still land on the source account but no longer auto-sweep.
* **Updatable fields** -- fees, refund account, `enabled`, metadata, and `sweepTargetAccountId`. Changing the sweep target keeps the same deposit details -- the new destination must satisfy the same rules as at creation, the change is rejected while the route has transfers in flight, and transfers already created keep the destination they were created with. Pointing a fiat pay-in at a USD balance account flips the route to `FIAT_TO_FIAT` and sets `destinationRail` to `BALANCE`. Developer fees are rejected on that destination, on create and on update. On this route, `sourceRail` / `sourceRails` can be updated in place; the account number stays the same.
* **`refundAccountId`** -- a customer wallet to return funds to if a sweep fails or fees exceed the deposit amount.
* **`fixed_route.disabled` webhook** -- emitted if Infinite disables a route because its source or sweep target account is no longer active.

## Testing in Sandbox

Use [`POST /v1/sandbox/fixed-routes/{id}/simulate-deposit`](/api-reference/sandbox/simulate-a-fixed-route-source-deposit) to simulate a deposit on the source account without moving real funds. Pass the **source account id** (`vba_…` or `inw_…`). The sweep runs end to end, creating a transfer you can track as usual -- including crediting a treasury Infinite wallet used as the sweep target.

On a fiat pay-in that lists `sourceRails`, including a [USD balance sweep](#fiat-deposits-into-a-usd-balance), pass `rail` (`ACH`, `WIRE`, or `SWIFT`) to choose the inbound rail. Omit it to book the stored rail -- ACH when the route accepts ACH and wire. Use a new `reference` for each rail.

ACH, wire, FedWire, and SWIFT simulations include `source.originator` (the inbound remitter) so GET responses and `transfer.*` webhooks match production. Override it with `originator`, or pass `{}` to leave it null. Crypto deposits omit originator unless you set it.

A route with `allowedSenderBanks` does not receive that default remitter. To sweep, pass `senderBank`, or an `originator` that includes a `bankName`, `routingNumber`, or `swiftBic` matching an allowlist entry. A name or account number alone does not match. That request, and one that omits both fields, returns `Fixed route sweep was skipped — route disabled, currency mismatch, sender bank not allowed, or unsupported corridor`.

Do **not** use [`POST /v1/sandbox/accounts/{id}/fund-in`](/api-reference/sandbox/simulate-account-funding) on an `inw_`. That endpoint only credits funding-method accounts (`vba_`, `vwa_`, `pba_`). Fund-in will not credit a treasury wallet or a crypto deposit mailbox, so it cannot be used to test withdrawals from an Infinite wallet. Simulate the inbound on the route, then `POST /v1/transfers` with the vault as source.

## Next Steps

<CardGroup cols={2}>
  <Card title="Transfers" icon="arrow-right-arrow-left" href="/concepts/transfers">
    One-off payments and how swept deposits are tracked
  </Card>

  <Card title="Fixed Routes API Reference" icon="code" href="/api-reference/fixed-routes/create-a-virtual-bank-account-fixed-route">
    Create a virtual bank account fixed route
  </Card>

  <Card title="Balance Accounts" icon="building" href="/guides/balance-accounts">
    Open a USD balance, then pay it out
  </Card>

  <Card title="Simulate a deposit" icon="webhook" href="/api-reference/sandbox/simulate-a-fixed-route-source-deposit">
    Sandbox inbound rail on a fixed-route source account
  </Card>
</CardGroup>


## Related topics

- [Update a fixed route](/api-reference/fixed-routes/update-a-fixed-route.md)
- [Get a fixed route by ID](/api-reference/fixed-routes/get-a-fixed-route-by-id.md)
- [List fixed routes for a customer](/api-reference/customers/list-fixed-routes-for-a-customer.md)
- [Simulate a fixed-route source deposit](/api-reference/sandbox/simulate-a-fixed-route-source-deposit.md)
- [Create a virtual wallet fixed route](/api-reference/fixed-routes/create-a-virtual-wallet-fixed-route.md)
