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

# Send transfers

> Move value publicly or privately with one endpoint: standard ERC-20 transfers and FHE-encrypted confidential transfers.

`POST /transfer` moves money out of a custodial wallet on either rail. One
field (`type`) decides whether the amount is public or encrypted.

<Columns cols={2}>
  <Card title="type: &#x22;public&#x22;" icon="eye" iconType="duotone">
    A standard ERC-20 transfer of the test stablecoin. Amount visible on-chain,
    cheapest and fastest.
  </Card>

  <Card title="type: &#x22;private&#x22;" icon="eye-slash" iconType="duotone">
    A cAROUSD `confidentialTransfer`. The amount is FHE-encrypted; observers
    see the transfer, never the value.
  </Card>
</Columns>

## Make the call

<CodeGroup>
  ```bash Private (cAROUSD) theme={null}
  curl -s "$BASE/transfer" \
    -H "Authorization: Bearer $KEY" \
    -H "content-type: application/json" \
    -d "{\"walletId\":\"$WALLET\",\"to\":\"0x52aE…9021\",\"amount\":\"25\",\"type\":\"private\"}"
  ```

  ```bash Public (stablecoin) theme={null}
  curl -s "$BASE/transfer" \
    -H "Authorization: Bearer $KEY" \
    -H "content-type: application/json" \
    -d "{\"walletId\":\"$WALLET\",\"to\":\"0x52aE…9021\",\"amount\":\"25\",\"type\":\"public\"}"
  ```
</CodeGroup>

```json theme={null}
{
  "ok": true,
  "data": {
    "transaction": {
      "id": "cktxn0003…",
      "type": "PRIVATE_TRANSFER",
      "status": "CONFIRMED",
      "amount": "25.00",
      "token": "cAROUSD",
      "txHash": "0x9ac2…",
      "fromAddress": "0x8Ba1…BA72",
      "toAddress": "0x52aE…9021",
      "walletId": "ckwlt0001…",
      "createdAt": "2026-08-13T09:45:00.000Z",
      "updatedAt": "2026-08-13T09:45:38.000Z"
    }
  }
}
```

## Request fields

| Field      | Required | Notes                                                                                                                                                                |
| ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `walletId` | ✓        | Must be a **custodial** wallet; the sandbox signs with its key. External wallets are rejected with `400`.                                                            |
| `to`       | ✓        | Recipient address (`0x…`, checksummed). Counterparties often share theirs as a [.nota address card](/concepts/nota-files); the address inside is what you pass here. |
| `amount`   | ✓        | Decimal string in human units, e.g. `"25"` or `"25.50"`.                                                                                                             |
| `type`     | ✓        | `"public"` or `"private"`.                                                                                                                                           |

The sending wallet needs a sufficient balance **on the matching rail**:
public stablecoin for `public`, cAROUSD for `private` (mint first if needed),
plus ETH for gas.

## Privacy characteristics

For `private` transfers, the amount is encrypted client-side *of the chain*;
that is, the sandbox produces the encrypted input and proof server-side and
submits it. On-chain observers see sender, recipient, and the fact of a
transfer; the value stays ciphertext. The recipient (or the sandbox, for
custodial recipients) decrypts their updated balance through the relayer.

<Note>
  Private transfers cost more gas and take a bit longer than public ones;
  encrypted inputs are bigger and verification is heavier. Budget for that in
  UX, not in correctness.
</Note>

## Failure modes

| Response                                  | Meaning                                                                            |
| ----------------------------------------- | ---------------------------------------------------------------------------------- |
| `400 invalid_recipient`                   | `to` is not a valid address.                                                       |
| `400 invalid_amount` / `validation_error` | Malformed amount or insufficient balance on the chosen rail.                       |
| `400` (non-custodial)                     | `walletId` refers to an external wallet.                                           |
| `502 transfer_failed`                     | The transaction reverted on-chain; the returned transaction row carries the error. |

Like all money movement, transfers block up to \~90 s and may return `PENDING`;
[poll to completion](/guides/track-transactions).
