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} | Destination | Amount unit |
|---|---|---|---|
| Crypto on-chain (BTC, ETH, …) | ofcbtc / ofctbtc / ofceth / ofcteth | address (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 ID | cents |
Pick the row that matches what you're doing. The rest of this guide walks through each.
ofc<coin> pathWithdrawals 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 matchingofc<coin>.400 cannot build on ofc coin, use an ofc token instead— the path is/api/v2/ofc/.... Theofcfamily requires a specific token (ofctbtc,ofctusd, …);ofcon 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.
GET /api/prime/trading/v1/accounts/{goAccountId}/balances- Use
withdrawableBalanceto determine how much can be sent
Use withdrawableBalance for withdrawal checks. A customer can have funds visible in balance or tradableBalance that are not yet available to withdraw.
withdrawableBalance by defaultBy 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}/sendcoinsaddress— 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
transferobject with anidandtxidyou can use to track the withdrawal
address, not walletIdFor 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.
For BTC, the amount is in satoshis, not whole BTC. If you send "50000", that means 0.0005 BTC.
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 /api/v2/{coin}/wallet/{goAccountId}/transfer- Returns a list of transfers sorted by most recent first
- Each transfer includes status, amount, and confirmation details
Get a Single Transfer
GET /api/v2/{coin}/wallet/{goAccountId}/transfer/{transferId}- Use the
transferIdreturned from the sendcoins response
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 rulePUT /api/v2/{coin}/wallet/{goAccountId}/policy/rule— update an existing ruleDELETE /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".
coin on advancedWhitelist rulesIt'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}—ofctusdin sandbox,ofcusdin productionwalletId— 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
transferobject with anidyou can use to track the transfer
Example payload:
{
"walletId": "<destination-go-account-id>",
"amount": "100000",
"walletPassphrase": "<passphrase>"
}
sendcoins schema is more generic than this flowThe 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.
walletId, not addressThe 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.
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.
GET /api/v2/bankaccounts- Look for a bank account with
verificationState: "approved"
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.
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}—tfiatusdin sandbox,fiatusdin productionaddress— the bank account ID (returned byGET /api/v2/bankaccounts), not a wire-routing stringamount— the amount in cents (e.g.,100000= $1,000.00)walletPassphrase— the wallet passphrase set during Go Account creation- Returns a
transferobject whosestatewill initially beconfirmationPending
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,stateflips toconfirmedand BitGo Trust executes the wire; on rejection,stateflips torejectedand 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(ortfiatusdin sandbox),limit,prevId - Returns
wireWithdrawals[]— each entry has the sameidyou pass as{transferId}to the confirm endpoint above
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.
addressFor 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.
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.
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