# Convert API

> Convierta entre criptomonedas directamente desde el saldo de su comercio — obtenga una cotización en vivo y ejecute al precio de mercado.

La Convert API le permite intercambiar entre las divisas que mantiene en el saldo de su comercio al precio de mercado actual — el mismo motor que impulsa la pestaña **Swap** del panel del comercio, ahora disponible desde su backend.

> **WARNING:** Los endpoints de Convert se firman con su **API key habitual** — la misma que usa para las solicitudes de [Payment API](/docs/payments), **no** la Payout API key. Ejecutar una conversión debita y acredita su saldo de inmediato, así que trate esta clave con el mismo cuidado que cualquier credencial que mueve fondos.

## Obtener cotización de conversión

Devuelve una cotización indicativa para una conversión al precio de mercado actual — la tasa efectiva y los montos resultantes. No se debita ni se reserva nada; llámelo tantas veces como necesite antes de ejecutar.

`POST /v1/convert/price`

### Parámetros de la solicitud

| Campo | Tipo | Obligatorio | Descripción | Valor |
|-------|------|-------------|-------------|-------|
| `from_currency` | string | sí | Divisa de origen | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | sí | Divisa de destino. Debe ser distinta de `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | sí | Monto a convertir, mayor que `0` |  |
| `amount_type` | string | sí | A qué lado se refiere `amount` | `from`, `to` |

> **INFO:** `amount_type=from` gasta exactamente `amount` de `from_currency`. `amount_type=to` recibe exactamente `amount` de `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"
  }
}
```

#### Campos de la respuesta

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `success` | boolean | Si la cotización se calculó correctamente |
| `from_currency` | string | Divisa de origen |
| `to_currency` | string | Divisa de destino |
| `amount_type` | string | Repite el `amount_type` de la solicitud |
| `from_amount` | string | Monto que se debitaría en `from_currency` |
| `to_amount` | string | Monto que se acreditaría en `to_currency` |
| `effective_rate` | string | Tasa aplicada a esta cotización — 1 unidad de `from_currency` en `to_currency` (ya incluye el precio de la plataforma) |
| `from_amount_usd` | string \| null | Equivalente en USD de `from_amount` |
| `to_amount_usd` | string \| null | Equivalente en USD de `to_amount` |

- La cotización es **solo indicativa** — el precio de mercado puede cambiar entre la cotización y la llamada de ejecución.
- Esta llamada no debita ni reserva ningún saldo.

> 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

## Ejecutar conversión

Ejecuta una conversión al precio de mercado actual y actualiza el saldo de su comercio. No hay un paso separado para "confirmar una cotización" — llame directamente con el monto que desea convertir.

`POST /v1/convert`

> **INFO:** **Idempotencia.** Repetir exactamente la misma solicitud (mismos `from_currency`, `to_currency`, `amount`, `amount_type`) dentro de aproximadamente un minuto tras la primera llamada devuelve la conversión existente en lugar de crear una segunda. Pasada esa ventana, una solicitud idéntica se trata como una nueva conversión — no reintente a ciegas ante un timeout sin antes comprobar el resultado anterior.

> **WARNING:** Este endpoint está limitado a **10 solicitudes por minuto** por llamador — más estricto que el límite general de la API — porque cada llamada mueve saldo real.

### Parámetros de la solicitud

| Campo | Tipo | Obligatorio | Descripción | Valor |
|-------|------|-------------|-------------|-------|
| `from_currency` | string | sí | Divisa de origen | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | sí | Divisa de destino. Debe ser distinta de `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | sí | Monto a convertir, mayor que `0` |  |
| `amount_type` | string | sí | A qué lado se refiere `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"
  }
}
```

#### Campos de la respuesta

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `id` | int | ID del pedido de conversión asignado por el sistema |
| `type` | string | Siempre `manual` en esta API |
| `status` | string | Estado actual (ver «Estados de conversión» más abajo) |
| `from_currency` | string | Divisa de origen |
| `to_currency` | string | Divisa de destino |
| `from_amount` | string | Monto debitado en `from_currency` |
| `requested_from_amount` | string \| null | Su monto de origen originalmente solicitado cuando `amount_type = from`. `null` cuando `amount_type = to` |
| `refund_amount` | string \| null | Parte del monto predebitado que se le reembolsa tras una ejecución parcial. `null` si el pedido se completó totalmente |
| `to_amount` | string | Monto acreditado en `to_currency` |
| `exchange_rate` | string | Tasa realmente aplicada a esta conversión, 1 unidad de `from_currency` en `to_currency` (ya incluye el precio de la plataforma) |
| `fee_amount` | string | Comisión de la plataforma cobrada en esta conversión, denominada en `from_currency` o `to_currency` según la dirección de la operación. Ya está reflejada en `exchange_rate` — se muestra por transparencia |
| `from_amount_usd` | string \| null | Equivalente en USD de `from_amount` |
| `to_amount_usd` | string \| null | Equivalente en USD de `to_amount` |
| `processed_at` | string (ISO 8601) \| null | Momento en que la conversión terminó de ejecutarse. `null` mientras aún se procesa |
| `created_at` | string (ISO 8601) | Momento en que se creó el pedido de conversión |

#### Estados de conversión

| Estado | Descripción |
|--------|-------------|
| `pending` | Creado, aún no enviado al mercado |
| `processing` | Saldo bloqueado y pedido colocado en el mercado |
| `completed` | Ejecutado por completo — `to_amount` ha sido acreditado a su saldo |
| `failed` | No se pudo ejecutar — el monto predebitado fue reembolsado automáticamente |
| `partially_completed` | Solo para pares de divisas sin mercado directo (enrutados mediante una divisa intermedia): el primer tramo se completó pero el segundo falló. Se le acredita la divisa intermedia en lugar de `to_currency` — vuelva a convertir desde ahí para alcanzar su objetivo original |

#### 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

## Errores

Ante un fallo, la respuesta tiene `state: 1` y un `error_code` — compartido por `/v1/convert/price` y `/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` | Estado HTTP | Descripción |
|--------------|-------------|-------------|
| `validation_failed` | 422 | Parámetros inválidos o faltantes, o rechazo por regla de negocio (p. ej. saldo insuficiente) — ver el campo `errors` para más detalles |
| `amount_too_small` | 422 | `amount` está por debajo del tamaño mínimo negociable para este par de divisas |
| `convert_unavailable` | 400 | La conversión no pudo ejecutarse en este momento (datos de mercado no disponibles o sin ruta entre las dos divisas) — reintente en breve |
| `internal_error` | 400 | Error interno inesperado del servidor al procesar la solicitud |

## Conversión automática de pagos entrantes

Auto-convert es una configuración de proyecto para pagos entrantes invoice y créditos de billetera estática. Se configura en el panel de merchant, no agregando campos a `/v1/payment`. Cada regla selecciona una o más monedas de origen y una moneda objetivo.

Cuando la conversión se complete, la información del pago y merchant webhooks pueden incluir:

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

Los dominios de cantidad están intencionalmente separados:

- `payment_amount` — lo que se detectó en la cadena en la moneda de pago de origen;
- `merchant_amount` — la cantidad neta de origen atribuible a merchant antes de la conversión;
- `convert.amount` — la cantidad acreditada en `convert.to_currency`;
- `convert.rate` y `convert.commission` — el resultado de la conversión ejecutada, no un precio que deba recalcular localmente.

> **WARNING:** La ausencia de `convert` tiene significado: la conversión puede no haber completado, puede no estar configurada para esa fuente o puede haber vuelto al crédito en la moneda de origen. Nunca invente una cantidad objetivo a partir de `/exchange-rates` o un precio de mercado público.

### Fallo de Auto-convert y fallback

La conversión ocurre después de recibir el pago en blockchain. La disponibilidad en el mercado, tamaños mínimos de orden, límites de precisión, tiempos de espera del intercambio y liquidez ejecutable insuficiente pueden retrasar o impedir la conversión.

- Los depósitos por debajo del mínimo global/proyecto omiten la canalización de conversión y acreditan la moneda de origen.
- Los fallos transitorios pueden reintentarse de manera asincrónica.
- Depósitos grandes o no comerciables pueden volver al crédito en moneda de origen después de que se agote la política de retry.
- Por lo tanto, un pago puede ser válido incluso cuando la conversión deseada a la moneda objetivo no haya ocurrido.

Su integración debe persistir primero el pago verificado, luego conciliar la moneda realmente acreditada a partir de la información de pago, el bloque opcional `convert` y los saldos merchant. No bloquee el reconocimiento del pago webhook mientras espera a sus propios sistemas de análisis o notificación.

### Pruebas de aceptación Auto-convert

Pruebe al menos: conversión directa exitosa, conversión puente/multi-hop, dust por debajo del mínimo, retry transitorio, fallback a la moneda de origen, pago insuficiente, pago excesivo, webhook duplicado, `convert` faltante y reconciliation después de un timeout ambiguo.

## Casos límite de conversión manual

- `/v1/convert/price` es una vista previa indicativa; el movimiento del mercado puede cambiar el resultado de la ejecución.
- `amount_type: from` corrige la solicitud del lado de origen, mientras que `amount_type: to` solicita un monto del lado de destino. No altere el significado al presentar la interfaz de confirmación.
- Un par sin un mercado directo puede ser enrutado a través de una moneda intermedia. Si solo una parte se completa, `partially_completed` informa el crédito intermedio.
- Si una llamada de ejecución excede el tiempo de espera, concilie antes de reintentar. Una orden de mercado puede ejecutarse incluso cuando se pierde su respuesta HTTP.
- Trate `failed` como un estado para conciliar, no como permiso para aplicar una entrada de balance compensatoria local; la plataforma posee la contabilidad de débitos/reembolsos.