Convert API
Convert between cryptocurrencies directly from your merchant balance — get a live quote and execute at market price.
The Convert API lets you swap between the currencies held in your merchant balance at the current market price — the same engine that powers the Swap tab in the merchant dashboard, now callable from your backend.
Convert endpoints are signed with your regular API key — the same one used for Payment API requests, not the Payout API key. Executing a convert immediately debits and credits your merchant balance, so treat this key with the same care as any money-moving credential.
Get conversion price
Returns an indicative quote for a conversion at the current market price — the effective rate and the resulting amounts. Nothing is debited or reserved; call it as often as you need before executing.
/v1/convert/pricecurl -X POST https://api.2328.io/api/v1/convert/price \
-H "Content-Type: application/json" \
-H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
-H "project: YOUR_PROJECT_UUID" \
-H "sign: YOUR_HMAC_SIGNATURE"Request parameters
| Field | Type | Required | Description | Values |
|---|---|---|---|---|
from_currency | string | yes | Uppercase source asset code. A usable route and balance must exist; the displayed catalog is not a guarantee of an active market. | |
to_currency | string | yes | Uppercase target asset code; must differ from from_currency and have a tradable direct or bridge route | |
amount | decimal | yes | Amount to convert, greater than 0 | |
amount_type | string | yes | Which side amount refers to |
amount_type=from spends exactly amount of from_currency. amount_type=to receives exactly amount of to_currency.
🟢 200 OK · application/json
{
"state": 0,
"result": {
"success": true,
"from_currency": "BTC",
"to_currency": "USDT",
"amount_type": "from",
"from_amount": "0.01000000",
"to_amount": "947.86690000",
"effective_rate": "94786.69000000",
"from_amount_usd": "947.87",
"to_amount_usd": "947.87"
}
}Response fields
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the quote was computed successfully |
from_currency | string | Source currency |
to_currency | string | Target currency |
amount_type | string | Echoes the request's amount_type |
from_amount | string | Amount that would be debited in from_currency |
to_amount | string | Amount that would be credited in to_currency |
effective_rate | string | Rate applied to this quote — 1 unit of from_currency in to_currency (already includes the platform's pricing) |
from_amount_usd | string | null | USD equivalent of from_amount |
to_amount_usd | string | null | USD equivalent of to_amount |
- The quote is indicative only — the market price can move between the quote and the execute call.
- No balance is debited or reserved by this call.
Execute convert
Executes a conversion at the current market price and updates your merchant balance. There is no separate "commit a quote" step — call this directly with the amount you want to convert.
/v1/convertcurl -X POST https://api.2328.io/api/v1/convert \
-H "Content-Type: application/json" \
-H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
-H "project: YOUR_PROJECT_UUID" \
-H "sign: YOUR_HMAC_SIGNATURE"Idempotency. Repeating the exact same request (same from_currency, to_currency, amount, amount_type) within about a minute of the first call returns the existing conversion instead of creating a second one. Once that window passes, an identical request is treated as a new conversion — don't blindly retry on a timeout without checking the previous result first.
This endpoint is limited to 10 requests per minute per caller — tighter than the general API rate limit — because every call moves real balance.
Request parameters
| Field | Type | Required | Description | Values |
|---|---|---|---|---|
from_currency | string | yes | Uppercase source asset code. A usable route and balance must exist. | |
to_currency | string | yes | Uppercase target asset code; must differ from from_currency and have a tradable direct or bridge route | |
amount | decimal | yes | Amount to convert, greater than 0 | |
amount_type | string | yes | Which side amount refers to |
🟢 200 OK · application/json
{
"state": 0,
"result": {
"id": 12345,
"type": "manual",
"status": "completed",
"from_currency": "BTC",
"to_currency": "USDT",
"from_amount": "0.01000000",
"requested_from_amount": "0.01000000",
"refund_amount": null,
"to_amount": "947.86690000",
"exchange_rate": "94786.69000000",
"fee_amount": "0.00000000",
"from_amount_usd": "947.87",
"to_amount_usd": "947.87",
"processed_at": "2026-01-20T15:30:24Z",
"created_at": "2026-01-20T15:30:22Z"
}
}Response fields
| Field | Type | Description |
|---|---|---|
id | int | Convert order ID assigned by the system |
type | string | Always manual for this API |
status | string | Current status (see Convert statuses below) |
from_currency | string | Source currency |
to_currency | string | Target currency |
from_amount | string | Amount debited in from_currency |
requested_from_amount | string | null | Your originally requested source amount when amount_type = from. null when amount_type = to |
refund_amount | string | null | Portion of the pre-debited amount refunded back to you after a partial fill. null when the order filled completely |
to_amount | string | Amount credited in to_currency |
exchange_rate | string | Rate actually applied to this conversion, 1 unit of from_currency in to_currency (already includes the platform's pricing) |
fee_amount | string | Platform fee charged on this conversion, denominated in from_currency or to_currency depending on trade direction. Already reflected in exchange_rate — shown for transparency |
from_amount_usd | string | null | USD equivalent of from_amount |
to_amount_usd | string | null | USD equivalent of to_amount |
processed_at | string (ISO 8601) | null | When the conversion finished executing. null while still processing |
created_at | string (ISO 8601) | When the conversion order was created |
Convert statuses
| Status | Description |
|---|---|
pending | Created, not yet sent to the market |
processing | Balance locked and the order placed on the market |
completed | Fully executed — to_amount has been credited to your balance |
failed | Could not execute — any pre-debited amount was automatically refunded |
partially_completed | Only for currency pairs with no direct market (routed through an intermediate currency): the first leg completed but the second failed. You're credited the intermediate currency instead of to_currency — convert again from there to reach your original target |
Errors
On failure the response has state: 1 and an error_code — shared by both /v1/convert/price and /v1/convert:
🔴 422 / 400 · application/json
{
"state": 1,
"error_code": "amount_too_small",
"errors": {
"amount": "Amount is too small for this conversion. Please increase the amount and try again."
}
}error_code | HTTP status | Description |
|---|---|---|
validation_failed | 422 | Invalid or missing parameters, or a business-rule rejection (e.g. insufficient balance) — see the errors field for details |
amount_too_small | 422 | amount is below the minimum tradable size for this currency pair |
convert_unavailable | 400 | The conversion couldn't be executed right now (market data unavailable or no route between the two currencies) — retry shortly |
internal_error | 400 | Unexpected server error while processing the request |
Automatic conversion of incoming payments
Auto-convert is a project setting for incoming invoice and static-wallet credits. It is configured in the merchant dashboard, not by adding fields to /v1/payment. Each rule selects one or more source currencies and a target currency.
When conversion completes, payment info and merchant webhooks can include:
{
"payment_amount": "0.14800000",
"merchant_amount": "0.146520000000000000",
"payer_currency": "XMR",
"convert": {
"to_currency": "USDT",
"commission": "0.09000000",
"rate": "323.21000000",
"amount": "47.262015740000000000"
}
}The amount domains are intentionally separate:
payment_amount— what was detected on-chain in the source payment currency;merchant_amount— the net source amount attributable to the merchant before conversion;convert.amount— the amount credited inconvert.to_currency;convert.rateandconvert.commission— the executed conversion result, not a price you should recompute locally.
The absence of convert is meaningful: conversion may not have completed, may not be configured for that source, or may have fallen back to source-currency credit. Never invent a target amount from /exchange-rates or a public market price.
Auto-convert failure and fallback
Conversion is downstream of receiving the blockchain payment. Market availability, minimum order sizes, precision limits, exchange timeouts, and insufficient executable liquidity can delay or prevent conversion.
- Deposits below the global/project minimum bypass the conversion pipeline and credit the source currency.
- Transient failures can be retried asynchronously.
- Large or untradeable deposits can fall back to a source-currency credit after the retry policy is exhausted.
- A payment can therefore be valid even when the desired target-currency conversion did not occur.
Your integration should persist the verified payment first, then reconcile the actual credited currency from payment info, the optional convert block, and merchant balances. Do not block acknowledgement of the payment webhook while waiting on your own analytics or notification systems.
Auto-convert acceptance tests
Test at least: successful direct conversion, bridge/multi-hop conversion, dust below minimum, transient retry, fallback to source currency, underpayment, overpayment, duplicate webhook, missing convert, and reconciliation after an ambiguous timeout.
Manual conversion edge cases
/v1/convert/priceis an indicative preview; market movement can change the execution result.amount_type: fromfixes the source-side request, whileamount_type: torequests a target-side amount. Do not swap the meaning when presenting confirmation UI.- A pair without a direct market can be routed through an intermediate currency. If only one leg completes,
partially_completedreports the intermediate credit. - If an execute call times out, reconcile before retrying. A market order can execute even when its HTTP response is lost.
- Treat
failedas a state to reconcile, not as permission to apply a local compensating balance entry; the platform owns debit/refund accounting.