Guide · Developers

Withdrawing crypto through the API: quote, idempotency and approval

A withdrawal is the one call that moves money out. The API makes it hard to do twice by accident and impossible for a leaked key to approve its own request, as long as you use it the way it was designed.

Updated · 2 min read

Build it with an AI agent

Give this to your AI agent

Paste it into Claude Code, Cursor or any agent that can read a URL. It reads the OpenAPI contract and the signing recipe, and follows the rules for handling real funds.

Read https://www.payaries.com/ai-agents.md and integrate Payaries crypto payments into this project. Follow its rules for handling real funds.
OpenAPI specDocs as markdown

Never paste your API secret into an agent. Keep it in an environment variable.

011. Check what is withdrawable

A confirmed deposit is credited immediately, but it is withdrawable only once the sweep to your Payaries wallet settles. GET /v1/merchants/me/balance separates the two.

FieldMeaning
availableWithdrawable now: credited and physically on your Payaries wallet
pendingSweepCredited, but the sweep hasn't confirmed. A withdrawal against it gets 409
pendingReserved by a withdrawal already in flight
frozenAn admin hold, normally zero

022. Quote it

GET /v1/withdrawals/quote applies the same pricing and checks as the real request and charges nothing.

Quote
GET /v1/withdrawals/quote?chainId=tron-mainnet&asset=USDT_TRC20&amount=1000

{
  "chainId": "tron-mainnet", "asset": "USDT_TRC20", "requestedAmount": "1000",
  "platformFee": "5", "platformFeePaidFrom": "transaction",
  "networkFee": "6.28125", "networkFeeIncluded": false, "gasSponsored": true,
  "netAmount": "988.71875"
}

networkFeeIncluded: true means gas is covered and shown as included. gasSponsored: false means that chain's gas station is off and your own Payaries wallet must hold native currency to pay gas. A too-small amount is refused here with BELOW_MINIMUM_WITHDRAWAL or AMOUNT_TOO_SMALL_FOR_NETWORK_FEE.

033. Create it, idempotently

With an API key the Idempotency-Key header is required (8 to 128 characters of A-Za-z0-9_-:.). Generate one per logical withdrawal, store it with your payout record, and reuse it on every retry. Withdraw to any address; it needn't be pre-registered.

Request
POST /v1/withdrawals
X-Api-Key: <your api key>
X-Timestamp: <unix ms>
X-Signature: <hex HMAC-SHA256>
Idempotency-Key: wd_2026_10_10_0001

{ "chainId": "tron-mainnet", "asset": "USDT_TRC20", "destinationAddress": "T...", "amount": "50.00" }
Response
201 Created
{
  "id": "670...", "chainId": "tron-mainnet", "asset": "USDT_TRC20", "destinationAddress": "T...",
  "requestedAmount": "50.00", "feeAmount": "0.50", "networkFeeAmount": "0", "netAmount": "49.50",
  "status": "processing", "autoApproved": true, "createdVia": "api", "createdAt": "..."
}
  • The same key with the same parameters returns the original withdrawal (200), not a new one.
  • The same key with different parameters is 409.
  • A retry is re-signed with a fresh timestamp but keeps the same Idempotency-Key.
  • requestedAmount is reserved from available only; netAmount, after fees, is what is sent.

044. Approval: who decides

Created viaApproval
A signed-in dashboard sessionExecutes at once. It requires 2FA on the account and a fresh single-use code, which is the approval
An API key, below the auto-approve threshold for that chain and assetautoApproved: true, executes immediately
An API key, at or above the thresholdstatus: pending_approval until a person approves or rejects it in the dashboard

Approving, rejecting and setting the threshold are dashboard-only; an API key gets 403. If an AI agent or any automated process holds a key, set a low threshold so its withdrawals wait for you, or give it no withdrawal tool at all. Chain and asset pairs with no threshold of your own use the platform's default ceiling.

055. Follow it

The lifecycle is pending_approval → processing → broadcast → confirmed, or → rejected, cancelled or failed. A withdrawal.status_changed webhook fires on every transition. Withdrawal webhooks have no stored status or resend endpoint, so if you suspect a miss, poll GET /v1/withdrawals/:id.

  • POST /v1/withdrawals/:id/cancel works only while pending_approval, and releases the reservation.
  • If the chain's gas station is paused, a withdrawal waits in processing for up to 24 hours, then fails and releases your reservation.
  • Bitcoin withdrawals aren't supported yet; btc-mainnet is refused with 400 before anything is reserved.
  • Transactions are irreversible once confirmed. Double-check the destination address and chain.

Questions

Is an Idempotency-Key required to create a withdrawal?
Yes, with an API key. The header is required, 8 to 128 characters. The same key with the same parameters returns the original withdrawal; the same key with different parameters returns 409.
Can an API key approve its own withdrawal?
No. Approving, rejecting and setting the auto-approve threshold are dashboard-session actions. An API key gets 403, so a leaked key can't approve its own request or raise its own ceiling.
Why does my withdrawal return 409 Insufficient balance when my invoices are paid?
A withdrawal is debited from the available balance only. Funds from a freshly confirmed invoice sit in pendingSweep until the sweep to your Payaries wallet settles, and a frozen amount is excluded as well.

Next

Create a test key and run a real invoice

Test keys work immediately, with no approval step. Payaries runs on real mainnet networks, so use a small real payment to test.