Skip to main content
A balance swap converts part of your store balance from one asset into another without leaving SouthPay. Use it to consolidate deposits into a single stablecoin before a payout, or to move off a chain whose network fees you no longer want to pay. Swaps are executed by an external routing provider, so the amount you receive is not known in advance. Quote first, then create. Paths are relative to https://api.southpay.io/api/v2.

Authentication

Any valid API key or OAuth token can call these endpoints. Balance swaps are not scope-checked, so no specific scope is required. Publishable keys are still rejected.
Creating a swap runs a step-up MFA check. That check is a no-op for API keys and OAuth tokens; it only gates dashboard sessions, which return 403 mfa_required without two-factor enabled.

Quote a swap

POST /balance_swaps/quote. No idempotency key, creates nothing.
string
required
The asset to sell, for example ETH_USDC.
string
required
The asset to receive. Must differ from source_asset_id.
string
required
Amount to sell, in atomic units of source_asset_id. Must be positive.
string
required
What the route currently expects to return, in atomic units of buy_asset_id.
string
required
The floor after slippage: estimated * (10000 - slippage_bps) / 10000. Size your downstream payout against this, not against the estimate.
integer
required
Slippage tolerance in basis points.
integer
required
Divisor exponent for the two atomic amounts above.
A quote carries no expiry field and reserves nothing. Treat it as indicative and re-quote if more than a few seconds pass.

Create a swap

POST /balance_swaps. Idempotency-Key is required. Same three parameters as the quote, sent at the top level.
string
required
The asset to sell.
string
required
The asset to receive.
string
required
Atomic units of source_asset_id. Must be positive and available in your balance.
Swaps are irreversible and execute at the market rate at the time of fill, which can differ from the quote. Replaying the same Idempotency-Key returns the original swap, still with status 201, not 200. A new key executes a second swap.

Response fields

string
required
Bare UUID.
string
required
pending, submitted, completed or failed.
string
required
Amount debited from source_asset_id.
string | null
Estimate recorded at creation.
string | null
What actually arrived. Null until the swap completes. This is the number to reconcile against.
string | null
Routing provider that executed the swap.
string | null
On-chain hash, once broadcast.
boolean
required

Retrieve a swap

GET /balance_swaps/{id}. Returns a bare swap object. Unknown id returns 404 {"error": "Balance swap not found"}. There are no swap webhooks. Poll this endpoint until status is completed or failed.

List swaps

GET /balance_swaps. Newest first.
string
pending, submitted, completed or failed.
integer
default:"1"
integer
default:"25"
Between 1 and 100.
Response 200
Balance swaps use the pagination envelope, matching payouts. Payments and billing use meta instead.

Lifecycle

Auto-swap is the automatic version of this, applied to incoming payments rather than to your standing balance. It is configured in the dashboard, not over the API, and its outcome surfaces as auto_swap_status and auto_swap_detail on a payment intent. Auto-swap targets are limited to ETH_USDC and ETH_USDT; manual swaps are not.

Errors

Only the last two carry a code. The rest use the flat {"error": "<message>"} envelope with no machine-readable identifier, so branch on the status code and the endpoint rather than on the message text.
Last modified on August 21, 2026