## Fund API Documentation

Fund endpoints let an organization withdraw funds, query withdrawal status and fees, and read its available USDT and USDC balances.

All Fund API prefix:

`/v2/funds`

### Authentication and Permissions

All endpoints use HMAC-SHA256 authentication with **`Date`**, **`Digest`** when the request has a body, and **`Authorization`** with **`keyId`**. For a GET request with query parameters, the signed request path must include the complete query string. See [Chapter 4: Authorization and Security Mechanisms](/docs/en/4-authorization).

| Permission | Endpoints |
|  --- | --- |
| `fund.withdraw` | `POST /v2/funds/withdraw`, `GET /v2/funds/withdraw/status`, `GET /v2/funds/withdraw/fees`, `GET /v2/funds/balances` |


> **IP whitelist:** API keys that include `fund.withdraw` must be configured with a non-empty IP whitelist when created or updated in the merchant dashboard.


### Withdrawal Idempotency

`request_id` is optional when creating a withdrawal:

- When omitted, Infini generates the withdrawal request ID and preserves the existing withdrawal behavior. Separate caller retries without a shared ID are not idempotent.
- When supplied, it must be a UUID and is used as the idempotency key.
- When the same merchant retries with the same `request_id`, the API returns the previously created withdrawal information with `is_duplicate: true`.
- Use one `request_id` for exactly one logical withdrawal. Do not reuse it for a different amount, chain, token, or destination address.


### Response Envelope

After gateway authentication, application responses use the standard `code` / `message` / `data` envelope. Business success is indicated by `code === 0`; a non-zero `code` indicates an error and `message` carries the details. Some downstream business failures can be returned with HTTP 200, a non-zero `code`, and `data: null`.

HMAC or gateway validation failures can be rejected before the request reaches the application and may return a gateway response such as `{"message":"client request can't be validated"}` instead of the application envelope. Clients should check the HTTP status first, then check `code` whenever the envelope is present.

### Withdraw Organization Funds

**POST** /v2/funds/withdraw

Creates a withdrawal from the authenticated organization to an external wallet.

- **Required permission:** `fund.withdraw`


#### Request Body

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| request_id | string (UUID) | No | Idempotency key. When omitted, Infini generates one |
| chain | string | Yes | Public chain name, for example `ETHEREUM` or `ARBITRUM` |
| token_type | string | Yes | Token type, for example `USDT` or `USDC` |
| amount | string | Yes | Positive decimal amount. Digits after the sixth decimal place are truncated without rounding; the resulting amount must be at least `1` and greater than the withdrawal fee |
| wallet_address | string | Yes | Destination address valid for the selected chain |
| note | string | No | Optional withdrawal note |


Use **`GET /v2/funds/withdraw/fees`** as the current source of truth for enabled chain/token pairs and their fees. Chain and token names in examples are uppercase.

#### Request Example

```json
{
  "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
  "chain": "ETHEREUM",
  "token_type": "USDT",
  "amount": "10.00",
  "wallet_address": "0x5f716e5775b18409917e2a2f0762d29d6c385cb0",
  "note": "Treasury withdrawal"
}
```

#### Response Example

```json
{
  "code": 0,
  "message": "",
  "data": {
    "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
    "status": "pending",
    "is_duplicate": false
  }
}
```

For an idempotent retry, `data.is_duplicate` is `true` and `data.status` is the existing withdrawal's current status.

### Get Withdrawal Status

**GET** /v2/funds/withdraw/status?request_id={request_id}

Returns the current state and amounts for a withdrawal owned by the authenticated organization.

- **Required permission:** `fund.withdraw`
- The HMAC signing path must include `?request_id={request_id}`.


#### Query Parameters

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| request_id | string (UUID) | Yes | Withdrawal request ID |


#### Response Example

```json
{
  "code": 0,
  "message": "",
  "data": {
    "request_id": "e94b4e88-36c2-4550-907e-839742cf5fae",
    "status": "completed",
    "amount": "11",
    "fee": "0.1",
    "actual_amount": "10.9",
    "transaction_hash": "0xabc123",
    "chain": "ETHEREUM",
    "token_type": "USDT"
  }
}
```

#### Response Fields

| Field | Type | Description |
|  --- | --- | --- |
| request_id | string | Withdrawal request ID |
| status | string | Current withdrawal status |
| amount | string | Original withdrawal amount |
| fee | string | Withdrawal fee denominated in `token_type` |
| actual_amount | string | On-chain amount after the fee |
| transaction_hash | string | On-chain transaction hash; empty before submission |
| chain | string | Public chain name |
| token_type | string | Token type |


#### Status Values

| Status | Description |
|  --- | --- |
| `pending` | Withdrawal created and waiting for submission or review |
| `processing` | Transaction submitted and waiting for confirmation |
| `completed` | Withdrawal completed |
| `failed` | Withdrawal failed |


### List Withdrawal Fees

**GET** /v2/funds/withdraw/fees

Returns currently enabled public chain/token pairs and their fees. Fees are dynamic; clients should query this endpoint instead of hard-coding a fee table.

- **Required permission:** `fund.withdraw`


#### Response Example

```json
{
  "code": 0,
  "message": "",
  "data": {
    "fees": [
      {
        "chain": "ARBITRUM",
        "token_type": "USDC",
        "withdraw_fee": "0.5"
      },
      {
        "chain": "ETHEREUM",
        "token_type": "USDT",
        "withdraw_fee": "2"
      }
    ]
  }
}
```

Each `withdraw_fee` is denominated in the corresponding `token_type`.

### Get Available Balances

**GET** /v2/funds/balances

Returns only the authenticated organization's USDT and USDC `AVAILABLE_BALANCE` values.

- **Required permission:** `fund.withdraw`


#### Response Example

```json
{
  "code": 0,
  "message": "",
  "data": {
    "available_balance_usdt": "120.50",
    "available_balance_usdc": "75.25"
  }
}
```

When the organization has no balance entry for one of the tokens, its value is returned as `"0"`.