# Convert API

> Конвертация криптовалют напрямую из баланса мерчанта — живая котировка и исполнение по рыночной цене.

Convert API позволяет обменивать валюты внутри баланса вашего мерчанта по текущей рыночной цене — тот же движок, что работает во вкладке **Обмен** в личном кабинете, теперь доступный из вашего бэкенда.

> **WARNING:** Ручки Convert подписываются вашим **обычным API-ключом** — тем же, что используется для запросов [Payment API](/docs/payments), а **не** ключом Payout. Исполнение конверта сразу списывает и зачисляет баланс мерчанта, поэтому относитесь к этому ключу с той же осторожностью, что и к любому денежному credential.

## Получить цену конверта

Возвращает индикативную котировку конверта по текущей рыночной цене — эффективный курс и итоговые суммы. Ничего не списывается и не резервируется; вызывайте сколько угодно раз перед исполнением.

`POST /v1/convert/price`

### Параметры котировки

| Поле | Тип | Обязательно | Описание | Значение |
|------|-----|--------------|----------|----------|
| `from_currency` | string | да | Валюта, из которой конвертируем | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | да | Валюта, в которую конвертируем. Должна отличаться от `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | да | Сумма конвертации, больше `0` |  |
| `amount_type` | string | да | К какой стороне относится `amount` | `from`, `to` |

> **INFO:** `amount_type=from` — потратить ровно `amount` в `from_currency`. `amount_type=to` — получить ровно `amount` в `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"
  }
}
```

#### Поля ответа

| Поле | Тип | Описание |
|------|-----|----------|
| `success` | boolean | Успешно ли рассчитана котировка |
| `from_currency` | string | Исходная валюта |
| `to_currency` | string | Целевая валюта |
| `amount_type` | string | Повторяет `amount_type` из запроса |
| `from_amount` | string | Сумма, которая будет списана в `from_currency` |
| `to_amount` | string | Сумма, которая будет зачислена в `to_currency` |
| `effective_rate` | string | Курс, применённый к этой котировке — 1 единица `from_currency` в `to_currency` (уже включает ценообразование платформы) |
| `from_amount_usd` | string \| null | USD-эквивалент `from_amount` |
| `to_amount_usd` | string \| null | USD-эквивалент `to_amount` |

- Котировка **только индикативная** — рыночная цена может измениться между запросом котировки и исполнением.
- Ничего не списывается и не резервируется этим вызовом.

> Use your project UUID and the endpoint-appropriate API key from the merchant dashboard.

#### Interactive request: `POST /v1/convert/price`
  - `from_currency` (enum, required): BTC,ETH,USDT,USDC,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `to_currency` (enum, required): USDT,USDC,BTC,ETH,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `amount` (decimal, required)
  - `amount_type` (enum, required): from,to

## Исполнить конверт

Исполняет конверт по текущей рыночной цене и обновляет баланс мерчанта. Отдельного шага «подтвердить котировку» нет — вызывайте эту ручку напрямую с той суммой, которую хотите конвертировать.

`POST /v1/convert`

> **INFO:** **Идемпотентность.** Повтор ровно того же запроса (те же `from_currency`, `to_currency`, `amount`, `amount_type`) в течение примерно минуты после первого вызова вернёт уже существующий конверт вместо создания второго. После истечения этого окна идентичный запрос будет воспринят как новый конверт — не ретраьте вслепую при таймауте, сначала проверьте результат предыдущего вызова.

> **WARNING:** Эта ручка ограничена **10 запросами в минуту** на вызывающего — жёстче общего лимита API, — потому что каждый вызов двигает реальный баланс.

### Параметры конверта

| Поле | Тип | Обязательно | Описание | Значение |
|------|-----|--------------|----------|----------|
| `from_currency` | string | да | Валюта, из которой конвертируем | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | да | Валюта, в которую конвертируем. Должна отличаться от `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | да | Сумма конвертации, больше `0` |  |
| `amount_type` | string | да | К какой стороне относится `amount` | `from`, `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"
  }
}
```

#### Поля ответа

| Поле | Тип | Описание |
|------|-----|----------|
| `id` | int | ID конверт-ордера, присвоенный системой |
| `type` | string | Всегда `manual` для этого API |
| `status` | string | Текущий статус (см. «Статусы конверта» ниже) |
| `from_currency` | string | Исходная валюта |
| `to_currency` | string | Целевая валюта |
| `from_amount` | string | Списанная сумма в `from_currency` |
| `requested_from_amount` | string \| null | Запрошенная вами исходная сумма при `amount_type = from`. `null` при `amount_type = to` |
| `refund_amount` | string \| null | Часть предварительно списанной суммы, возвращённая вам после частичного исполнения. `null`, если ордер исполнился полностью |
| `to_amount` | string | Зачисленная сумма в `to_currency` |
| `exchange_rate` | string | Курс, фактически применённый к этой конвертации — 1 единица `from_currency` в `to_currency` (уже включает ценообразование платформы) |
| `fee_amount` | string | Комиссия платформы по этой конвертации, в `from_currency` или `to_currency` в зависимости от направления сделки. Уже учтена в `exchange_rate` — показана для прозрачности |
| `from_amount_usd` | string \| null | USD-эквивалент `from_amount` |
| `to_amount_usd` | string \| null | USD-эквивалент `to_amount` |
| `processed_at` | string (ISO 8601) \| null | Когда конвертация завершила исполнение. `null`, пока она в обработке |
| `created_at` | string (ISO 8601) | Когда был создан конверт-ордер |

#### Статусы конверта

| Статус | Описание |
|--------|----------|
| `pending` | Создан, ещё не отправлен на биржу |
| `processing` | Баланс заблокирован, ордер выставлен на биржу |
| `completed` | Полностью исполнен — `to_amount` зачислен на ваш баланс |
| `failed` | Не удалось исполнить — предварительно списанная сумма возвращена автоматически |
| `partially_completed` | Только для валютных пар без прямого рынка (маршрутизация через промежуточную валюту): первый хоп исполнился, второй — нет. Вам зачислена промежуточная валюта вместо `to_currency` — сконвертируйте её ещё раз, чтобы достичь исходной цели |

#### Interactive request: `POST /v1/convert`
  - `from_currency` (enum, required): BTC,ETH,USDT,USDC,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `to_currency` (enum, required): USDT,USDC,BTC,ETH,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `amount` (decimal, required)
  - `amount_type` (enum, required): from,to

## Ошибки

При ошибке ответ содержит `state: 1` и `error_code` — общие для `/v1/convert/price` и `/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_code` | HTTP-статус | Описание |
|--------------|-------------|----------|
| `validation_failed` | 422 | Неверные или отсутствующие параметры, либо отказ по бизнес-правилу (например, недостаточно баланса) — детали в поле `errors` |
| `amount_too_small` | 422 | `amount` меньше минимального торгуемого размера для этой валютной пары |
| `convert_unavailable` | 400 | Конвертацию не удалось исполнить прямо сейчас (недоступны рыночные данные или нет маршрута между двумя валютами) — повторите чуть позже |
| `internal_error` | 400 | Непредвиденная ошибка сервера при обработке запроса |

## Автоматическая конвертация входящих платежей

Автоконвертация — настройка проекта для входящих платежей и пополнений статических кошельков. Она задаётся в панели управления мерчанта, а не дополнительными полями запроса `/v1/payment`. Каждое правило связывает одну или несколько исходных валют с целевой валютой.

После успешной конвертации данные платежа и webhook-уведомления мерчанта могут содержать:

```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"
  }
}
```

Эти суммы намеренно относятся к разным этапам обработки:

- `payment_amount` — сумма, обнаруженная в блокчейне, в исходной валюте платежа;
- `merchant_amount` — чистая сумма мерчанта в исходной валюте до конвертации;
- `convert.amount` — сумма, зачисленная в валюте `convert.to_currency`;
- `convert.rate` и `convert.commission` — фактический результат выполненной конвертации, а не исходные данные для локального пересчёта.

> **WARNING:** Отсутствие `convert` — допустимый результат: конвертация могла ещё не завершиться, не быть настроена для этой исходной валюты либо завершиться зачислением в исходной валюте. Не рассчитывайте целевую сумму самостоятельно по `/exchange-rates` или публичному рыночному курсу.

### Сбой автоконвертации и резервный сценарий

Конвертация выполняется после получения платежа в блокчейне. Недоступность рынка, минимальный размер ордера, ограничения точности, тайм-аут биржи или недостаточная исполнимая ликвидность могут задержать конвертацию либо сделать её невозможной.

- Депозиты ниже глобального или проектного минимума не проходят конвертацию и зачисляются в исходной валюте.
- После временного сбоя система может повторить попытку асинхронно.
- Крупные или неликвидные депозиты после исчерпания попыток могут быть зачислены в исходной валюте.
- Поэтому платёж может быть корректно получен, даже если конвертация в целевую валюту не состоялась.

Сначала сохраните подтверждённый платёж, затем определите фактически зачисленную валюту по данным платежа, необязательному блоку `convert` и балансам мерчанта. Не задерживайте обработку подтверждающего webhook из-за ожидания собственной аналитики или уведомлений.

### Приёмочные тесты автоконвертации

Как минимум проверьте: успешную прямую конвертацию; конвертацию через промежуточную валюту или несколько переходов; сумму ниже минимальной; повтор после временного сбоя; зачисление в исходной валюте; недоплату; переплату; дубликат webhook; отсутствие `convert`; сверку после неоднозначного тайм-аута.

## Нестандартные ситуации при ручной конвертации

- `/v1/convert/price` возвращает ориентировочный предварительный расчёт; движение рынка может изменить фактический результат исполнения.
- `amount_type: from` фиксирует сумму исходной валюты, а `amount_type: to` запрашивает сумму целевой валюты. Не меняйте это значение между показом экрана подтверждения и отправкой запроса.
- Если для пары нет прямого рынка, обмен может пройти через промежуточную валюту. Статус `partially_completed` означает, что завершён только один этап и средства находятся в промежуточной валюте.
- Если запрос завершился тайм-аутом, сначала выполните сверку и только затем повторяйте операцию: рыночный ордер мог исполниться, даже если HTTP-ответ потерян.
- Статус `failed` требует сверки и не даёт права самостоятельно проводить компенсирующую запись по локальному балансу; учёт списания и возврата ведёт платформа.