# API NusaOTP

Base URL adalah origin website Anda ditambah `/api`. Buat token dari **Developer API** setelah login. Kirim `Authorization: Bearer TOKEN_ANDA`. Token hanya boleh dipakai pada server aplikasi Anda.

Semua request tulis memakai `Content-Type: application/json`. Respons sukses berbentuk `{"success":true,"data":...}`. Respons gagal: `{"success":false,"error":{"code":"...","message":"...","request_id":"..."}}`. Nominal memakai integer IDR; tanggal lokal memakai epoch milidetik, kecuali timestamp capability provider yang dapat berbentuk ISO 8601.

| Metode | Path | Keterangan |
|---|---|---|
| GET | `/summary` | User, saldo, total/aktif/selesai, total refund |
| GET | `/catalog/services` | Array layanan, country_id opsional |
| GET | `/catalog/countries` | Array negara |
| GET | `/catalog/operators?country_id=...&platform_id=...` | Operator; array kosong berarti tidak ada filter operator |
| GET | `/catalog/products?country_id=...&platform_id=...` | Paket dan harga jual; operator_id opsional |
| POST | `/quote` | Harga terkunci 90 detik |
| POST | `/orders` | Konfirmasi pembelian dengan quote_id |
| GET | `/orders` | Maksimal 500 order akun terbaru |
| GET | `/orders/{id}` | Status lokal dan SMS terakhir |
| POST | `/orders/{id}/refresh` | Sinkronkan ke provider |
| POST | `/orders/{id}/cancel` | Batal sesuai izin provider |
| POST | `/orders/{id}/finish` | Selesai sesuai izin provider |
| POST | `/orders/{id}/resend` | Minta kode baru sesuai izin provider |

Body quote:

```json
{"country_id":7,"platform_id":3,"product_id":123,"operator_id":null}
```

ID tersebut contoh. Ambil nilai aktual dari katalog. Quote menghasilkan `id`, `price`, `expires_at`, dan ringkasan produk. Order:

```http
POST /api/orders
Authorization: Bearer TOKEN_ANDA
Content-Type: application/json
Idempotency-Key: referensi-unik-minimal-16-karakter

{"quote_id":"ID_QUOTE_DARI_SERVER"}
```

Idempotency-Key wajib 16–128 karakter: huruf, angka, `_`, dan `-`. Saat retry karena timeout, pertahankan key dan body persis sama. Jangan memakai key sama dengan quote berbeda. Respons create dapat berstatus FAILED dengan refund atau REVIEW dengan dana ditahan; periksa status, bukan hanya HTTP 201. Simpan referensi secara persisten pada aplikasi klien.

Tindakan refresh/cancel/finish/resend memakai body `{}`. Cek `capabilities.can_cancel`, `can_finish`, dan `can_resend` sebelum menampilkan tindakan. `received_at` yang terisi berarti SMS diterima meskipun `otp` kosong; tampilkan `message`. `revision` membantu membedakan SMS baru. Status akhir: COMPLETED, CANCELED, EXPIRED, FAILED. `refunded_at` menandakan refund ke saldo.

Polling lokal setiap 5–15 detik. Sinkronisasi otomatis default 15 detik pada server; GET order membaca database lokal, sedangkan POST refresh memanggil provider. Refresh dibatasi 12/menit/order, create 12/menit/akun, quote 30/menit/akun; ada batas umum 600 request/menit/IP. HTTP 429 membawa Retry-After. Batas dapat disesuaikan di src/http.mjs untuk kebutuhan terukur.

Token member hanya bisa memakai endpoint pada tabel. Pengaturan profil, deposit, token management, dan panel admin membutuhkan sesi website plus CSRF. Token yang dicabut, akun dinonaktifkan, atau password direset tidak dapat dipakai lagi.
