Langsung ke konten
Search documentation titles, headings, and guides
API v1Updated 2026-09-20

API Reseller

Referensi REST lengkap untuk program reseller: daftar, login, top-up kuota, minting, dan pengelolaan pelanggan.

API reseller adalah permukaan REST di bawah /api/reseller. Setiap endpoint kecuali signup dan login membutuhkan JWT reseller yang dikirim sebagai Authorization: Bearer <token>. Token diterbitkan saat login dan berlaku selama 24 jam (HS256).

Endpoint autentikasi#

POST/api/reseller/auth/signup
Auth: public

Mendaftarkan akun reseller baru. Saat berhasil, akun dibuat dalam status pending dan menunggu review admin.

Parameters

emailstringbodyrequired

Email login. Tidak boleh masuk daftar hitam.

passwordstringbodyrequired

Kata sandi akun. Minimal 8 karakter.

namestringbodyrequired

Nama tampilan reseller.

wa_numberstringbodyrequired

Nomor WhatsApp untuk kontak, hanya digit dengan kode negara (misalnya `6281234567890`).

Request Example

curl -X POST "https://api.zrouter.dev/api/reseller/auth/signup" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "<password>",
    "name": "Your Name",
    "wa_number": "6281234567890"
  }'

Responses

201Akun reseller dibuat dalam status `pending`.
{
  "reseller": {
    "id": "<reseller-id>",
    "email": "[email protected]",
    "name": "Your Name",
    "status": "pending"
  }
}
409`email_exists` — sudah ada akun untuk email ini.
422`validation_error` — field wajib hilang atau format salah.
403`email_blacklisted` — email tidak diizinkan mendaftar.
POST/api/reseller/auth/login
Auth: public

Mengautentikasi dan memperoleh JWT reseller. Token berjenis HS256 dan berlaku 24 jam.

Parameters

emailstringbodyrequired

Email reseller yang terdaftar.

passwordstringbodyrequired

Kata sandi akun.

Request Example

curl -X POST "https://api.zrouter.dev/api/reseller/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "<password>"
  }'

Responses

200Terdiautentikasi. Mengembalikan JWT dan profil reseller.
{
  "token": "<token>",
  "reseller": {
    "id": "<reseller-id>",
    "email": "[email protected]",
    "name": "Your Name",
    "status": "active"
  }
}
401`invalid_credentials` — email atau kata sandi salah.
429`rate_limited` — terlalu banyak percobaan login (5 per menit).

Endpoint akun#

GET/api/reseller/me
Auth: reseller-jwt

Mengembalikan profil reseller yang sedang terautentikasi, termasuk status terkini.

Responses

200Profil reseller.
{
  "id": "<reseller-id>",
  "email": "[email protected]",
  "name": "Your Name",
  "status": "active"
}
401`invalid_credentials` — JWT hilang atau tidak valid.
GET/api/reseller/balance
Auth: reseller-jwt

Mengembalikan saldo kuota terkini sebagai nilai integer micro-USD.

Responses

200Kuota terkini.
{
  "balance_usd_micro": "99500000"
}
401`invalid_credentials` — JWT hilang atau tidak valid.

Endpoint top-up#

POST/api/reseller/topup
Auth: reseller-jwt

Membuat order top-up yang menambah kuota USD. Method yang tersedia: qris (IDR, kurs tetap), crypto (USDC/USDT di Solana, 1:1), sumopod (QRIS, tanpa biaya), atau crypto_bep20 (USDT/USDC di BNB Smart Chain, 1:1). Order dimulai dalam status pending dan dikreditkan hanya setelah terkonfirmasi.

Parameters

methodstringbodyrequired

Metode pembayaran untuk order ini.

Allowed values:qriscryptosumopodcrypto_bep20
amount_idrintegerbodyoptional

Jumlah pokok dalam IDR. Wajib saat `method` adalah `qris` atau `sumopod`.

amount_usd_microintegerbodyoptional

Jumlah dalam micro-USD. Wajib saat `method` adalah `crypto` atau `crypto_bep20`.

token_kindstringbodyoptional

Token deposit untuk `crypto_bep20` (BEP20 di BSC chain 56). Diabaikan oleh method lain.

Allowed values:usdtusdc

Request Example

curl -X POST "https://api.zrouter.dev/api/reseller/topup" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"method": "qris", "amount_idr": 50000}'

Responses

201Order top-up dibuat dengan `status` `pending` dan `payment_status` `pending`. Untuk QRIS respons memuat konten QR dan gambar QR; untuk SumoPod respons memuat string QRIS mentah (`qr_content`) dan halaman pembayaran terhosting (`payment_url`); untuk crypto memuat alamat deposit. Order `crypto_bep20` tambahan memuat fakta jaringan yang immutable: `network` (`bsc`), `chain_id` (`56`), `token_kind`, `contract_address`, `expected_raw` (jumlah token 18-desimal persis yang harus dikirim), dan — setelah terdeteksi — `received_raw`/`received_micro`; EOA deposit ada di `deposit_address`. `quota_usd_micro` adalah jumlah yang dikreditkan (fee tidak pernah dikreditkan); `paid_at`/`credited_at` muncul setelah terisi.
{
  "id": "0d9c6f5e-4c1b-4f6e-9d2a-7b3f8c1e5a20",
  "order_number": "RTO-0d9c6f5e-4c1b-4f6e-9d2a-7b3f8c1e5a20",
  "payment_method": "qris",
  "provider": "tako",
  "amount_idr": 50000,
  "fee_idr": 2500,
  "total_idr": 52500,
  "quota_usd_micro": 5000000,
  "status": "pending",
  "payment_status": "pending",
  "provider_payment_id": "...",
  "expires_at": "2026-09-16T00:15:00Z",
  "created_at": "2026-09-16T00:00:00Z",
  "qr_content": "0002...",
  "qr_image_url": "https://...",
  "payment_url": "https://..."
}
422`amount_out_of_range` atau `validation_error` — jumlah di luar rentang, atau field hilang/saling bertentangan.
503`provider_unavailable` — penyedia pembayaran sementara tidak tersedia.
Buat order top-up crypto
curl -X POST "https://api.zrouter.dev/api/reseller/topup" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"method": "crypto", "amount_usd_micro": 20000000}'
Buat order top-up BEP20 (BSC)
curl -X POST "https://api.zrouter.dev/api/reseller/topup" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"method": "crypto_bep20", "amount_usd_micro": 20000000, "token_kind": "usdt"}'
GET/api/reseller/topup/{orderID}
Auth: reseller-jwt

Mengambil status order top-up dan merekonsiliasinya. Jika pembayaran telah final, order ditandai paid dan kuota Anda dikreditkan dalam panggilan yang sama. Field status adalah siklus hidup order (pending → crediting → credited, atau expired / failed); payment_status adalah tampilan pembayaran hasil derivasi (paid, pending, expired, failed) — pembayaran paid pada order yang belum dikredit tetap dikreditkan. Gunakan endpoint GET ini untuk polling.

Parameters

orderIDstringpathrequired

Field `id` order yang dikembalikan oleh `POST /api/reseller/topup`.

Request Example

curl "https://api.zrouter.dev/api/reseller/topup/<order-id>" \
  -H "Authorization: Bearer <token>"

Responses

200Order yang diperbarui. Bentuknya sama dengan respons pembuatan; `paid_at` dan `credited_at` muncul setelah terisi.
{
  "id": "0d9c6f5e-4c1b-4f6e-9d2a-7b3f8c1e5a20",
  "order_number": "RTO-0d9c6f5e-4c1b-4f6e-9d2a-7b3f8c1e5a20",
  "payment_method": "qris",
  "provider": "tako",
  "amount_idr": 50000,
  "fee_idr": 2500,
  "total_idr": 52500,
  "quota_usd_micro": 5000000,
  "status": "credited",
  "payment_status": "paid",
  "provider_payment_id": "...",
  "expires_at": "2026-09-16T00:15:00Z",
  "created_at": "2026-09-16T00:00:00Z",
  "paid_at": "2026-09-16T00:07:41Z",
  "credited_at": "2026-09-16T00:07:41Z"
}
404`topup_not_found` — order tidak ada untuk reseller ini.
GET/api/reseller/topups
Auth: reseller-jwt

Menampilkan daftar order top-up Anda, terbaru lebih dulu. Setiap entri bentuknya sama dengan respons order tunggal. total adalah total jumlah order Anda (bukan ukuran halaman), untuk pagination.

Parameters

limitintegerqueryoptional

Ukuran halaman, default 20, maksimum 100.

offsetintegerqueryoptional

Offset halaman, default 0.

Request Example

curl "https://api.zrouter.dev/api/reseller/topups?limit=20&offset=0" \
  -H "Authorization: Bearer <token>"

Responses

200Satu halaman order top-up Anda beserta jumlah totalnya.
{
  "topups": [
    {
      "id": "0d9c6f5e-...",
      "order_number": "RTO-0d9c6f5e-...",
      "payment_method": "qris",
      "provider": "tako",
      "amount_idr": 50000,
      "fee_idr": 2500,
      "total_idr": 52500,
      "quota_usd_micro": 5000000,
      "status": "credited",
      "payment_status": "paid",
      "expires_at": "2026-09-16T00:15:00Z",
      "created_at": "2026-09-16T00:00:00Z",
      "paid_at": "2026-09-16T00:07:41Z",
      "credited_at": "2026-09-16T00:07:41Z"
    }
  ],
  "total": 1
}
POST/api/reseller/topup/{orderID}/check
Auth: reseller-jwt

Alias legacy dari GET /api/reseller/topup/{orderID} — perilaku poll-dan-rekonsiliasi sama, body respons juga sama. Dipertahankan untuk kompatibilitas mundur.

Parameters

orderIDstringpathrequired

ID order yang dikembalikan oleh `POST /api/reseller/topup`.

Request Example

curl -X POST "https://api.zrouter.dev/api/reseller/topup/<order-id>/check" \
  -H "Authorization: Bearer <token>"

Responses

200Order yang diperbarui — body sama dengan `GET /api/reseller/topup/{orderID}`.
404`topup_not_found` — order tidak ada untuk reseller ini.

Endpoint minting#

POST/api/reseller/mint
Auth: reseller-jwt

Me-mint tier Token Plan kepada pelanggan, mendebit kuota Anda secara atomik. Idempoten pada operation_id. Lihat Kuota & Minting.

Parameters

operation_idstringbodyrequired

Key idempotensi yang dibuat klien. Non-kosong, maksimal 128 karakter.

customer_emailstringbodyrequired

Email pelanggan target (seorang pengguna zrouter).

plan_tier_idstringbodyrequired

Tier Token Plan yang diberikan. Lihat [halaman pricing](/id/pricing) yang live.

Request Example

curl -X POST "https://api.zrouter.dev/api/reseller/mint" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "operation_id": "op_1234567890abcdef",
    "customer_email": "[email protected]",
    "plan_tier_id": "<tier-id>"
  }'

Responses

200Mint diterapkan. `already_applied` bernilai `true` pada replay idempoten.
{
  "customer_user_id": "<user-id>",
  "model_id": "<model-id>",
  "granted_tokens": 1000,
  "price_usd_micro": "1000000",
  "balance_after": "98500000",
  "already_applied": false
}
402`insufficient_quota` — kuota Anda tidak dapat menutupi mint ini.
409`idempotency_conflict` — `operation_id` dipakai ulang dengan payload berbeda.
422`tier_not_available` atau `validation_error` — tier tidak dijual, atau field salah format.
429`rate_limited` — batas rate limit mint terlampaui (30 per menit).
500`internal_error` — kegagalan internal tak terduga.

Katalog tier#

GET /api/reseller/tiers menampilkan daftar tier Token Plan yang dapat Anda mint, satu entri per tier, diurutkan berdasarkan harga tier menaik lalu nama model menaik. Field topup_idr_required adalah kuota top-up IDR yang dibutuhkan untuk menutupi tier pada kurs tetap Rp10.000 per USD (tier_usd_cents × 100); fee pemrosesan QRIS 5% terpisah dan tidak pernah disertakan. Hanya field publik yang aman yang dikembalikan.

GET/api/reseller/tiers
Auth: reseller-jwt

Mengembalikan daftar tier Token Plan yang tersedia untuk di-mint, diurutkan berdasarkan harga tier menaik lalu nama model menaik. Field pricing internal tidak pernah diekspos.

Responses

200Daftar tier yang dapat di-mint.
{
  "tiers": [
    {
      "plan_tier_id": "<tier-id>",
      "model_id": "<model-id>",
      "model_name": "<public-name>",
      "tier_usd_cents": 100,
      "price_usd_micro": 1000000,
      "granted_tokens": 1000,
      "topup_idr_required": 10000
    }
  ]
}
401`invalid_credentials` — JWT hilang atau tidak valid.

Endpoint pelanggan#

GET/api/reseller/customers
Auth: reseller-jwt

Menampilkan daftar pelanggan yang terkait dengan reseller ini.

Responses

200Daftar pelanggan.
{
  "customers": [
    { "user_id": "<user-id>", "email": "[email protected]" }
  ]
}
401`invalid_credentials` — JWT hilang atau tidak valid.
POST/api/reseller/customers
Auth: reseller-jwt

Resolve-atau-buat pelanggan berdasarkan email. Jika pengguna zrouter belum ada, satu dibuat dan ditautkan ke reseller ini.

Parameters

emailstringbodyrequired

Email pelanggan.

Request Example

curl -X POST "https://api.zrouter.dev/api/reseller/customers" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]"}'

Responses

200Pelanggan yang di-resolve atau baru dibuat.
{
  "user_id": "<user-id>",
  "email": "[email protected]",
  "created": false
}
422`validation_error` — format email salah.
POST/api/reseller/customers/{userID}/keys
Auth: reseller-jwt

Membuat API key untuk pelanggan. Secret ditampilkan sekali dan tidak pernah disimpan. Respons dikirim dengan Cache-Control: no-store.

Parameters

userIDstringpathrequired

User id pelanggan.

namestringbodyrequired

Label untuk key ini.

Request Example

curl -X POST "https://api.zrouter.dev/api/reseller/customers/<user-id>/keys" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "production"}'

Responses

201Key dibuat. `secret` hanya ditampilkan sekali.
{
  "key_id": "<key-id>",
  "name": "production",
  "secret": "zr_<public-id>_<secret>"
}
404User target bukan pelanggan dari reseller ini.
DELETE/api/reseller/keys/{keyID}
Auth: reseller-jwt

Mencabut API key. Terbatas ruang lingkup: key harus milik pelanggan reseller ini, jika tidak request gagal dengan 404.

Parameters

keyIDstringpathrequired

ID key yang akan dicabut.

Request Example

curl -X DELETE "https://api.zrouter.dev/api/reseller/keys/<key-id>" \
  -H "Authorization: Bearer <token>"

Responses

200Key dicabut.
404Key tidak ada atau bukan milik pelanggan reseller ini.

Endpoint saldo#

Endpoint saldo berbagi satu bentuk respons: objek payg dengan saldo USD sebagai string desimal, dan array token_plans berisi jumlah token integer. Uang selalu berupa string desimal micro-USD (misalnya "0.000001" adalah $0.000001) — tidak pernah float — dan jumlah token selalu integer. Key yang dicabut tetap bisa di-resolve ke saldo pemiliknya, sehingga Anda dapat merekonsiliasi pemakaian historis.

GET/api/reseller/keys/{keyID}/balances
Auth: reseller-jwt

Mengembalikan saldo PAYG dan saldo Token Plan milik pelanggan pemilik API key yang diberikan. Tetap berfungsi meskipun key sudah dicabut, untuk rekonsiliasi historis. Terbatas ruang lingkup: key harus milik pelanggan reseller ini, jika tidak request gagal dengan 404.

Parameters

keyIDstringpathrequired

ID API key (UUID) yang akan dicari.

Request Example

curl "https://api.zrouter.dev/api/reseller/keys/<key-id>/balances" \
  -H "Authorization: Bearer <token>"

Responses

200Saldo pemilik key, termasuk `payg` dan `token_plans`.
{
  "key_id": "<key-id>",
  "customer_user_id": "<user-id>",
  "payg": {
    "currency": "USD",
    "balance": "0.000001"
  },
  "token_plans": [
    {
      "model": "<public-name>",
      "purchased_tokens": 1000,
      "used_tokens": 123,
      "remaining_tokens": 877,
      "status": "active"
    }
  ],
  "as_of": "2026-09-16T00:00:00Z"
}
401`invalid_credentials` — JWT hilang atau tidak valid.
404`not_found` — key tidak ada atau bukan milik pelanggan reseller ini.
422`validation_error` — `keyID` bukan UUID yang valid.
GET/api/reseller/customers/{userID}/balances
Auth: reseller-jwt

Mengembalikan saldo PAYG dan saldo Token Plan untuk salah satu pelanggan Anda. Bentuknya sama dengan endpoint saldo key, dikurangi field key_id. Pelanggan harus dimiliki oleh reseller ini, jika tidak request gagal dengan 404.

Parameters

userIDstringpathrequired

User id pelanggan (UUID).

Request Example

curl "https://api.zrouter.dev/api/reseller/customers/<user-id>/balances" \
  -H "Authorization: Bearer <token>"

Responses

200Saldo pelanggan, termasuk `payg` dan `token_plans`.
{
  "customer_user_id": "<user-id>",
  "payg": {
    "currency": "USD",
    "balance": "0.000001"
  },
  "token_plans": [
    {
      "model": "<public-name>",
      "purchased_tokens": 1000,
      "used_tokens": 123,
      "remaining_tokens": 877,
      "status": "active"
    }
  ],
  "as_of": "2026-09-16T00:00:00Z"
}
401`invalid_credentials` — JWT hilang atau tidak valid.
404`not_found` — pelanggan bukan milik reseller ini.
422`validation_error` — `userID` bukan UUID yang valid.

Endpoint penggunaan#

GET/api/reseller/usage
Auth: reseller-jwt

Mengembalikan penggunaan yang sudah settle untuk pelanggan reseller ini, terbaru lebih dulu, dengan paginasi cursor. Setiap item memuat biaya persisnya sebagai display_cost_usd_micro.

Parameters

limitintegerqueryoptionaldefault: 50

Ukuran halaman. Minimal 1, maksimal 100.

cursorstringqueryoptional

Cursor buram dari `next_cursor` respons sebelumnya.

Responses

200Satu halaman peristiwa penggunaan.
{
  "data": [
    {
      "user_id": "<user-id>",
      "model": "<public-name>",
      "total_tokens": 49,
      "display_cost_usd_micro": "0.000120",
      "timestamp": "2026-09-16T00:00:00Z"
    }
  ],
  "next_cursor": "<opaque-string>"
}
401`invalid_credentials` — JWT hilang atau tidak valid.