Skip to main content

Withdrawals

Magnolia supports three withdrawal flows out of a Go Account, all issued through the same sendcoins endpoint. What changes between them is the {coin} path parameter and the destination shape:

Flow{coin}DestinationAmount unit
Crypto on-chain (BTC, ETH, …)ofcbtc / ofctbtc / ofceth / ofctethaddress (on-chain address)smallest unit (e.g., satoshis)
USD → another Go Account (off-chain)ofcusd (prod) / ofctusd (sandbox)walletId (destination Go Account ID)cents
USD → linked bank account (wire)fiatusd (prod) / tfiatusd (sandbox)address set to the bank account IDcents

Pick the row that matches what you're doing. The rest of this guide walks through each.

A Go Account is off-chain — always use the ofc<coin> path

Withdrawals from a Go Account go through the off-chain coin path (ofcbtc, ofctbtc, ofcusd, ofctusd, …), not the on-chain coin path (btc, tbtc, …). Two errors usually mean you picked the wrong path:

  • 404 wallet not found: <goAccountId> — you're calling /api/v2/{onChainCoin}/wallet/{goAccountId}/sendcoins. Switch to the matching ofc<coin>.
  • 400 cannot build on ofc coin, use an ofc token instead — the path is /api/v2/ofc/.... The ofc family requires a specific token (ofctbtc, ofctusd, …); ofc on its own is not a valid path.

Crypto Withdrawal

Send crypto from a Go Account to an external on-chain address.

Check Balance

Before withdrawing, check the available balance.

Trading balance and withdrawal balance are not the same

Use withdrawableBalance for withdrawal checks. A customer can have funds visible in balance or tradableBalance that are not yet available to withdraw.

Unsettled trades are excluded from withdrawableBalance by default

By default, funds from trades that have not yet settled are excluded from withdrawableBalance. Pass includeUnsettledInAvailable=true to include them. A sendcoins call against an unsettled balance will be accepted but cannot be processed until the underlying trade settles, so we recommend gating withdrawals on the default (settled-only) balance.

Send Crypto (sendcoins)

  • POST /api/v2/{coin}/wallet/{goAccountId}/sendcoins
  • address — the destination on-chain address (required for crypto)
  • amount — the amount in the smallest unit (e.g., satoshis for BTC, where 100,000,000 satoshis = 1 BTC)
  • walletPassphrase — the wallet passphrase set during Go Account creation
  • Returns a transfer object with an id and txid you can use to track the withdrawal
Crypto withdrawals require address, not walletId

For on-chain crypto, the destination is always an address. Sending a walletId instead returns a 400. Use the USD Off-Chain Transfer flow when the destination is another Go Account.

Example payload (BTC):

{
"address": "<destination-address>",
"amount": "50000",
"walletPassphrase": "<passphrase>"
}

Example response (abridged):

{
"transfer": {
"id": "<transferId>",
"coin": "ofcbtc",
"wallet": "<goAccountId>",
"txid": "<onChainTxid>",
"state": "confirmed",
"type": "send",
"valueString": "-50000",
"baseValueString": "-50000"
}
}

The full transfer schema is the same one returned by List Transfers — use that page as the reference for every field.

The {coin} path parameter determines which asset to withdraw. For example, use ofcbtc for Bitcoin or ofctbtc for testnet Bitcoin.

Crypto withdrawal amounts use the smallest unit

For BTC, the amount is in satoshis, not whole BTC. If you send "50000", that means 0.0005 BTC.

Fee deduction

If withdrawal fees are configured for your account, the fee is automatically deducted from the withdrawal amount before the transaction is sent. See the Fees guide for details.

List Transfers

Track withdrawals (and deposits) for a wallet.

Get a Single Transfer

Whitelist Policies

Restrict which destinations a Go Account can withdraw to. Outbound transfers that don't match the whitelist trigger the rule's action (e.g. require approval).

  • POST /api/v2/{coin}/wallet/{goAccountId}/policy/rule — add a rule
  • PUT /api/v2/{coin}/wallet/{goAccountId}/policy/rule — update an existing rule
  • DELETE /api/v2/{coin}/wallet/{goAccountId}/policy/rule — remove a rule

There are two whitelist rule types depending on your use case.

coinAddressWhitelist — simple address-based whitelist:

{
"id": "my-rule-id",
"type": "coinAddressWhitelist",
"condition": {
"add": "0xYourAddress..."
},
"action": { "type": "getApproval" }
}

advancedWhitelist — supports whitelisting by address, wallet ID, or enterprise ID, with optional metadata:

{
"id": "my-rule-id",
"type": "advancedWhitelist",
"condition": {
"add": {
"item": "0xYourAddress...",
"type": "address",
"metaData": { "label": "Recipient Wallet" }
}
},
"action": { "type": "getApproval" }
}

The advancedWhitelist entry type field accepts "address", "walletId", or "enterpriseId".

Omit coin on advancedWhitelist rules

It's generally recommended not to set a coin field on advancedWhitelist rules so they apply across all coins in the wallet.


USD Off-Chain Transfer (Go Account → Go Account)

Move USD between two Go Accounts on the platform. There is no on-chain transaction — the transfer is settled internally, so the destination is another Go Account ID rather than an address. This is the right flow for moving USD between customers, between a customer and a treasury account, or for any internal book transfer.

Send USD (sendcoins)

  • POST /api/v2/{coin}/wallet/{goAccountId}/sendcoins
  • {coin}ofctusd in sandbox, ofcusd in production
  • walletId — the destination Go Account ID (required for USD off-chain transfers)
  • amount — the amount in cents (e.g., 100000 = $1,000.00)
  • walletPassphrase — the wallet passphrase set during Go Account creation
  • Returns a transfer object with an id you can use to track the transfer

Example payload:

{
"walletId": "<destination-go-account-id>",
"amount": "100000",
"walletPassphrase": "<passphrase>"
}
The generated sendcoins schema is more generic than this flow

The endpoint page is generated from an upstream sendcoins schema that focuses on generic address-based sends. This API also supports this USD off-chain walletId destination shape for ofctusd / ofcusd.

USD off-chain uses walletId, not address

The destination of an off-chain USD transfer is a Go Account, identified by walletId. Passing an address instead returns a 400. The {coin} path parameter is ofctusd (sandbox) or ofcusd (production) — the same ofcusd* family used elsewhere for USD balances.

USD amounts are in cents

For USD, amount is expressed in the smallest unit (cents). 100000 is $1,000.00, not $100,000. This matches the convention used everywhere else on the platform for fiat amounts.


USD Wire Withdrawal (Go Account → Bank)

Send USD from a Go Account to a linked bank account by wire. Like the off-chain transfer, this also goes through sendcoins — the difference is the {coin} path (fiatusd / tfiatusd instead of ofcusd / ofctusd) and that the address field carries a bank account ID, not an on-chain address or another Go Account ID.

Check for an Approved Bank Account

A wire withdrawal requires an approved bank account on the customer's enterprise.

Only approved bank accounts can receive wire withdrawals

If the bank account is still pending verification, the customer is not ready to wire out yet. Keep bank-linking and withdrawal as separate steps in your UI and backend flow. See Funding for how to add and approve a bank account.

Approval can be asynchronous

A newly created bank account can remain pending for some time before it becomes usable for ACH or wire activity. Plan for a wait/retry loop instead of assuming the account is immediately wire-ready after creation.

Send Wire (sendcoins)

  • POST /api/v2/{coin}/wallet/{goAccountId}/sendcoins
  • {coin}tfiatusd in sandbox, fiatusd in production
  • address — the bank account ID (returned by GET /api/v2/bankaccounts), not a wire-routing string
  • amount — the amount in cents (e.g., 100000 = $1,000.00)
  • walletPassphrase — the wallet passphrase set during Go Account creation
  • Returns a transfer object whose state will initially be confirmationPending

Example payload:

{
"address": "<bank-account-id>",
"amount": "100000",
"walletPassphrase": "<passphrase>"
}

Approve the Wire

A USD wire created via sendcoins lands in confirmationPending and does not move funds until it is approved.

  • PUT /api/v2/wallet/{goAccountId}/wirewithdrawals/{transferId}/confirm
  • Body: { "action": "approve" } (or "reject" to cancel)
  • Returns the updated transfer. On approval, state flips to confirmed and BitGo Trust executes the wire; on rejection, state flips to rejected and the pending wire is canceled.

Example payload (approve):

{ "action": "approve" }

You can also list pending wires for a Go Account before deciding which to confirm:

  • GET /api/v2/wallet/{goAccountId}/wirewithdrawals/confirm
  • Useful query params: state=confirmationPending, coin=fiatusd (or tfiatusd in sandbox), limit, prevId
  • Returns wireWithdrawals[] — each entry has the same id you pass as {transferId} to the confirm endpoint above
Approval is synchronous; settlement is not

The confirm call returns immediately with the updated state, but the actual wire takes 1-3 business days to settle at the receiving bank. After approval, you can keep polling the transfer via List Transfers and Get a Single Transfer to follow it through settlement.

Use the bank account ID as address

For tfiatusd / fiatusd wires, the address field carries the bank account ID from GET /api/v2/bankaccounts — not a routing/account number, IBAN, or any other wire-routing string. The platform derives the actual wire details from the bank account record. Passing a routing string here returns a 400.


Sandbox Testing Notes

A few sandbox-specific behaviors that differ from production. None of them apply to production traffic.

Trades are not auto-settled on sandbox

On sandbox, trades placed through Conversions do not settle automatically, so withdrawableBalance stays at 0 even after the order is filled. A subsequent sendcoins call returns 400 insufficient balance. To test the full trade-then-withdraw flow on sandbox, contact Magnolia support to enable automatic settlement of small trades on the test enterprise. Production trades settle as expected without this step.

Crypto withdrawals on sandbox are not broadcast on-chain

On sandbox, a crypto sendcoins call against a Go Account is accepted and progresses to the platform's pending-approval state, but the underlying transaction is not broadcast to the testnet. The transfer will sit in a pending or unconfirmed state and no txid will appear on a block explorer. This is expected — use the transfer.state returned by the API to drive integration tests rather than waiting for an on-chain confirmation. Off-chain USD flows (ofctusd, tfiatusd) are not affected.


Putting It Together

All three flows go through sendcoins. The branching is in the {coin} you pick and the destination you set.


Next Steps

  • Fees — How withdrawal fees are calculated
  • Funding — Deposit funds back in
  • Conversions — Buy or sell Bitcoin