cryptoinfraDocs

#Withdrawals

Pay out from a user's balance

Open as MarkdownFor AI assistants

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

EndpointSummary
GET /v1/withdrawal-fee-quotePreview a send's exact fees before confirming
GET /v1/withdrawalsList withdrawals, or look one up by idempotency key
POST /v1/withdrawalsRequest a payout
GET /v1/withdrawals/{id}Get a withdrawal
POST /v1/withdrawals/batchBatch 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

NameInTypeRequiredDescription
asset_idquerystringyes—
amountquerystringnoOptional decimal amount; only affects percentage-based platform fees.
to_addressquerystringnoOptional 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_idquerystringnoOptional 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

StatusBodyMeaning
200objectItemized fee preview.
401ErrorMissing, malformed or unknown API key.

200 response body

FieldTypeDescription
asset_idstring—
network_feestringA 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_basestringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
platform_feestringA 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_basestringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
merchant_feestringYour own fee on this send (Profit → Your fees); 0 unless you set one, and 0 for your own balance.
merchant_fee_basestringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
total_feestringA 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_basestringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
dynamicbooleantrue when the network fee was priced live (the normal case); false means the static fallback was charged.
quoted_atstring<date-time>When this quote was priced. Every figure here is computed for this response, never stored.
recipient_pricedbooleantrue when to_address was given and probed, so the figure is exact for that recipient.
network_costobjectWhat the live network figure was built from; null on the static fallback.
network_cost.energy_trx requiredstringTRX for the energy the transfers use (rented
network_cost.bandwidth_trx requiredstringTRX for the bandwidth the transfers burn.
network_cost.activation_trx requiredstringTRX to create the sender's account on its first send; "0" otherwise.
network_cost.trx_usd requirednumberThe TRX price the bill was converted at.
network_cost.transfers requiredintegerOn-chain transfers in the operation (a payout or load and its fee leg = 2).
network_cost.recipient requiredstringWhether the recipient was priced as holding the token already. (one of fresh, existing, unknown)
network_cost.first_send requiredbooleanWhether 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

NameInTypeRequiredDescription
idempotency_keyquerystringnoExact-match lookup. withdrawals is unique on (tenant, key).
fromquerystringnoOnly withdrawals requested on/after this IST day (or instant).
toquerystringnoOnly withdrawals requested on/before this IST day (or before this instant).
statusquerystringnoExact status match. (one of requested, queued, awaiting_approval, signing, broadcast, confirming, confirmed, failed, reversed)
searchquerystringnoCase-insensitive substring match on the destination address or the payer's external_user_id.
limitqueryintegerno— (default 50; min 1; max 200)
offsetqueryintegerno— (default 0; min 0)
sortquerystringno— (one of id, created_at, amount, status, chain, fee; default "id")
dirquerystringno— (one of asc, desc; default "desc")

#Responses

StatusBodyMeaning
200objectMatching withdrawals, plus the total matching the same filter.
401ErrorMissing, malformed or unknown API key.
404—No withdrawal for that idempotency_key.

200 response body

FieldTypeDescription
total requiredintegerWithdrawals matching the filter, ignoring limit/offset.
limit requiredinteger—
offset requiredinteger—
withdrawals requiredobject[]—
withdrawals[].id requiredinteger—
withdrawals[].status requiredstringLifecycle. 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 requiredstringThe 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 requiredstring—
withdrawals[].to_address requiredstring—
withdrawals[].amount requiredstringA 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 requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
withdrawals[].fee requiredstringThe network fee, gas included, charged on top in the sent asset.
withdrawals[].fee_base requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
withdrawals[].fee_asset_id requiredstringThe 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 requiredstringThe 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 requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
withdrawals[].txidstring | null—
withdrawals[].confirmations requiredinteger—
withdrawals[].errorstring | null—
withdrawals[].idempotency_key requiredstring—
withdrawals[].created_atstring<date-time>Present on GET.
withdrawals[].updated_atstring<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

FieldTypeDescription
external_user_id requiredstring(minLength 1; maxLength 255)
asset_id requiredstringAn id from GET /v1/assets.
to_address requiredstringDestination address (validated for the asset's chain).
amount requiredstringDecimal amount the recipient receives (fees are charged on top). (pattern ^\d+(\.\d+)?$)
idempotency_key requiredstring(minLength 1; maxLength 255)
fee_modestringDeprecated 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

StatusBodyMeaning
202WithdrawalPayout accepted — queued, or awaiting_approval when your own threshold parked it for a human.
400ErrorRefused before anything was held. error is one of: validationerror, unknownasset, assetdisabled, invalidamount, amountbelowminimum (a zero or negative amount — a payout has no other minimum), invalidaddress, selftransfer (the recipient is the user's own deposit address — nothing would move but the network fee), invalid_request.
401ErrorMissing, malformed or unknown API key.
404ErrorUnknown externaluserid for this tenant.
409objectTwo 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.
503ErrorNo chain adapter configured for the asset's chain.

202 response body — Withdrawal

FieldTypeDescription
id requiredinteger—
status requiredstringLifecycle. 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 requiredstringThe 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 requiredstring—
to_address requiredstring—
amount requiredstringA 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 requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
fee requiredstringThe network fee, gas included, charged on top in the sent asset.
fee_base requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
fee_asset_id requiredstringThe 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 requiredstringThe 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 requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
txidstring | null—
confirmations requiredinteger—
errorstring | null—
idempotency_key requiredstring—
created_atstring<date-time>Present on GET.
updated_atstring<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

NameInTypeRequiredDescription
idpathintegeryes— (min 1)

#Responses

StatusBodyMeaning
200WithdrawalThe withdrawal (includes timestamps).
401ErrorMissing, malformed or unknown API key.
404ErrorRecord not found (or belongs to another tenant).

200 response body — Withdrawal

FieldTypeDescription
id requiredinteger—
status requiredstringLifecycle. 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 requiredstringThe 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 requiredstring—
to_address requiredstring—
amount requiredstringA 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 requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
fee requiredstringThe network fee, gas included, charged on top in the sent asset.
fee_base requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
fee_asset_id requiredstringThe 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 requiredstringThe 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 requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
txidstring | null—
confirmations requiredinteger—
errorstring | null—
idempotency_key requiredstring—
created_atstring<date-time>Present on GET.
updated_atstring<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

FieldTypeDescription
items requiredobject[]—
items[].external_user_id requiredstring(minLength 1; maxLength 255)
items[].asset_id requiredstringAn id from GET /v1/assets.
items[].to_address requiredstringDestination address (validated for the asset's chain).
items[].amount requiredstringDecimal amount the recipient receives (fees are charged on top). (pattern ^\d+(\.\d+)?$)
items[].idempotency_key requiredstring(minLength 1; maxLength 255)
items[].fee_modestringDeprecated 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

StatusBodyMeaning
202objectPer-item outcomes, in order.

202 response body

FieldTypeDescription
totalinteger—
acceptedinteger—
failedinteger—
resultsobject[]—
results[].idempotency_keystring—
results[].okboolean—
results[].withdrawalobject—
results[].withdrawal.id requiredinteger—
results[].withdrawal.status requiredstringLifecycle. 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 requiredstringThe 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 requiredstring—
results[].withdrawal.to_address requiredstring—
results[].withdrawal.amount requiredstringA 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 requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
results[].withdrawal.fee requiredstringThe network fee, gas included, charged on top in the sent asset.
results[].withdrawal.fee_base requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
results[].withdrawal.fee_asset_id requiredstringThe 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 requiredstringThe 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 requiredstringExact integer amount in the asset's smallest unit. (pattern ^[0-9]+$)
results[].withdrawal.txidstring | null—
results[].withdrawal.confirmations requiredinteger—
results[].withdrawal.errorstring | null—
results[].withdrawal.idempotency_key requiredstring—
results[].withdrawal.created_atstring<date-time>Present on GET.
results[].withdrawal.updated_atstring<date-time>Present on GET.
results[].errorobject—
results[].error.codestring—
results[].error.messagestring—
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"
    }
  ]
}'