> ## 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.

# Transfers

> One-off payments, shared deposit instructions, and when to use a unique fixed route

Transfers are the core payment processing component of Infinite, handling movement of funds across fiat and crypto rails with built-in compliance.

## Overview

A transfer moves money from a source account to a destination account, optionally converting between currencies. Transfers support:

* **Fiat to crypto** -- Deposit fiat, receive stablecoins

* **Crypto to fiat** -- Deposit stablecoins, receive fiat via SWIFT, SEPA, ACH, etc.

* **Crypto to crypto** -- Move between chains or stablecoins. Same-asset USDC/USDT across supported chains settles at par — see [Same-asset bridging](/guides/same-asset-bridging)

* **Fiat to fiat** -- Cross-border fiat payments

Transfers are one-off: you specify the amount and destination per payment. For a standing deposit address that sweeps every incoming deposit automatically, see [Fixed Routes](/concepts/fixed-routes).

## Creating a Transfer

Every transfer requires a source, destination, amount, and payment reason:

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

  response = requests.post(
      'https://api.infinite.dev/v1/transfers',
      headers={
          'Authorization': 'Bearer sk_live_...',
          'Content-Type': 'application/json',
      },
      json={
          'onBehalfOf': 'cus_abc123',
          'amount': {
              'currency': 'USDC',
              'rawValue': '100000000',
          },
          'source': {
              'currency': 'USDC',
              'paymentRail': 'ETHEREUM',
              'accountId': 'inw_source123',
          },
          'destination': {
              'currency': 'USD',
              'paymentRail': 'ACH',
              'accountId': 'eba_dest456',
          },
          'paymentReason': 'PAYMENT_FOR_GOODS_AND_SERVICES',
      },
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch('https://api.infinite.dev/v1/transfers', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer sk_live_...',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      onBehalfOf: 'cus_abc123',
      amount: {
        currency: 'USDC',
        rawValue: '100000000',
      },
      source: {
        currency: 'USDC',
        paymentRail: 'ETHEREUM',
        accountId: 'inw_source123',
      },
      destination: {
        currency: 'USD',
        paymentRail: 'ACH',
        accountId: 'eba_dest456',
      },
      paymentReason: 'PAYMENT_FOR_GOODS_AND_SERVICES',
    }),
  });
  ```

  ```bash CLI theme={null}
  export INFINITE_API_KEY=sk_live_...
  infinite transfers create \
    --customer cus_abc123 \
    --amount 100000000 \
    --amount-currency USDC \
    --source-currency USDC \
    --source-rail ETHEREUM \
    --source-account inw_source123 \
    --dest-account eba_dest456 \
    --dest-currency USD \
    --dest-rail ACH \
    --reason PAYMENT_FOR_GOODS_AND_SERVICES
  ```

  ```text MCP theme={null}
  Send 100 USDC from inw_source123 on Ethereum to eba_dest456 as USD by ACH on behalf of cus_abc123, for goods and services.

  # Via MCP this needs a write-capable session (agent key for payments, org key for onboarding — see /guides/mcp); otherwise run the CLI tab.
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.infinite.dev/v1/transfers" \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "onBehalfOf": "cus_abc123",
      "amount": {
        "currency": "USDC",
        "rawValue": "100000000"
      },
      "source": {
        "currency": "USDC",
        "paymentRail": "ETHEREUM",
        "accountId": "inw_source123"
      },
      "destination": {
        "currency": "USD",
        "paymentRail": "ACH",
        "accountId": "eba_dest456"
      },
      "paymentReason": "PAYMENT_FOR_GOODS_AND_SERVICES"
    }'
  ```
</CodeGroup>

### Key Fields

| Field | Description |
| - | - |
| `onBehalfOf` | Customer ID the transfer is for |
| `amount` | Amount as `MoneyInput` (`{ currency, rawValue }`) in minor units |
| `source` | Source account with `currency`, `paymentRail`, and optional `accountId` |
| `destination` | Destination account with `currency`, `paymentRail`, and `accountId` |
| `paymentReason` | Compliance reason code (e.g. `PAYMENT_FOR_GOODS_AND_SERVICES`, `TRADE_SETTLEMENT`) -- see the create-transfer API reference for the full list |
| `quoteId` | Optional -- reference a held quote to lock in an exchange rate |
| `confirm` | Optional -- set `true` to submit immediately, otherwise creates as `DRAFT` |

### Response

```json theme={null}
{
  "id": "tfr_xyz789",
  "sourceCustomerId": "cus_abc123",
  "status": "PENDING",
  "source": {
    "currency": "USDC",
    "paymentRail": "ETHEREUM",
    "accountId": "inw_source123"
  },
  "destination": {
    "currency": "USD",
    "paymentRail": "ACH",
    "accountId": "eba_dest456"
  },
  "sourceAmount": {
    "currency": "USDC",
    "exponent": 6,
    "value": "100000000",
    "displayValue": "100.00"
  },
  "destinationAmount": null,
  "createdAt": "2026-01-15T10:30:00Z"
}
```

## Transfer Status

| Status | Description |
| - | - |
| `DRAFT` | Transfer created but not yet submitted |
| `IN_REVIEW` | Under compliance review |
| `PENDING` | Submitted, awaiting processing |
| `PROCESSING` | Actively being processed |
| `COMPLETED` | Successfully completed |
| `FAILED` | Transfer failed before funds were received |
| `CANCELLED` | Cancelled before completion |
| `REVERSED` | Funds were returned |

If funds have already been received and the transfer needs manual review before it can be settled or returned, the transfer remains `IN_REVIEW` externally until it reaches `COMPLETED` or `REVERSED`.

## Transfer Legs

Transfers are broken into **legs** (steps) that represent each segment of the payment flow. For example, a crypto-to-fiat transfer might have:

1. **Crypto deposit leg** -- Customer deposits stablecoins
2. **Conversion leg** -- Stablecoins converted to fiat
3. **Fiat payout leg** -- Fiat sent to bank account

Each leg has its own status (`PENDING`, `IN_PROGRESS`, `COMPLETED`, `FAILED`, `SKIPPED`) and is visible in the transfer response under the `legs` array.

## Deposit Instructions

When a transfer requires the customer to deposit funds (e.g. crypto or fiat), the response includes `depositInstructions`.

One-off `POST /v1/transfers` onramps often **reuse the same bank coordinates** for a customer. There is no unique memo or transaction reference on those fiat instructions, so concurrent deposits cannot be told apart by bank details -- matching is by amount. Parallel onramps at the same amount will collide.

For concurrent or recurring USD→crypto, create a **unique virtual bank account per destination** with [`POST /v1/fixed-routes/virtual-bank-accounts`](/concepts/fixed-routes) and `sweepTargetAccountId` set to that wallet. Each route gets its own account number, so the deposit identifies itself. Listen for `transfer.*` webhooks on the auto-created sweep -- no per-deposit create-transfer needed. The same pattern applies to crypto offramps via [`POST /v1/fixed-routes/virtual-wallets`](/concepts/fixed-routes) (unique deposit address per destination).

### Crypto Deposit

```json theme={null}
{
  "depositInstructions": {
    "type": "crypto",
    "paymentRail": "ETHEREUM",
    "toAddress": "0x1234567890abcdef1234567890abcdef12345678",
    "amount": {
      "currency": "USDC",
      "exponent": 6,
      "value": "100000000",
      "displayValue": "100.00"
    }
  }
}
```

### Fiat Deposit

```json theme={null}
{
  "depositInstructions": {
    "type": "fiat",
    "paymentRail": "ACH",
    "bankName": "JPMorgan Chase",
    "accountNumber": "1234567890",
    "routingNumber": "021000021",
    "beneficiaryName": "Infinite Inc",
    "amount": {
      "currency": "USD",
      "exponent": 2,
      "value": "10000",
      "displayValue": "100.00"
    }
  }
}
```

## Amounts

All amounts in the API use the `MoneyOutput` format:

```json theme={null}
{
  "currency": "USD",
  "exponent": 2,
  "value": "10000",
  "displayValue": "100.00"
}
```

* `value` is the amount in the smallest unit (cents, base units)
* `exponent` is the number of decimal places
* `displayValue` is the human-readable decimal

When creating transfers, use `MoneyInput`:

```json theme={null}
{
  "currency": "USD",
  "rawValue": "10000"
}
```

## Tracking Transfers

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

  response = requests.get(
      'https://api.infinite.dev/v1/transfers/tfr_xyz789',
      headers={'Authorization': 'Bearer sk_live_...'},
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    'https://api.infinite.dev/v1/transfers/tfr_xyz789',
    {
      headers: {
        Authorization: 'Bearer sk_live_...',
      },
    },
  );
  ```

  ```bash CLI theme={null}
  export INFINITE_API_KEY=sk_live_...
  infinite transfers get tfr_xyz789
  ```

  ```text MCP theme={null}
  Show me transfer tfr_xyz789.
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.infinite.dev/v1/transfers/tfr_xyz789" \
    -H "Authorization: Bearer sk_live_..."
  ```
</CodeGroup>

## Webhooks

Subscribe to transfer events for real-time updates:

* `transfer.created` -- Transfer created
* `transfer.processing` -- Transfer is being processed
* `transfer.completed` -- Transfer completed successfully
* `transfer.failed` -- Transfer failed before funds were received
* `transfer.cancelled` -- Transfer was cancelled
* `transfer.refund_initiated` -- Refund was initiated
* `transfer.refund_completed` -- Refund completed

See the [Webhooks guide](/guides/webhooks) for setup instructions.

## Next Steps

<CardGroup cols={2}>
  <Card title="Quotes and Transfers" icon="calculator" href="/guides/quotes-and-transfers">
    Learn about quoting and rate locking
  </Card>

  <Card title="SWIFT Payouts" icon="globe" href="/guides/swift-payouts">
    Crypto-to-fiat via SWIFT wire transfers
  </Card>

  <Card title="Same-asset bridging" icon="arrows-left-right" href="/guides/same-asset-bridging">
    USDC or USDT from one chain to another at par
  </Card>
</CardGroup>


## Related topics

- [Quotes and Transfers](/guides/quotes-and-transfers.md)
- [List transfers](/api-reference/transfers/list-transfers.md)
- [Update transfer details](/api-reference/transfers/update-transfer-details.md)
- [Submit transfer for processing](/api-reference/transfers/submit-transfer-for-processing.md)
- [Request transfer cancellation](/api-reference/transfers/request-transfer-cancellation.md)
