---
title: "Move your own USDC to another network"
method: POST
path: "/network-transfers"
tags: ["Network Transfers"]
---

# Move your own USDC to another network

`POST /network-transfers`

## Request

- Base URL: `https://developers.ozav.io/v2`
- URL: `https://developers.ozav.io/v2/network-transfers`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

**Not callable** (`coming_soon`). No API key can be issued with the `network_transfers:write` scope
this route requires, so it answers `403 INSUFFICIENT_SCOPE` for every key. The contract below shows
the request and the response.

Moves USDC you already hold from one network to another, in the same wallet. It is not a payment:
origin and destination are the same holder.

- **Answers `202`.** The transfer burns on the origin network under a two-party signing quorum, waits
  for attestation and then mints on the destination, which takes minutes. The response carries an
  `id`; read the state from `GET /network-transfers/{transfer_id}`.
- **One transfer in flight per source network.** A second request while one is in flight answers
  `202` with the transfer already running and `duplicate: true`.
- **The holder comes from your key**, never from the body.
- **The amount** is a decimal integer string in the asset's smallest unit.

## Request body

- object
  - `source_chain` string, required
  - `destination_chain` string, required
  - `asset` string, required: Only USDC crosses today.
  - `amount` string, required: Decimal integer string, smallest unit of `asset`. Never a float.

## Response `202`

Accepted. Read the state with `GET /network-transfers/{transfer_id}`.
`duplicate` is true when a transfer was already in flight for that
source network; the transfer returned is that one.

- object
  - `data` NetworkTransfer: One network transfer of your own balance. `status` is smaller than the internal state machine. There is no "expired": internally that means the attestation window passed and will be requested again, and showing the word to a client would say the money was lost about a transfer that is going fine.
    - `id` string, uuid
    - `status` 'pending' | 'running' | 'submitted' | 'refused' | 'failed' | 'indeterminate': Six states, and the difference between them is money, so it is stated one by one. This mirrors `YieldApplication.status`. `pending` - accepted and queued. Nothing has been attempted yet. `running` - being attempted right now. Wait; do not order again. `submitted` - it left the source network and is on its way. This is not a failure and not a completion: the arrival is confirmed on the destination network, and this field does not yet report it. `refused` - a gate declined it and no money left your balance. `detail` says which gate. Fixing what it names and ordering again is safe. `failed` - it was attempted and nothing left your balance, and that is known. Safe to order again. `indeterminate` - it stopped without knowing whether the amount left your balance. Do not order it again: a second attempt may move the amount twice, and moving between networks cannot be undone. Support has been notified and a person confirms it on the network.
    - `source_chain` string
    - `destination_chain` string
    - `asset` string
    - `amount` string: Decimal integer string, smallest unit of `asset`.
    - `spread_bps` integer, nullable: The spread rate that applies to this transfer, in integer basis points, frozen when it was accepted. It is the rate, not an amount deducted: the transfer moves the full `amount` you requested. Null until then. The network cost is a separate line and is never folded into this.
    - `delivery` 'in_progress' | 'completed' | 'failed' | 'null', nullable: Whether the amount arrived on the destination network. This is a different question from `status`, and both matter. `status` is about the order: accepted, running, submitted, refused, failed, indeterminate. It stops at `submitted` once the amount leaves the source network, and stays there. `delivery` is about the money: `in_progress` while it is crossing, `completed` once it is credited on the destination network, and `failed` if the crossing ended without crediting. `null` means there is no crossing yet: the order has not left. It does not mean the amount failed to arrive.
    - `detail` string, nullable: Why, for `refused`, `failed` and `indeterminate`. Null otherwise. For `indeterminate` it carries the do-not-retry text.
    - `created_at` string, date-time
  - `duplicate` boolean

## Other responses

- `400`: The request is invalid.
- `401`: Missing or invalid API key.
- `403`: The key does not hold `network_transfers:write`. This is the primary gate and it is not self-service: the scope is outside the bootstrap, so `POST /api-keys` cannot grant it. OZAV grants it per account.
- `409`: `NETWORK_TRANSFER_PRICE_NOT_CONFIGURED` : no active pricing row applies to this account, so the transfer is refused rather than priced at zero.
- `503`: `NETWORK_TRANSFER_PRICE_UNREADABLE`: pricing could not be read, so the transfer cannot be accepted. Retry shortly.

---

[API](https://skmtc.dev/ozav/apis/ozav-api.md) - [All operations](https://skmtc.dev/ozav/apis/ozav-api/llms.txt) - [OpenAPI document](https://skmtc.dev/ozav/apis/ozav-api/revisions/4133dc2109d7?raw)
