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

# Book transfers

# Book transfers

Move **same-currency fiat** between accounts under your partner API key (for example one customer's EUR account to another customer's EUR account). Source is always a customer-owned fiat `account_id` (`pcus_*`). Destination may be another customer's (or legacy entity) account id reachable under the **same** key.

Prerequisite: [Customers quickstart](/customers/quickstart). Banking gate: `products.deposit_account.status=ready`.

## Auth and environments

```bash theme={null}
export BASE_SANDBOX="$BASE_SANDBOX"   # sandbox Partner API base
export BASE_LIVE="$BASE_LIVE"         # live Partner API base
export BASE="$BASE_SANDBOX"
export API_KEY="is_test_…"            # or is_live_… on live
```

```http theme={null}
X-API-Key: <API_KEY>
Content-Type: application/json
```

## Gates

| Gate               | Field                                        | Unlocks                                           |
| ------------------ | -------------------------------------------- | ------------------------------------------------- |
| **Quote identity** | `status` is `approved` (or `active`)         | `customer_id` on quote                            |
| **Banking**        | `products.deposit_account.status` is `ready` | Fiat book transfers from that customer's accounts |

Source `account_id` must belong to `{customer_id}`. Destination `to_account_id` must be under the same API key (may belong to another `pcus_*` or a legacy entity account).

If banking routes are called before `ready`, expect **409** with `data` such as `deposit_account_status` and `reason: banking_profile_incomplete`.

## Happy path

<Steps>
  <Step title="Confirm banking ready">
    Poll `GET /partner/customers/{customer_id}` until `deposit_account.status=ready`
  </Step>

  <Step title="List accounts">
    Resolve source fiat `account_id` and destination `to_account_id` via `GET …/accounts`
  </Step>

  <Step title="Preview">
    `POST …/book-transfers/preview` with `to_account_id` + `amount` → `preview_token`
  </Step>

  <Step title="Confirm">
    `POST …/book-transfers` with `preview_token` + `idempotency_key` (tokens expire quickly; use a fresh preview)
  </Step>

  <Step title="Re-list balances">
    `GET …/accounts` on both sides to confirm balances moved
  </Step>
</Steps>

## 1. Resolve accounts

Use ids returned by **your** API key (do not hardcode from this guide).

```bash theme={null}
export FROM_CUSTOMER="pcus_…"
export TO_CUSTOMER="pcus_…"

curl -sS "$BASE/partner/customers/$FROM_CUSTOMER/accounts" \
  -H "X-API-Key: $API_KEY" | jq '.data'

curl -sS "$BASE/partner/customers/$TO_CUSTOMER/accounts" \
  -H "X-API-Key: $API_KEY" | jq '.data'

export ACCOUNT_ID="<source fiat account_id>"
export TO_ACCOUNT_ID="<destination fiat account_id>"
```

## 2. Preview

Preview and confirm must use the **same** source `account_id`. The `preview_token` seals source + destination internally.

```bash theme={null}
curl -sS -X POST "$BASE/partner/customers/$FROM_CUSTOMER/accounts/$ACCOUNT_ID/book-transfers/preview" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"to_account_id\": $TO_ACCOUNT_ID,
    \"amount\": \"0.50\"
  }" | jq '.'
```

Typical success shape (field names may vary slightly):

```json theme={null}
{
  "status": "success",
  "message": "Book-transfer preview",
  "data": {
    "preview_token": "ep_book_prev_…",
    "currency": "EUR",
    "from_account_id": 18,
    "to_account_id": 21,
    "amount": "0.50",
    "fee": "0.00",
    "receive_amount": "0.50",
    "customer_id": "pcus_…"
  }
}
```

Save `preview_token`. Tokens expire quickly; confirm promptly or re-preview if needed.

## 3. Confirm

<Warning>
  On **live**, confirm moves **real** fiat balances. Prefer sandbox for full preview → confirm loops **only when both accounts already have usable balance** (sandbox self-serve funding is often unavailable). Otherwise validate preview errors (for example insufficient balance → **422**) without confirming.
</Warning>

```bash theme={null}
curl -sS -X POST "$BASE/partner/customers/$FROM_CUSTOMER/accounts/$ACCOUNT_ID/book-transfers" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "preview_token": "<from preview response>",
    "idempotency_key": "book-2026-08-23-001"
  }' | jq '.'
```

Reuse the same `idempotency_key` on retries after network errors. Confirm success typically includes `status: completed` (or submitted), `id`, amounts, `customer_id`, and fee status `final`.

## Business rules

1. **Same currency fiat → fiat** only (e.g. EUR → EUR).
2. **Same partner API key**: destination must be reachable under this key (another `pcus_*` or legacy entity account).
3. **Preview → confirm** on the same source `account_id`; use a fresh preview if the token expired.
4. **Partner-neutral JSON**: error `message` / `data` do not expose upstream PSP names.
5. **Balance:** book transfers require `available >= amount` (exact balance is allowed). This differs from [stablecoin sends](/customers/stablecoin-sends), which need amount **strictly below** `available` (1 minor-unit headroom). Insufficient balance still returns **422** with `data.available`, `data.amount`, and `data.currency` when known.

## Legacy routes

The same operations exist under `/partner/entities/{entity_id}/accounts/.../book-transfers`. Those paths are **legacy**. New integrations should use `pcus_*` customer paths only.

## Errors

| Situation                                     | Typical                                     | Action                                       |
| --------------------------------------------- | ------------------------------------------- | -------------------------------------------- |
| Banking gate not ready                        | `409` + `banking_profile_incomplete`        | Poll `deposit_account`                       |
| Insufficient balance (`amount` > `available`) | `422` + `available` / `amount` / `currency` | Lower amount or fund source account          |
| Preview token expired / wrong account         | `4xx`                                       | Re-preview on the same source `account_id`   |
| Unknown / other-tenant destination            | `404` / `4xx`                               | Use `to_account_id` returned to this API key |

## Next

* [Accounts](/customers/accounts) · [Deposit instructions](/customers/deposit-instructions)
* [Stablecoin sends](/customers/stablecoin-sends)
* [Route cheat sheet](/customers/route-cheat-sheet)
