# Convert API

> Konversi antar mata uang kripto langsung dari saldo merchant Anda — dapatkan penawaran harga langsung dan eksekusi pada harga pasar.

Convert API memungkinkan Anda menukar antar mata uang yang disimpan di saldo merchant Anda dengan harga pasar saat ini — mesin yang sama yang menggerakkan tab **Swap** di dashboard merchant, kini dapat dipanggil dari backend Anda.

> **WARNING:** Endpoint Convert ditandatangani dengan **API key biasa** Anda — sama dengan yang digunakan untuk permintaan [Payment API](/docs/payments), **bukan** Payout API key. Menjalankan konversi langsung mendebit dan mengkredit saldo merchant Anda, jadi perlakukan kunci ini dengan kehati-hatian yang sama seperti kredensial apa pun yang menggerakkan uang.

## Mendapatkan harga konversi

Mengembalikan penawaran indikatif untuk konversi pada harga pasar saat ini — kurs efektif dan jumlah yang dihasilkan. Tidak ada yang didebit atau dicadangkan; panggil sesering yang Anda butuhkan sebelum mengeksekusi.

`POST /v1/convert/price`

### Parameter permintaan

| Kolom | Tipe | Wajib | Deskripsi | Nilai |
|-------|------|-------|-----------|-------|
| `from_currency` | string | ya | Mata uang asal | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | ya | Mata uang tujuan. Harus berbeda dari `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | ya | Jumlah yang akan dikonversi, lebih besar dari `0` |  |
| `amount_type` | string | ya | Sisi mana yang dirujuk `amount` | `from`, `to` |

> **INFO:** `amount_type=from` membelanjakan tepat `amount` dalam `from_currency`. `amount_type=to` menerima tepat `amount` dalam `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"
  }
}
```

#### Kolom respons

| Kolom | Tipe | Deskripsi |
|-------|------|-----------|
| `success` | boolean | Apakah penawaran berhasil dihitung |
| `from_currency` | string | Mata uang asal |
| `to_currency` | string | Mata uang tujuan |
| `amount_type` | string | Mencerminkan `amount_type` dari permintaan |
| `from_amount` | string | Jumlah yang akan didebit dalam `from_currency` |
| `to_amount` | string | Jumlah yang akan dikreditkan dalam `to_currency` |
| `effective_rate` | string | Kurs yang diterapkan pada penawaran ini — 1 unit `from_currency` dalam `to_currency` (sudah termasuk penetapan harga platform) |
| `from_amount_usd` | string \| null | Setara USD dari `from_amount` |
| `to_amount_usd` | string \| null | Setara USD dari `to_amount` |

- Harga ini **hanya bersifat indikatif** — harga pasar dapat berubah antara saat penawaran diberikan dan saat pemanggilan eksekusi.
- Panggilan ini tidak mendebit atau mencadangkan saldo apa pun.

> 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

## Menjalankan konversi

Mengeksekusi konversi pada harga pasar saat ini dan memperbarui saldo merchant Anda. Tidak ada langkah terpisah untuk "mengonfirmasi harga" — panggil langsung endpoint ini dengan jumlah yang ingin Anda konversi.

`POST /v1/convert`

> **INFO:** **Idempotensi.** Mengulangi permintaan yang persis sama (`from_currency`, `to_currency`, `amount`, `amount_type` yang sama) dalam waktu sekitar satu menit setelah panggilan pertama akan mengembalikan konversi yang sudah ada alih-alih membuat konversi kedua. Setelah jendela waktu itu berakhir, permintaan yang identik akan diperlakukan sebagai konversi baru — jangan mencoba lagi secara membabi buta saat terjadi timeout tanpa terlebih dahulu memeriksa hasil panggilan sebelumnya.

> **WARNING:** Endpoint ini dibatasi hingga **10 permintaan per menit** per pemanggil — lebih ketat daripada batas API umum — karena setiap panggilan menggerakkan saldo nyata.

### Parameter permintaan

| Kolom | Tipe | Wajib | Deskripsi | Nilai |
|-------|------|-------|-----------|-------|
| `from_currency` | string | ya | Mata uang asal | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | ya | Mata uang tujuan. Harus berbeda dari `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | ya | Jumlah yang akan dikonversi, lebih besar dari `0` |  |
| `amount_type` | string | ya | Sisi mana yang dirujuk `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"
  }
}
```

#### Kolom respons

| Kolom | Tipe | Deskripsi |
|-------|------|-----------|
| `id` | int | ID pesanan konversi yang diberikan oleh sistem |
| `type` | string | Selalu `manual` untuk API ini |
| `status` | string | Status saat ini (lihat «Status konversi» di bawah) |
| `from_currency` | string | Mata uang asal |
| `to_currency` | string | Mata uang tujuan |
| `from_amount` | string | Jumlah yang didebit dalam `from_currency` |
| `requested_from_amount` | string \| null | Jumlah asal yang awalnya Anda minta saat `amount_type = from`. `null` saat `amount_type = to` |
| `refund_amount` | string \| null | Bagian dari jumlah yang telah didebit sebelumnya, dikembalikan kepada Anda setelah eksekusi sebagian. `null` jika pesanan terisi penuh |
| `to_amount` | string | Jumlah yang dikreditkan dalam `to_currency` |
| `exchange_rate` | string | Kurs yang sebenarnya diterapkan pada konversi ini — 1 unit `from_currency` dalam `to_currency` (sudah termasuk penetapan harga platform) |
| `fee_amount` | string | Biaya platform yang dikenakan pada konversi ini, dinyatakan dalam `from_currency` atau `to_currency` tergantung arah transaksi. Sudah tercermin dalam `exchange_rate` — ditampilkan untuk transparansi |
| `from_amount_usd` | string \| null | Setara USD dari `from_amount` |
| `to_amount_usd` | string \| null | Setara USD dari `to_amount` |
| `processed_at` | string (ISO 8601) \| null | Waktu konversi selesai dieksekusi. `null` selama masih diproses |
| `created_at` | string (ISO 8601) | Waktu pesanan konversi dibuat |

#### Status konversi

| Status | Deskripsi |
|--------|-----------|
| `pending` | Dibuat, belum dikirim ke pasar |
| `processing` | Saldo terkunci, pesanan ditempatkan di pasar |
| `completed` | Sepenuhnya dieksekusi — `to_amount` telah dikreditkan ke saldo Anda |
| `failed` | Tidak dapat dieksekusi — jumlah yang telah didebit sebelumnya dikembalikan secara otomatis |
| `partially_completed` | Hanya untuk pasangan mata uang tanpa pasar langsung (dirutekan melalui mata uang perantara): tahap pertama selesai tetapi tahap kedua gagal. Anda dikreditkan mata uang perantara alih-alih `to_currency` — konversi lagi dari sana untuk mencapai target awal Anda |

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

## Error

Saat gagal, respons memiliki `state: 1` dan sebuah `error_code` — bersama untuk `/v1/convert/price` dan `/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` | Status HTTP | Deskripsi |
|--------------|-------------|-----------|
| `validation_failed` | 422 | Parameter tidak valid atau hilang, atau ditolak karena aturan bisnis (mis. saldo tidak cukup) — lihat kolom `errors` untuk detailnya |
| `amount_too_small` | 422 | `amount` di bawah ukuran minimum yang dapat diperdagangkan untuk pasangan mata uang ini |
| `convert_unavailable` | 400 | Konversi tidak dapat dieksekusi saat ini (data pasar tidak tersedia atau tidak ada rute antara kedua mata uang) — coba lagi sebentar lagi |
| `internal_error` | 400 | Kesalahan server internal tak terduga saat memproses permintaan |

## Konversi otomatis untuk pembayaran masuk

Konversi otomatis adalah pengaturan proyek untuk faktur masuk dan kredit dompet statis. Ini dikonfigurasi di dasbor pedagang, bukan dengan menambahkan bidang ke `/v1/payment`. Setiap aturan memilih satu atau lebih mata uang sumber dan satu mata uang target.

Saat konversi selesai, info pembayaran dan webhook pedagang dapat mencakup:

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

Domain jumlah sengaja dipisahkan:

- `payment_amount` — apa yang terdeteksi di blockchain dalam mata uang pembayaran sumber;
- `merchant_amount` — jumlah sumber bersih yang menjadi hak pedagang sebelum konversi;
- `convert.amount` — jumlah yang dikreditkan dalam `convert.to_currency`;
- `convert.rate` dan `convert.commission` — hasil konversi yang dijalankan, bukan harga yang harus Anda hitung ulang secara lokal.

> **WARNING:** Ketidakhadiran `convert` memiliki makna: konversi mungkin belum selesai, mungkin belum dikonfigurasi untuk sumber itu, atau mungkin jatuh kembali ke kredit mata uang sumber. Jangan pernah membuat jumlah target dari `/exchange-rates` atau harga pasar publik.

### Kegagalan dan fallback konversi otomatis

Konversi berada di hilir menerima pembayaran blockchain. Ketersediaan pasar, ukuran pesanan minimum, batas presisi, waktu habis pertukaran, dan likuiditas yang tidak cukup dapat menunda atau mencegah konversi.

- Setoran di bawah minimum global/proyek melewati jalur konversi dan mengkredit mata uang sumber.
- Kegagalan sementara dapat dicoba kembali secara asinkron.
- Setoran besar atau yang tidak dapat diperdagangkan dapat kembali ke kredit dalam mata uang sumber setelah kebijakan percobaan ulang habis.
- Oleh karena itu, sebuah pembayaran dapat tetap berlaku meskipun konversi ke mata uang target yang diinginkan tidak terjadi.

Integrasi Anda harus menyimpan pembayaran yang telah diverifikasi terlebih dahulu, kemudian mencocokkan mata uang yang sebenarnya dikreditkan dari info pembayaran, blok opsional `convert`, dan saldo pedagang. Jangan menahan pengakuan webhook pembayaran sambil menunggu sistem analitik atau notifikasi Anda sendiri.

### Uji penerimaan konversi otomatis

Uji setidaknya: konversi langsung yang berhasil, konversi jembatan/multi-langkah, debu di bawah minimum, percobaan ulang sementara, fallback ke mata uang sumber, pembayaran kurang, pembayaran berlebih, webhook duplikat, `convert` hilang, dan rekonsiliasi setelah timeout ambigu.

## Kasus tepi konversi manual

- `/v1/convert/price` adalah pratinjau indikatif; pergerakan pasar dapat mengubah hasil eksekusi.
- `amount_type: from` memperbaiki permintaan sisi sumber, sementara `amount_type: to` meminta jumlah sisi target. Jangan menukar maknanya saat menampilkan UI konfirmasi.
- Pasangan tanpa pasar langsung dapat dialihkan melalui mata uang perantara. Jika hanya satu kaki yang selesai, `partially_completed` melaporkan kredit perantara.
- Jika panggilan eksekusi mengalami timeout, lakukan rekonsiliasi sebelum mencoba lagi. Pesanan pasar dapat dieksekusi meskipun respons HTTP-nya hilang.
- Perlakukan `failed` sebagai status untuk direkonsiliasi, bukan sebagai izin untuk menerapkan entri saldo kompensasi lokal; platform memiliki akuntansi debit/refund.