# Withdrawals

Pay out from a user's balance

5 endpoints. Generated from `docs/openapi.yaml` v1.0.0.

| Endpoint | Summary |
| --- | --- |
| [`GET /v1/withdrawal-fee-quote`](#get-v1-withdrawal-fee-quote) | Preview a send's exact fees before confirming |
| [`GET /v1/withdrawals`](#get-v1-withdrawals) | List withdrawals, or look one up by idempotency key |
| [`POST /v1/withdrawals`](#post-v1-withdrawals) | Request a payout |
| [`GET /v1/withdrawals/{id}`](#get-v1-withdrawals-id) | Get a withdrawal |
| [`POST /v1/withdrawals/batch`](#post-v1-withdrawals-batch) | Batch payouts — up to 50 sends in one call |

## GET /v1/withdrawal-fee-quote

**Preview a send's exact fees before confirming**  ·  _Auth: bearer API key_  ·  `operationId: withdrawalFeeQuote`

Read-only. **Our live network cost plus a flat platform fee.** `network_fee`
is what this send costs us on-chain, priced now — the energy we rent for
the payout and for our fee's own transfer, the bandwidth, and (with
`user_id`) the one-time activation of a sender address that has never held
TRX — converted at the live TRX price with a cushion and rounded up to the cent.
`platform_fee` is your account's flat schedule from GET /v1/fees (the
platform's own figure is public on GET /v1/pricing); `total_fee` is their sum and is what POST
/v1/withdrawals holds on top of `amount`. Show the user `total_fee`;
the figure returned here is the one charged.

Pass `to_address` so the recipient is priced exactly (an address that
holds none of the token costs about twice one that does — `recipient_priced`
says it was probed) and `user_id` so a first send's activation is
included. `network_cost` says what the figure was built from.

`dynamic: false` means the live TRX price could not be read and the
asset's static fallback (`static_fallback` on GET /v1/pricing) was
charged instead. When only the energy market is unreadable the energy
part is priced at a fixed fallback rate and the quote stays
`dynamic: true`.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `asset_id` | query | `string` | yes | — |
| `amount` | query | `string` | no | Optional decimal amount; only affects percentage-based platform fees. |
| `to_address` | query | `string` | no | Optional recipient. Validated for the asset's chain (400 invalid_address otherwise) and priced exactly: an address holding none of the token costs about twice one that does. Without it the worst case is quoted. |
| `user_id` | query | `string` | no | Optional sender (your user id). The send leaves this user's own address; if it has never held TRX its one-time activation (about 2.5 TRX) is part of the quote. |

#### Responses

| Status | Body | Meaning |
| --- | --- | --- |
| `200` | `object` | Itemized fee preview. |
| `401` | `Error` | Missing, malformed or unknown API key. |

**`200` response body**

| Field | Type | Description |
| --- | --- | --- |
| `asset_id` | `string` | — |
| `network_fee` | `string` | A monetary value as a decimal string. Always paired with a `*_base` field that carries the exact integer base-unit value; compute with the `_base` string. |
| `network_fee_base` | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `platform_fee` | `string` | A monetary value as a decimal string. Always paired with a `*_base` field that carries the exact integer base-unit value; compute with the `_base` string. |
| `platform_fee_base` | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `merchant_fee` | `string` | Your own fee on this send (Profit → Your fees); 0 unless you set one, and 0 for your own balance. |
| `merchant_fee_base` | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `total_fee` | `string` | A monetary value as a decimal string. Always paired with a `*_base` field that carries the exact integer base-unit value; compute with the `_base` string. |
| `total_fee_base` | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `dynamic` | `boolean` | true when the network fee was priced live (the normal case); false means the static fallback was charged. |
| `quoted_at` | `string<date-time>` | When this quote was priced. Every figure here is computed for this response, never stored. |
| `recipient_priced` | `boolean` | true when `to_address` was given and probed, so the figure is exact for that recipient. |
| `network_cost` | `object` | What the live network figure was built from; null on the static fallback. |
| `network_cost.energy_trx` **required** | `string` | TRX for the energy the transfers use (rented |
| `network_cost.bandwidth_trx` **required** | `string` | TRX for the bandwidth the transfers burn. |
| `network_cost.activation_trx` **required** | `string` | TRX to create the sender's account on its first send; "0" otherwise. |
| `network_cost.trx_usd` **required** | `number` | The TRX price the bill was converted at. |
| `network_cost.transfers` **required** | `integer` | On-chain transfers in the operation (a payout or load and its fee leg = 2). |
| `network_cost.recipient` **required** | `string` | Whether the recipient was priced as holding the token already. _(one of `fresh`, `existing`, `unknown`)_ |
| `network_cost.first_send` **required** | `boolean` | Whether the sender's activation was included. |

```json
{
  "asset_id": "string",
  "network_fee": "1.5",
  "network_fee_base": "1500000000",
  "platform_fee": "1.5",
  "platform_fee_base": "1500000000",
  "merchant_fee": "1.5",
  "merchant_fee_base": "1500000000",
  "total_fee": "1.5",
  "total_fee_base": "1500000000",
  "dynamic": true,
  "quoted_at": "2026-08-31T09:15:00.000Z",
  "recipient_priced": true,
  "network_cost": {
    "energy_trx": "string",
    "bandwidth_trx": "string",
    "activation_trx": "string",
    "trx_usd": 0,
    "transfers": 1,
    "recipient": "fresh",
    "first_send": true
  }
}
```

#### Example request

```bash
curl -X GET 'https://api.cryptoinfra.build/v1/withdrawal-fee-quote?asset_id=string' \
  -H 'Authorization: Bearer ck_live_your_key'
```

## GET /v1/withdrawals

**List withdrawals, or look one up by idempotency key**  ·  _Auth: bearer API key_  ·  `operationId: listWithdrawals`

Newest-first, scoped to your merchant. The `idempotency_key` lookup is the
resolution path for an INDETERMINATE create (timeout, dropped connection):
it tells you whether the payout landed, so you never have to re-send one
to find out. Returns 404 when that key has no withdrawal.

Every filter is additive; with none supplied the list is the one it has
always been. `total` is counted through the SAME filter as the rows, so
page with `offset` until you have `total` rows. `from`/`to` accept either a
bare IST calendar day (`YYYY-MM-DD`, both ends included) or a full RFC 3339
instant with an offset, and filter on when the payout was requested.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `idempotency_key` | query | `string` | no | Exact-match lookup. `withdrawals` is unique on (tenant, key). |
| `from` | query | `string` | no | Only withdrawals requested on/after this IST day (or instant). |
| `to` | query | `string` | no | Only withdrawals requested on/before this IST day (or before this instant). |
| `status` | query | `string` | no | Exact status match. _(one of `requested`, `queued`, `awaiting_approval`, `signing`, `broadcast`, `confirming`, `confirmed`, `failed`, `reversed`)_ |
| `search` | query | `string` | no | Case-insensitive substring match on the destination address or the payer's `external_user_id`. |
| `limit` | query | `integer` | no | — _(default `50`; min `1`; max `200`)_ |
| `offset` | query | `integer` | no | — _(default `0`; min `0`)_ |
| `sort` | query | `string` | no | — _(one of `id`, `created_at`, `amount`, `status`, `chain`, `fee`; default `"id"`)_ |
| `dir` | query | `string` | no | — _(one of `asc`, `desc`; default `"desc"`)_ |

#### Responses

| Status | Body | Meaning |
| --- | --- | --- |
| `200` | `object` | Matching withdrawals, plus the total matching the same filter. |
| `401` | `Error` | Missing, malformed or unknown API key. |
| `404` | — | No withdrawal for that idempotency_key. |

**`200` response body**

| Field | Type | Description |
| --- | --- | --- |
| `total` **required** | `integer` | Withdrawals matching the filter, ignoring `limit`/`offset`. |
| `limit` **required** | `integer` | — |
| `offset` **required** | `integer` | — |
| `withdrawals` **required** | `object[]` | — |
| `withdrawals[].id` **required** | `integer` | — |
| `withdrawals[].status` **required** | `string` | Lifecycle. `awaiting_approval` = parked by your own maker-checker threshold (set from the dashboard under Settings → Security): the hold is placed and nothing is signed until an owner or finance member approves it from the dashboard's Payouts page (→ `queued`); a rejection refunds the hold and arrives as `withdrawal.failed` (→ `failed`). `failed` = never broadcast (hold refunded); `reversed` = broadcast then definitively failed on-chain (hold refunded, operator reconciliation). _(one of `requested`, `queued`, `awaiting_approval`, `signing`, `broadcast`, `confirming`, `confirmed`, `failed`, `reversed`)_ |
| `withdrawals[].chain` **required** | `string` | The chain an address or asset is on. TRON is the only network the platform serves: `btc`, `eth`, `bsc` and `sol` were retired — historical addresses on them are never handed out again and nothing scans them. Read the value as an open string rather than a closed list, so a chain added later does not break your client. _(one of `tron`)_ |
| `withdrawals[].asset_id` **required** | `string` | — |
| `withdrawals[].to_address` **required** | `string` | — |
| `withdrawals[].amount` **required** | `string` | A monetary value as a decimal string. Always paired with a `*_base` field that carries the exact integer base-unit value; compute with the `_base` string. |
| `withdrawals[].amount_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `withdrawals[].fee` **required** | `string` | The network fee, gas included, charged on top in the sent asset. |
| `withdrawals[].fee_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `withdrawals[].fee_asset_id` **required** | `string` | The asset `fee` is charged in. Equals `asset_id` on every payout since 2026-09-05. A payout created before that date may say `trx`: an option, since retired, let the network fee be paid from the customer's TRX balance, and such a fee is reported in the coin it was actually paid in. |
| `withdrawals[].platform_fee` **required** | `string` | The flat platform fee from your schedule (GET /v1/fees; the platform's own figure is public on GET /v1/pricing). `fee` is the live network cost of the send. Total held = amount + fee + platform_fee. |
| `withdrawals[].platform_fee_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `withdrawals[].txid` | `string | null` | — |
| `withdrawals[].confirmations` **required** | `integer` | — |
| `withdrawals[].error` | `string | null` | — |
| `withdrawals[].idempotency_key` **required** | `string` | — |
| `withdrawals[].created_at` | `string<date-time>` | Present on GET. |
| `withdrawals[].updated_at` | `string<date-time>` | Present on GET. |

```json
{
  "total": 1,
  "limit": 1,
  "offset": 1,
  "withdrawals": [
    {
      "id": 1,
      "status": "requested",
      "chain": "tron",
      "asset_id": "string",
      "to_address": "string",
      "amount": "1.5",
      "amount_base": "1500000000",
      "fee": "1.5",
      "fee_base": "1500000000",
      "fee_asset_id": "string",
      "platform_fee": "1.5",
      "platform_fee_base": "1500000000",
      "txid": "string",
      "confirmations": 1,
      "error": "string",
      "idempotency_key": "string",
      "created_at": "2026-08-31T09:15:00.000Z",
      "updated_at": "2026-08-31T09:15:00.000Z"
    }
  ]
}
```

#### Example request

```bash
curl -X GET 'https://api.cryptoinfra.build/v1/withdrawals?from=2026-08-01&to=2026-08-31' \
  -H 'Authorization: Bearer ck_live_your_key'
```

## POST /v1/withdrawals

**Request a payout**  ·  _Auth: bearer API key_  ·  `operationId: createWithdrawal`

Holds `amount + fee + platform_fee` from the user's balance and queues the
payout. `amount` is what the recipient receives. Idempotent on `idempotency_key`.
Poll `GET /v1/withdrawals/{id}` or consume the `withdrawal.confirmed` /
`withdrawal.failed` webhooks.

**Approval thresholds.** If you have set an approval threshold for this asset
(dashboard → Settings → Security; owner or finance login), an API-key send at or
above it answers `202` with `status: awaiting_approval`: the hold is placed, nothing
is signed, and it is not queued until an owner or finance member approves it
from the dashboard (→ `queued`). A rejection refunds the hold and arrives as
`withdrawal.failed` with `error: "rejected from the dashboard"`.

#### Request body

`application/json` · required · schema `CreateWithdrawal`

| Field | Type | Description |
| --- | --- | --- |
| `external_user_id` **required** | `string` |  _(minLength `1`; maxLength `255`)_ |
| `asset_id` **required** | `string` | An id from GET /v1/assets. |
| `to_address` **required** | `string` | Destination address (validated for the asset's chain). |
| `amount` **required** | `string` | Decimal amount the recipient receives (fees are charged on top). _(pattern `^\d+(\.\d+)?$`)_ |
| `idempotency_key` **required** | `string` |  _(minLength `1`; maxLength `255`)_ |
| `fee_mode` | `string` | Deprecated and ignored. Until 2026-09-05 this chose which balance paid the network fee (`auto` / `asset` / `trx`); every network fee is now charged in the sent asset. The field is still accepted, with any value, so an older client keeps working — it changes nothing. |

#### Responses

| Status | Body | Meaning |
| --- | --- | --- |
| `202` | `Withdrawal` | Payout accepted — `queued`, or `awaiting_approval` when your own threshold parked it for a human. |
| `400` | `Error` | Refused before anything was held. `error` is one of: validation_error, unknown_asset, asset_disabled, invalid_amount, amount_below_minimum (a zero or negative amount — a payout has no other minimum), invalid_address, self_transfer (the recipient is the user's own deposit address — nothing would move but the network fee), invalid_request. |
| `401` | `Error` | Missing, malformed or unknown API key. |
| `404` | `Error` | Unknown external_user_id for this tenant. |
| `409` | `object` | Two different conditions share this status, and they need different handling — branch on `error`, never on the code alone: - `insufficient_balance` — the user cannot cover amount + fee + platform_fee. Do not retry. - `idempotency_conflict` — this `idempotency_key` was already used with DIFFERENT parameters. Your replay is not the request you think it is. |
| `503` | `Error` | No chain adapter configured for the asset's chain. |

**`202` response body** — `Withdrawal`

| Field | Type | Description |
| --- | --- | --- |
| `id` **required** | `integer` | — |
| `status` **required** | `string` | Lifecycle. `awaiting_approval` = parked by your own maker-checker threshold (set from the dashboard under Settings → Security): the hold is placed and nothing is signed until an owner or finance member approves it from the dashboard's Payouts page (→ `queued`); a rejection refunds the hold and arrives as `withdrawal.failed` (→ `failed`). `failed` = never broadcast (hold refunded); `reversed` = broadcast then definitively failed on-chain (hold refunded, operator reconciliation). _(one of `requested`, `queued`, `awaiting_approval`, `signing`, `broadcast`, `confirming`, `confirmed`, `failed`, `reversed`)_ |
| `chain` **required** | `string` | The chain an address or asset is on. TRON is the only network the platform serves: `btc`, `eth`, `bsc` and `sol` were retired — historical addresses on them are never handed out again and nothing scans them. Read the value as an open string rather than a closed list, so a chain added later does not break your client. _(one of `tron`)_ |
| `asset_id` **required** | `string` | — |
| `to_address` **required** | `string` | — |
| `amount` **required** | `string` | A monetary value as a decimal string. Always paired with a `*_base` field that carries the exact integer base-unit value; compute with the `_base` string. |
| `amount_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `fee` **required** | `string` | The network fee, gas included, charged on top in the sent asset. |
| `fee_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `fee_asset_id` **required** | `string` | The asset `fee` is charged in. Equals `asset_id` on every payout since 2026-09-05. A payout created before that date may say `trx`: an option, since retired, let the network fee be paid from the customer's TRX balance, and such a fee is reported in the coin it was actually paid in. |
| `platform_fee` **required** | `string` | The flat platform fee from your schedule (GET /v1/fees; the platform's own figure is public on GET /v1/pricing). `fee` is the live network cost of the send. Total held = amount + fee + platform_fee. |
| `platform_fee_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `txid` | `string | null` | — |
| `confirmations` **required** | `integer` | — |
| `error` | `string | null` | — |
| `idempotency_key` **required** | `string` | — |
| `created_at` | `string<date-time>` | Present on GET. |
| `updated_at` | `string<date-time>` | Present on GET. |

```json
{
  "id": 1,
  "status": "requested",
  "chain": "tron",
  "asset_id": "string",
  "to_address": "string",
  "amount": "1.5",
  "amount_base": "1500000000",
  "fee": "1.5",
  "fee_base": "1500000000",
  "fee_asset_id": "string",
  "platform_fee": "1.5",
  "platform_fee_base": "1500000000",
  "txid": "string",
  "confirmations": 1,
  "error": "string",
  "idempotency_key": "string",
  "created_at": "2026-08-31T09:15:00.000Z",
  "updated_at": "2026-08-31T09:15:00.000Z"
}
```

#### Example request

```bash
curl -X POST 'https://api.cryptoinfra.build/v1/withdrawals' \
  -H 'Authorization: Bearer ck_live_your_key' \
  -H 'Content-Type: application/json' \
  -d '{
  "external_user_id": "string",
  "asset_id": "string",
  "to_address": "string",
  "amount": "25.5",
  "idempotency_key": "string"
}'
```

## GET /v1/withdrawals/{id}

**Get a withdrawal**  ·  _Auth: bearer API key_  ·  `operationId: getWithdrawal`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `integer` | yes | — _(min `1`)_ |

#### Responses

| Status | Body | Meaning |
| --- | --- | --- |
| `200` | `Withdrawal` | The withdrawal (includes timestamps). |
| `401` | `Error` | Missing, malformed or unknown API key. |
| `404` | `Error` | Record not found (or belongs to another tenant). |

**`200` response body** — `Withdrawal`

| Field | Type | Description |
| --- | --- | --- |
| `id` **required** | `integer` | — |
| `status` **required** | `string` | Lifecycle. `awaiting_approval` = parked by your own maker-checker threshold (set from the dashboard under Settings → Security): the hold is placed and nothing is signed until an owner or finance member approves it from the dashboard's Payouts page (→ `queued`); a rejection refunds the hold and arrives as `withdrawal.failed` (→ `failed`). `failed` = never broadcast (hold refunded); `reversed` = broadcast then definitively failed on-chain (hold refunded, operator reconciliation). _(one of `requested`, `queued`, `awaiting_approval`, `signing`, `broadcast`, `confirming`, `confirmed`, `failed`, `reversed`)_ |
| `chain` **required** | `string` | The chain an address or asset is on. TRON is the only network the platform serves: `btc`, `eth`, `bsc` and `sol` were retired — historical addresses on them are never handed out again and nothing scans them. Read the value as an open string rather than a closed list, so a chain added later does not break your client. _(one of `tron`)_ |
| `asset_id` **required** | `string` | — |
| `to_address` **required** | `string` | — |
| `amount` **required** | `string` | A monetary value as a decimal string. Always paired with a `*_base` field that carries the exact integer base-unit value; compute with the `_base` string. |
| `amount_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `fee` **required** | `string` | The network fee, gas included, charged on top in the sent asset. |
| `fee_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `fee_asset_id` **required** | `string` | The asset `fee` is charged in. Equals `asset_id` on every payout since 2026-09-05. A payout created before that date may say `trx`: an option, since retired, let the network fee be paid from the customer's TRX balance, and such a fee is reported in the coin it was actually paid in. |
| `platform_fee` **required** | `string` | The flat platform fee from your schedule (GET /v1/fees; the platform's own figure is public on GET /v1/pricing). `fee` is the live network cost of the send. Total held = amount + fee + platform_fee. |
| `platform_fee_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `txid` | `string | null` | — |
| `confirmations` **required** | `integer` | — |
| `error` | `string | null` | — |
| `idempotency_key` **required** | `string` | — |
| `created_at` | `string<date-time>` | Present on GET. |
| `updated_at` | `string<date-time>` | Present on GET. |

```json
{
  "id": 1,
  "status": "requested",
  "chain": "tron",
  "asset_id": "string",
  "to_address": "string",
  "amount": "1.5",
  "amount_base": "1500000000",
  "fee": "1.5",
  "fee_base": "1500000000",
  "fee_asset_id": "string",
  "platform_fee": "1.5",
  "platform_fee_base": "1500000000",
  "txid": "string",
  "confirmations": 1,
  "error": "string",
  "idempotency_key": "string",
  "created_at": "2026-08-31T09:15:00.000Z",
  "updated_at": "2026-08-31T09:15:00.000Z"
}
```

#### Example request

```bash
curl -X GET 'https://api.cryptoinfra.build/v1/withdrawals/1' \
  -H 'Authorization: Bearer ck_live_your_key'
```

## POST /v1/withdrawals/batch

**Batch payouts — up to 50 sends in one call**  ·  _Auth: bearer API key_

Each item runs through the same path as a single withdrawal (fees and
approval thresholds apply per item) and carries its own
idempotency key, so re-POSTing a batch after a timeout re-creates
nothing. Items are independent — one bad address never sinks the rest.
An infrastructure failure stops the march mid-batch; retry the whole
batch, the keys de-duplicate.

#### Request body

`application/json` · required

| Field | Type | Description |
| --- | --- | --- |
| `items` **required** | `object[]` | — |
| `items[].external_user_id` **required** | `string` |  _(minLength `1`; maxLength `255`)_ |
| `items[].asset_id` **required** | `string` | An id from GET /v1/assets. |
| `items[].to_address` **required** | `string` | Destination address (validated for the asset's chain). |
| `items[].amount` **required** | `string` | Decimal amount the recipient receives (fees are charged on top). _(pattern `^\d+(\.\d+)?$`)_ |
| `items[].idempotency_key` **required** | `string` |  _(minLength `1`; maxLength `255`)_ |
| `items[].fee_mode` | `string` | Deprecated and ignored. Until 2026-09-05 this chose which balance paid the network fee (`auto` / `asset` / `trx`); every network fee is now charged in the sent asset. The field is still accepted, with any value, so an older client keeps working — it changes nothing. |

#### Responses

| Status | Body | Meaning |
| --- | --- | --- |
| `202` | `object` | Per-item outcomes, in order. |

**`202` response body**

| Field | Type | Description |
| --- | --- | --- |
| `total` | `integer` | — |
| `accepted` | `integer` | — |
| `failed` | `integer` | — |
| `results` | `object[]` | — |
| `results[].idempotency_key` | `string` | — |
| `results[].ok` | `boolean` | — |
| `results[].withdrawal` | `object` | — |
| `results[].withdrawal.id` **required** | `integer` | — |
| `results[].withdrawal.status` **required** | `string` | Lifecycle. `awaiting_approval` = parked by your own maker-checker threshold (set from the dashboard under Settings → Security): the hold is placed and nothing is signed until an owner or finance member approves it from the dashboard's Payouts page (→ `queued`); a rejection refunds the hold and arrives as `withdrawal.failed` (→ `failed`). `failed` = never broadcast (hold refunded); `reversed` = broadcast then definitively failed on-chain (hold refunded, operator reconciliation). _(one of `requested`, `queued`, `awaiting_approval`, `signing`, `broadcast`, `confirming`, `confirmed`, `failed`, `reversed`)_ |
| `results[].withdrawal.chain` **required** | `string` | The chain an address or asset is on. TRON is the only network the platform serves: `btc`, `eth`, `bsc` and `sol` were retired — historical addresses on them are never handed out again and nothing scans them. Read the value as an open string rather than a closed list, so a chain added later does not break your client. _(one of `tron`)_ |
| `results[].withdrawal.asset_id` **required** | `string` | — |
| `results[].withdrawal.to_address` **required** | `string` | — |
| `results[].withdrawal.amount` **required** | `string` | A monetary value as a decimal string. Always paired with a `*_base` field that carries the exact integer base-unit value; compute with the `_base` string. |
| `results[].withdrawal.amount_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `results[].withdrawal.fee` **required** | `string` | The network fee, gas included, charged on top in the sent asset. |
| `results[].withdrawal.fee_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `results[].withdrawal.fee_asset_id` **required** | `string` | The asset `fee` is charged in. Equals `asset_id` on every payout since 2026-09-05. A payout created before that date may say `trx`: an option, since retired, let the network fee be paid from the customer's TRX balance, and such a fee is reported in the coin it was actually paid in. |
| `results[].withdrawal.platform_fee` **required** | `string` | The flat platform fee from your schedule (GET /v1/fees; the platform's own figure is public on GET /v1/pricing). `fee` is the live network cost of the send. Total held = amount + fee + platform_fee. |
| `results[].withdrawal.platform_fee_base` **required** | `string` | Exact integer amount in the asset's smallest unit. _(pattern `^[0-9]+$`)_ |
| `results[].withdrawal.txid` | `string | null` | — |
| `results[].withdrawal.confirmations` **required** | `integer` | — |
| `results[].withdrawal.error` | `string | null` | — |
| `results[].withdrawal.idempotency_key` **required** | `string` | — |
| `results[].withdrawal.created_at` | `string<date-time>` | Present on GET. |
| `results[].withdrawal.updated_at` | `string<date-time>` | Present on GET. |
| `results[].error` | `object` | — |
| `results[].error.code` | `string` | — |
| `results[].error.message` | `string` | — |

```json
{
  "total": 1,
  "accepted": 1,
  "failed": 1,
  "results": [
    {
      "idempotency_key": "string",
      "ok": true,
      "withdrawal": {
        "id": 1,
        "status": "requested",
        "chain": "tron",
        "asset_id": "string",
        "to_address": "string",
        "amount": "1.5",
        "amount_base": "1500000000",
        "fee": "1.5",
        "fee_base": "1500000000",
        "fee_asset_id": "string",
        "platform_fee": "1.5",
        "platform_fee_base": "1500000000",
        "txid": "string",
        "confirmations": 1,
        "error": "string",
        "idempotency_key": "string",
        "created_at": "2026-08-31T09:15:00.000Z",
        "updated_at": "2026-08-31T09:15:00.000Z"
      },
      "error": {
        "code": "string",
        "message": "string"
      }
    }
  ]
}
```

#### Example request

```bash
curl -X POST 'https://api.cryptoinfra.build/v1/withdrawals/batch' \
  -H 'Authorization: Bearer ck_live_your_key' \
  -H 'Content-Type: application/json' \
  -d '{
  "items": [
    {
      "external_user_id": "string",
      "asset_id": "string",
      "to_address": "string",
      "amount": "25.5",
      "idempotency_key": "string"
    }
  ]
}'
```
