Sign in
Converts/Convert API

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.

POST/v1/convert/price
Credentials
RequestPOST/v1/convert/price
curl -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"
Response
Click Try it to see the response here.

Request parameters

FieldTypeRequiredDescriptionValues
from_currencystringyesUppercase source asset code. A usable route and balance must exist; the displayed catalog is not a guarantee of an active market.
to_currencystringyesUppercase target asset code; must differ from from_currency and have a tradable direct or bridge route
amountdecimalyesAmount to convert, greater than 0
amount_typestringyesWhich 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

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

FieldTypeDescription
successbooleanWhether the quote was computed successfully
from_currencystringSource currency
to_currencystringTarget currency
amount_typestringEchoes the request's amount_type
from_amountstringAmount that would be debited in from_currency
to_amountstringAmount that would be credited in to_currency
effective_ratestringRate applied to this quote — 1 unit of from_currency in to_currency (already includes the platform's pricing)
from_amount_usdstring | nullUSD equivalent of from_amount
to_amount_usdstring | nullUSD 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.

POST/v1/convert
RequestPOST/v1/convert
curl -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"
Response
Click Try it to see the response here.

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

FieldTypeRequiredDescriptionValues
from_currencystringyesUppercase source asset code. A usable route and balance must exist.
to_currencystringyesUppercase target asset code; must differ from from_currency and have a tradable direct or bridge route
amountdecimalyesAmount to convert, greater than 0
amount_typestringyesWhich side amount refers to

🟢 200 OK · application/json

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

FieldTypeDescription
idintConvert order ID assigned by the system
typestringAlways manual for this API
statusstringCurrent status (see Convert statuses below)
from_currencystringSource currency
to_currencystringTarget currency
from_amountstringAmount debited in from_currency
requested_from_amountstring | nullYour originally requested source amount when amount_type = from. null when amount_type = to
refund_amountstring | nullPortion of the pre-debited amount refunded back to you after a partial fill. null when the order filled completely
to_amountstringAmount credited in to_currency
exchange_ratestringRate actually applied to this conversion, 1 unit of from_currency in to_currency (already includes the platform's pricing)
fee_amountstringPlatform 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_usdstring | nullUSD equivalent of from_amount
to_amount_usdstring | nullUSD equivalent of to_amount
processed_atstring (ISO 8601) | nullWhen the conversion finished executing. null while still processing
created_atstring (ISO 8601)When the conversion order was created

Convert statuses

StatusDescription
pendingCreated, not yet sent to the market
processingBalance locked and the order placed on the market
completedFully executed — to_amount has been credited to your balance
failedCould not execute — any pre-debited amount was automatically refunded
partially_completedOnly 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

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_codeHTTP statusDescription
validation_failed422Invalid or missing parameters, or a business-rule rejection (e.g. insufficient balance) — see the errors field for details
amount_too_small422amount is below the minimum tradable size for this currency pair
convert_unavailable400The conversion couldn't be executed right now (market data unavailable or no route between the two currencies) — retry shortly
internal_error400Unexpected 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:

JSON
{
  "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 in convert.to_currency;
  • convert.rate and convert.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/price is an indicative preview; market movement can change the execution result.
  • amount_type: from fixes the source-side request, while amount_type: to requests 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_completed reports 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 failed as a state to reconcile, not as permission to apply a local compensating balance entry; the platform owns debit/refund accounting.