Developer Docs

REST API & Webhook untuk integrasi AI Doodle RNV4

API ini memungkinkan aplikasi eksternal — seperti RNV4 Studio atau aplikasi buatan Anda sendiri — untuk:

  • Mengirim foto ke server dan meminta AI men-generate gambar doodle
  • Memantau status generate secara polling atau menerima notifikasi via Webhook
  • Mengambil hasil gambar yang sudah selesai
  • Mengecek saldo kredit akun

Base URL: https://app.ruskomponen.uk

Semua response dalam format application/json kecuali endpoint download gambar.

Quick Start

  1. 1Daftar akun di https://app.ruskomponen.uk/register
  2. 2Buat API Key di Dashboard → Developer → API Keys
  3. 3Kirim foto ke POST /api/v1/ai/generate dengan header X-API-Token: <token>
  4. 4Poll GET /api/v1/ai/status/{gen_id} hingga status == "done", lalu ambil gambar
# Contoh cURL — generate gambar curl -X POST https://app.ruskomponen.uk/api/v1/ai/generate \ -H "X-API-Token: rnv4-your-token-here" \ -F "[email protected]" \ -F "prompt=Ubah foto ini menjadi line art hitam putih" \ -F "robot_id=RNV4-001"

Autentikasi

Semua endpoint API memerlukan API Key yang dikirim via HTTP header:

X-API-Token: rnv4-your-token-here

Cara mendapatkan API Key

Login ke Dashboard → tab Developer → bagian API Keys → klik Buat Token Baru. Key hanya ditampilkan sekali saat dibuat — simpan di tempat aman.

Polling vs Webhook

Setelah memanggil POST /api/v1/ai/generate, server memproses gambar di background. Ada dua cara untuk mengetahui hasilnya — pilih sesuai jenis aplikasi Anda:

🔄

Polling

Client bertanya ke server secara berkala hingga selesai.

Cocok untuk:

  • Desktop app (tidak punya public URL)
  • Script / CLI / quick testing
  • Di balik NAT / firewall
# Poll tiap 3 detik while True: r = requests.get(f"{BASE}/status/{gen_id}", headers=headers) if r.json()["status"] in ("done","failed"): break time.sleep(3)

Webhook

Server mengirim notifikasi ke URL Anda begitu selesai.

Cocok untuk:

  • Web app / backend dengan public URL
  • Real-time tanpa buang bandwidth
  • Integrasi ke sistem lain
# Server POST ke URL Anda saat selesai # { "event": "ai.generate.done", # "data": { "gen_id": 42, # "result_url": "..." } }
Bisa gunakan keduanya. Tidak ada larangan menggunakan polling dan mendaftarkan webhook sekaligus — misalnya polling sebagai fallback jika webhook gagal terkirim.

REST API

POST /api/v1/ai/generate

Kirim foto dan prompt. Server memotong kredit, lalu menjalankan Gemini di background. Hasilnya bisa diambil via polling atau Webhook.

Request (multipart/form-data)

FieldTipeKeterangan
photofile File gambar (JPEG/PNG, maks 10MB) wajib
promptstring Instruksi untuk AI. Jika kosong, digunakan prompt default (line art hitam putih).
robot_idstring ID robot pengirim (untuk log dan tracking).

Response

{ "gen_id": 42, "result_token": "a1b2c3d4e5f6...", "status": "pending", "queue_position": 0, "credit_cost": 1, "balance": 49 }
GET /api/v1/ai/status/{gen_id}

Cek status satu job generate. Poll endpoint ini hingga status bernilai done atau failed.

Nilai status

pendingAntri, belum diproses
processingSedang diproses oleh Gemini
doneSelesai — hasil siap diambil
failedGagal — kredit dikembalikan otomatis
expiredHasil sudah dihapus (TTL 1 jam setelah done)
# Response saat status = done { "gen_id": 42, "status": "done", "done_at": "2026-06-25T10:05:30Z", "result_token": "a1b2c3d4e5f6...", "expires_at": "2026-06-25T11:05:30Z" } # Response saat status = failed { "gen_id": 42, "status": "failed", "error_msg": "Gemini API timeout" }
GET /api/v1/ai/queue

Ambil status semua job generate milik akun sekaligus (50 terbaru). Lebih efisien dari poll /status satu-satu jika ada banyak job aktif.

{ "queue": [ { "gen_id": 44, "status": "processing", "created_at": "2026-06-25 10:06:00" }, { "gen_id": 42, "status": "done", "done_at": "2026-06-25 10:05:30", "result_token": "a1b2c3d4e5f6..." }, { "gen_id": 41, "status": "failed", "error": "Gemini API timeout" } ] }
GET /api/v1/ai/result/{gen_id}?token={result_token}

Download gambar hasil generate (PNG). Autentikasi via query param token (result_token dari response generate) atau header X-API-Token.

Hasil tersedia selama 1 jam setelah done. Setelah itu file dihapus dan status berubah ke expired.

Perlakukan result_token seperti kredensial

token di URL ini memberi akses unduh langsung ke hasil generate tanpa perlu X-API-Token — siapa pun yang memegang URL lengkap (termasuk result_url di payload webhook ai.generate.done) bisa mengunduhnya. Jangan tampilkan URL ini di halaman publik, log yang bisa diakses pihak lain, atau kirim ke analytics pihak ketiga. Berlaku hanya 1 jam, tapi tetap jaga kerahasiaannya selama itu.

curl "https://app.ruskomponen.uk/api/v1/ai/result/42?token=a1b2c3d4e5f6..." \ --output hasil.png
GET /api/v1/ai/history

Daftar generate history milik akun. Mendukung header X-API-Token (robot) maupun JWT (browser).

Query params

limit10 | 50 | 100Jumlah per halaman (default: 10)
pageintegerHalaman (default: 1)
periodstringFilter: all | today | week | month
{ "ok": true, "total": 42, "page": 1, "pages": 5, "limit": 10, "history": [ { "id": 123, "robot_id": "RNV4-ABC123", "prompt": "line art hitam putih", "status": "done", "model": "gemini-2.0-flash-exp-image-generation", "credits_used": 1, "sell_price_idr": 5000, "created_at": "2026-06-26T10:00:00", "done_at": "2026-06-26T10:00:08", "expires_at": "2026-06-26T11:00:08", "has_result": true, "result_token": "a1b2c3d4e5f6..." } ] }
GET /api/v1/ai/balance

Cek saldo kredit dan informasi rate limit.

{ "ok": true, "balance": 49, "rate_limit_secs": 10, "username": "johndoe" }
GET /api/v1/ai/rate-limit

Cek apakah akun boleh mengirim generate sekarang, dan berapa detik lagi jika belum boleh. Berguna untuk polling mode agar tidak kena error 429.

# Boleh generate sekarang { "can_generate": true, "wait_seconds": 0, "rate_limit_secs": 10 } # Masih harus tunggu { "can_generate": false, "wait_seconds": 6.3, "rate_limit_secs": 10 }
GET /api/v1/ai/token-info

Validasi API Key dan lihat informasi token yang sedang dipakai. Berguna saat pertama setup integrasi untuk memastikan key sudah benar sebelum mulai kirim request lain.

GET /api/v1/ai/token-info X-API-Token: rnv4_xxxxxxxxxxxx { "name": "Studio Utama", "token_last4": "a1b2", "created_at": "2026-01-15 08:00:00", "last_used_at": "2026-06-26 10:30:00", "username": "johndoe" }
GET /api/v1/ai/profile

Profil lengkap akun — saldo, jumlah robot, statistik generate, dan model aktif.

{ "username": "johndoe", "avatar_id": 5, "balance": 49, "robot_count": 2, "total_generations": 120, "generations_today": 5, "member_since": "2026-01-15", "model_key": "gemini-2.5-flash-image", "model_label": "Gemini 2.5 Flash Image", "model_credit_cost": 1 }

avatar_id0 berarti tampilkan inisial huruf pertama username (bukan emoji). Untuk avatar_id lain, lihat GET /api/v1/avatars utk daftar emoji+warnanya.

GET /api/v1/avatars

Daftar avatar yang tersedia — publik, tidak perlu X-API-Token. Gunakan untuk menampilkan ikon profil pengguna (mis. di RNV4 Studio) berdasarkan avatar_id dari /api/v1/ai/profile atau /api/v1/user/me.

Penting untuk aplikasi native (RNV4 Studio dkk): jangan render karakter emoji di field e langsung — aplikasi desktop umumnya tidak punya font emoji terpasang, hasilnya kotak kosong. Gunakan icon_url (URL gambar PNG absolut) dan tampilkan sebagai gambar biasa (mis. Image/PictureBox control). Field ini yang dipakai web (browser) juga, supaya tampilan konsisten di semua platform.
avatar_id 0Tampilkan inisial huruf pertama username, bukan gambar dari array ini
avatar_id NAmbil avatars[N] langsung (indeks array = avatar_id, bukan N-1)
{ "ok": true, "avatars": [ { "e": "🐱", "c": "#f97316", "icon_url": "https://app.ruskomponen.uk/static/img/avatars/0.png" }, { "e": "🐶", "c": "#eab308", "icon_url": "https://app.ruskomponen.uk/static/img/avatars/1.png" }, { "e": "🐸", "c": "#22c55e", "icon_url": "https://app.ruskomponen.uk/static/img/avatars/2.png" } // ... total 20 entri, indeks 0..19 ] }

icon_url = URL gambar PNG (72×72px, rekomendasi utama), c = warna hex latar lingkaran, e = karakter emoji (khusus web, opsional). Contoh render: avatar_id=5avatars[5] → gambar penguin, warna latar #3b82f6.

GET /api/v1/ai/robots

Daftar robot yang terhubung ke akun beserta status online real-time. Gunakan untuk menampilkan indikator koneksi robot di aplikasi Anda.

onlinetrue jika robot mengirim heartbeat dalam 15 menit terakhir
firmwareVersi firmware aktif robot, misal v1.9.35
stateState mesin robot: 0 idle, 1 ready, 2 working
{ "robots": [ { "robot_id": "RNV4-A1B2C3", "online": true, "firmware": "v1.9.35", "state": 1, "claimed_at": "2026-01-15 08:00:00" }, { "robot_id": "RNV4-D4E5F6", "online": false, "firmware": "v1.9.34", "state": 0, "claimed_at": "2026-03-01 10:30:00" } ] }
GET /api/v1/credit/topup/{topup_id}/status

Poll status pembayaran topup. Gunakan ini jika tidak menggunakan webhook topup.success. topup_id didapat dari response saat membuat topup.

pendingMenunggu pembayaran / bukti transfer
reviewingBukti sedang diverifikasi admin
confirmedDikonfirmasi — kredit sudah ditambah
failedPembayaran gagal di gateway (Tripay)
expiredBatas waktu pembayaran habis
refundedDana dikembalikan — kredit dipotong kembali
rejectedDitolak admin (manual topup)
{ "ok": true, "topup_id": 5, "status": "paid", "credits": 55, "amount_idr": 50000, "paid_at": "2026-06-25 10:00:00" }
GET /api/v1/credit/topup/by-ref/{payment_ref}/status

Cek status topup berdasarkan payment_ref (merchant reference Tripay) — berguna di halaman return URL setelah redirect dari DANA, OVO, atau metode redirect lainnya, di mana Anda tidak menyimpan topup_id secara lokal.

GET /api/v1/credit/topup/by-ref/RNV4-20260625-0005/status { "ok": true, "status": "confirmed", "credits": 55, "amount_idr": 50000, "paid_at": "2026-06-25 10:00:00" }
POST /rnv4/share

Upload foto (misalnya hasil /api/v1/ai/generate atau foto apa pun) dan dapatkan link share publik yang bisa di-scan pelanggan via QR code. Masa aktif link diatur admin (5 menit s/d 72 jam, atau Unlimited/tanpa batas waktu) — jangan asumsikan durasi tetap, selalu baca dari field response di bawah.

Request (multipart/form-data)

FieldTipeKeterangan
filefile File gambar, maks 5MB wajib. Selalu disajikan ulang sebagai image/png apa pun format aslinya.

Header X-API-Token

Sertakan header ini agar upload tercatat sebagai milik akun Anda — foto akan muncul di Share History dan memicu webhook share.created. Beberapa saat admin dapat mengaktifkan pengaturan yang mewajibkan token ini (upload tanpa token/token tidak valid ditolak 401) — selalu sertakan token agar integrasi Anda tidak terpengaruh perubahan pengaturan tersebut.

curl -X POST https://app.ruskomponen.uk/rnv4/share \ -H "X-API-Token: rnv4-your-token-here" \ -F "[email protected]" # Respons saat admin set masa aktif TERBATAS (mis. 12 jam) { "url": "https://ruskomponen.uk/share/aBcDeFgH1234", "token": "aBcDeFgH1234", "expires_h": 12, "expires_m": 720, "unlimited": false } # Respons saat admin set "Unlimited" — perhatikan expires_h bernilai null, BUKAN angka { "url": "https://ruskomponen.uk/share/aBcDeFgH1234", "token": "aBcDeFgH1234", "expires_h": null, "expires_m": 0, "unlimited": true }
Selalu cek field unlimited sebelum memakai expires_h/expires_m — kalau true, kedua field itu bukan durasi nyata (expires_h=null, expires_m=0), bukan berarti "sudah kedaluwarsa".

Error

400bad_requestField file tidak ada, kosong, atau melebihi 5MB
401unauthorizedToken wajib (diaktifkan admin) tapi tidak dikirim / tidak valid
GET /api/v1/share/history

Daftar foto yang di-share milik akun Anda (paginasi 20 per halaman). Mendukung auth JWT (browser) maupun X-API-Token (robot/aplikasi).

GET /api/v1/share/history?page=1 X-API-Token: rnv4_xxxxxxxxxxxx { "ok": true, "total": 12, "page": 1, "per_page": 20, "items": [ { "token": "aBcDeFgH1234", "url": "https://ruskomponen.uk/share/aBcDeFgH1234", "image_url": "https://ruskomponen.uk/share/aBcDeFgH1234/image", "qr_url": "https://ruskomponen.uk/share/aBcDeFgH1234/qr.png", "upload_time": "2026-06-26T10:30:00+00:00", "expires_at": "2026-06-27T10:30:00+00:00", "is_unlimited": false, "is_expired": false } ] }
DELETE /api/v1/share/{token}

Hapus foto share milik Anda. Hanya pemilik (akun yang mengupload) yang bisa menghapus.

DELETE /api/v1/share/aBcDeFgH1234 X-API-Token: rnv4_xxxxxxxxxxxx { "ok": true }
Polling: Panggil /api/v1/share/history secara berkala (misalnya setiap 30 detik) untuk memperbarui daftar foto di aplikasi Anda. Atau gunakan webhook share.created untuk notifikasi real-time setiap kali foto baru di-share.

Endpoint tambahan (publik, tanpa auth):

GET /share/{token}/qr.png → QR code PNG (200×200) GET /share/{token}/image → foto PNG asli GET /share/{token} → halaman web untuk pelanggan scan

Webhook

Setup Webhook

Webhook memungkinkan server mengirim notifikasi real-time ke URL Anda saat event tertentu terjadi, tanpa perlu polling.

  1. 1Di Dashboard → tab Developer → bagian Webhook → klik Tambah
  2. 2Masukkan URL endpoint yang akan menerima POST request dari server kami
  3. 3Pilih events yang ingin Anda terima
  4. 4Simpan Webhook Secret yang muncul — hanya ditampilkan sekali, dipakai untuk verifikasi signature
  5. 5Klik Test untuk kirim payload percobaan ke URL Anda
Endpoint Anda harus merespons HTTP 2xx dalam waktu 10 detik. Jika gagal, server akan retry hingga 3 kali (delay: 10 detik → 60 detik → 300 detik).

Events

ai.generate.processing proses

Gemini mulai memproses gambar. Gunakan untuk menampilkan indikator "sedang diproses..." tanpa perlu poll status.

ai.generate.done sukses

Generate gambar AI selesai dan hasil siap diunduh.

ai.generate.failed gagal

Generate gagal (timeout Gemini, error API, dll). Kredit dikembalikan otomatis.

topup.success sukses

Topup kredit berhasil dikonfirmasi (Tripay atau manual). Kredit sudah ditambah ke saldo.

topup.pending menunggu

Topup baru dibuat — menunggu pembayaran. Berguna untuk menampilkan notifikasi "pembayaran sedang diproses" di aplikasi Anda.

topup.reviewing review

Bukti transfer manual berhasil diunggah user — sedang menunggu konfirmasi admin. Gunakan ini untuk menampilkan status "bukti sedang diverifikasi".

topup.failed gagal

Pembayaran Tripay gagal di sisi gateway (kartu ditolak, saldo tidak cukup, dll). Kredit tidak ditambahkan.

topup.expired kadaluarsa

Batas waktu pembayaran Tripay habis (default 24 jam). User perlu membuat topup baru.

topup.refunded refund

Penting: Dana dikembalikan oleh Tripay setelah sebelumnya PAID. Kredit yang sudah ditambahkan akan dipotong kembali dari saldo. Tangani event ini untuk memperbarui tampilan saldo di aplikasi Anda.

topup.rejected ditolak

Topup ditolak oleh admin, disertai alasan penolakan. Kredit tidak ditambahkan.

credit.low peringatan

Saldo kredit turun di bawah threshold setelah generate selesai. Gunakan ini untuk memperingatkan user agar topup sebelum kehabisan. Threshold default: 5 kredit.

share.created share

Foto berhasil di-share oleh robot menggunakan API Key. Payload berisi URL share dan URL QR code yang siap ditampilkan di aplikasi Anda — tanpa perlu polling history.

{ "event": "share.created", "user_id": 42, "data": { "token": "aBcDeFgH1234", "url": "https://ruskomponen.uk/share/aBcDeFgH1234", "qr_url": "https://ruskomponen.uk/share/aBcDeFgH1234/qr.png", "expires_at": "2026-06-27T10:30:00+00:00", "unlimited": false }, "timestamp": "2026-06-26T10:30:00Z" }

Format Payload

Server mengirim POST ke URL Anda dengan header dan body berikut:

Headers

Content-Typeapplication/json
X-RNV4-Signaturesha256=<hmac> — HMAC-SHA256 dari body menggunakan webhook secret
X-RNV4-EventNama event, misal ai.generate.done
X-RNV4-DeliveryID unik delivery ini (hex string)
X-RNV4-TimestampUnix timestamp saat pengiriman

Body — ai.generate.processing

{ "event": "ai.generate.processing", "timestamp": "2026-06-25T10:05:00Z", "delivery_id": "f6a7b8c9", "data": { "gen_id": 42, "prompt": "Ubah foto ini menjadi line art" } }

Body — ai.generate.done

{ "event": "ai.generate.done", "timestamp": "2026-06-25T10:05:30Z", "delivery_id": "a1b2c3d4", "data": { "gen_id": 42, "robot_id": "RNV4-001", "prompt": "Ubah foto ini menjadi line art", "result_url": "https://app.ruskomponen.uk/api/v1/ai/result/42?token=xxx", "credits_used": 1, "model": "gemini-2.5-flash-image", "done_at": "2026-06-25T10:05:30Z" } }

Body — ai.generate.failed

{ "event": "ai.generate.failed", "timestamp": "2026-06-25T10:05:30Z", "delivery_id": "b2c3d4e5", "data": { "gen_id": 42, "prompt": "Ubah foto ini menjadi line art", "error": "Gemini API timeout after 30s" } }

Body — topup.success

{ "event": "topup.success", "timestamp": "2026-06-25T10:00:00Z", "delivery_id": "c3d4e5f6", "data": { "topup_id": 5, "payment_ref": "TRP1A2B3C4D5", "credits": 55, "amount_idr": 50000, "gateway": "tripay", "paid_at": "2026-06-25T10:00:00Z" } }

Body — topup.pending

{ "event": "topup.pending", "timestamp": "2026-06-25T09:59:00Z", "delivery_id": "d4e5f6a7", "data": { "topup_id": 5, "credits": 55, "amount_idr": 50000, "method": "QRIS", "status": "pending", "created_at": "2026-06-25 09:59:00" } }

Body — topup.reviewing

{ "event": "topup.reviewing", "timestamp": "2026-06-25T10:05:00Z", "delivery_id": "h8i9j0k1", "data": { "topup_id": 5, "payment_ref": "RNV4-20260625-0005", "credits": 55, "amount_idr": 50000, "submitted_at": "2026-06-25 10:05:00" } }

Body — topup.failed

{ "event": "topup.failed", "timestamp": "2026-06-25T10:10:00Z", "delivery_id": "i9j0k1l2", "data": { "topup_id": 5, "payment_ref": "TRP1A2B3C4D5", "credits": 55, "amount_idr": 50000, "method": "QRIS", "failed_at": "2026-06-25 10:10:00" } }

Body — topup.expired

{ "event": "topup.expired", "timestamp": "2026-06-26T09:59:00Z", "delivery_id": "j0k1l2m3", "data": { "topup_id": 5, "payment_ref": "TRP1A2B3C4D5", "credits": 55, "amount_idr": 50000, "method": "QRIS", "expired_at": "2026-06-26 09:59:00" } }

Body — topup.refunded

Perhatian: Kredit sudah dipotong kembali dari saldo saat event ini dikirim. Perbarui tampilan saldo di aplikasi Anda.
{ "event": "topup.refunded", "timestamp": "2026-06-26T11:00:00Z", "delivery_id": "k1l2m3n4", "data": { "topup_id": 5, "payment_ref": "TRP1A2B3C4D5", "credits": 55, "amount_idr": 50000, "method": "QRIS", "refunded_at": "2026-06-26 11:00:00", "note": "Kredit telah dipotong kembali dari saldo akun." } }

Body — topup.rejected

{ "event": "topup.rejected", "timestamp": "2026-06-25T10:30:00Z", "delivery_id": "g7h8i9j0", "data": { "topup_id": 5, "credits": 55, "amount_idr": 50000, "reason": "Bukti transfer tidak sesuai", "rejected_at": "2026-06-25 10:30:00" } }

Body — credit.low

{ "event": "credit.low", "timestamp": "2026-06-25T10:05:31Z", "delivery_id": "e5f6a7b8", "data": { "balance": 3, "threshold": 5, "reason": "after_generate" } }

Body — share.created

{ "event": "share.created", "timestamp": "2026-06-26T10:30:00Z", "delivery_id": "f7a8b9c0", "data": { "token": "aBcDeFgH1234", "url": "https://ruskomponen.uk/share/aBcDeFgH1234", "qr_url": "https://ruskomponen.uk/share/aBcDeFgH1234/qr.png", "expires_at": "2026-06-27T10:30:00+00:00", "unlimited": false } }

Verifikasi Signature

Selalu verifikasi header X-RNV4-Signature untuk memastikan request berasal dari server kami dan tidak dimanipulasi. Signature adalah HMAC-SHA256 dari raw request body menggunakan webhook secret Anda.

HMAC-SHA256(key=webhook_secret, message=raw_body)
Penting: Gunakan raw body bytes untuk komputasi HMAC, bukan parsed JSON. Jangan pernah memproses request webhook tanpa verifikasi signature.

Contoh Kode

Flask webhook receiver + verifikasi signature

import hashlib, hmac, json from flask import Flask, request, jsonify app = Flask(__name__) WEBHOOK_SECRET = "webhook-secret-anda-di-sini" def verify_signature(raw_body: bytes, header_sig: str) -> bool: expected = "sha256=" + hmac.new( WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, header_sig) @app.route("/webhook", methods=["POST"]) def webhook(): sig = request.headers.get("X-RNV4-Signature", "") if not verify_signature(request.get_data(), sig): return jsonify({"error": "Invalid signature"}), 401 event = request.headers.get("X-RNV4-Event") data = request.json.get("data", {}) if event == "ai.generate.done": gen_id = data["gen_id"] result_url = data["result_url"] print(f"Generate #{gen_id} selesai: {result_url}") # Download gambar, simpan ke storage, notif user, dll elif event == "ai.generate.failed": print(f"Generate #{data['gen_id']} gagal: {data['error']}") elif event == "topup.success": print(f"Topup #{data['topup_id']} berhasil: {data['credits']} kredit") return jsonify({"ok": True})

Kirim generate request dari Python

import requests, time API_TOKEN = "rnv4-token-anda" BASE_URL = "https://app.ruskomponen.uk" # 1. Kirim foto with open("foto.jpg", "rb") as f: resp = requests.post( f"{BASE_URL}/api/v1/ai/generate", headers={"X-API-Token": API_TOKEN}, files={"photo": f}, data={"prompt": "Buat line art", "robot_id": "APP-001"}, ) gen = resp.json() gen_id = gen["gen_id"] print(f"Job {gen_id} queued, sisa kredit: {gen['balance']}") # 2. Poll status while True: s = requests.get( f"{BASE_URL}/api/v1/ai/status/{gen_id}", headers={"X-API-Token": API_TOKEN}, ).json() if s["status"] == "done": token = s["result_token"] # 3. Download hasil img = requests.get(f"{BASE_URL}/api/v1/ai/result/{gen_id}?token={token}") with open("hasil.png", "wb") as out: out.write(img.content) print("Selesai! hasil.png") break elif s["status"] == "failed": print("Gagal:", s.get("error_msg")) break time.sleep(3)

Referensi

Error Codes

HTTPKodeKeterangan
400bad_requestParameter tidak valid atau field wajib kosong
401unauthorizedAPI Token tidak valid atau tidak ada
402payment_requiredKredit tidak cukup untuk generate
403forbiddenResource ditemukan tapi bukan milik akun Anda (mis. hapus share orang lain)
404not_foundgen_id tidak ditemukan atau bukan milik akun Anda
410goneHasil sudah expired (>1 jam setelah selesai)
429rate_limitedTerlalu banyak request. Tunggu beberapa detik.

Rate Limits

Endpoint / AksiLimit per akun
POST /ai/generate1 request per interval (default: 10 detik). Cek nilai aktual via GET /api/v1/ai/rate-limit
GET /ai/status60 request / menit — cukup untuk poll tiap detik
GET /ai/queue30 request / menit
GET /ai/balance30 request / menit
GET /ai/profile20 request / menit
GET /ai/token-info20 request / menit
GET /ai/robots20 request / menit
GET /ai/rate-limit30 request / menit

Saat kena limit, server mengembalikan HTTP 429. Implementasikan exponential backoff — jangan langsung retry dalam loop ketat.

Keamanan

Panduan ini membantu Anda mengintegrasikan API dengan aman. Keamanan yang baik tidak bergantung pada informasi ini disembunyikan — justru transparansi membantu developer membangun integrasi yang benar.

Simpan API Key dengan Aman

  • Simpan di environment variable atau secrets manager, bukan hardcode di source code
  • Jangan commit key ke git — gunakan .env dan tambahkan ke .gitignore
  • Buat key terpisah per aplikasi — mudah di-revoke jika satu bocor
  • Jangan tampilkan key di log, error message, atau response ke client

Selalu Verifikasi Webhook Signature

Tanpa verifikasi, endpoint Anda bisa menerima request palsu dari siapa saja yang mengetahui URL Anda. Selalu validasi header X-RNV4-Signature sebelum memproses payload.

# Python — verifikasi wajib sebelum proses import hmac, hashlib def is_valid(secret: str, body: bytes, sig_header: str) -> bool: expected = "sha256=" + hmac.new( secret.encode(), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, sig_header) # Tolak jika tidak valid if not is_valid(WEBHOOK_SECRET, request.data, request.headers.get("X-RNV4-Signature", "")): return "Forbidden", 403

Gunakan HTTPS

  • Semua request ke API harus via HTTPS — API key dikirim di header dan bisa dicuri jika pakai HTTP biasa
  • URL webhook Anda juga harus HTTPS agar payload tidak bisa disadap di jaringan

Respons Terhadap Rate Limit (429)

Jika menerima HTTP 429, hentikan request dan tunggu sebelum coba lagi. Jangan retry dalam loop tanpa jeda — ini tidak akan berhasil dan memperburuk situasi.

# Exponential backoff yang benar import time def poll_with_backoff(gen_id, api_key, max_tries=10): delay = 3 for _ in range(max_tries): r = requests.get(f"{BASE}/status/{gen_id}", headers={"X-API-Token": api_key}) if r.status_code == 429: time.sleep(delay) delay = min(delay * 2, 60) # maks 60 detik continue data = r.json() if data["status"] in ("done", "failed"): return data time.sleep(3) # interval normal polling return None

Sistem Kredit sebagai Perlindungan Ekonomi

Setiap generate memotong kredit akun — spam generate langsung merugikan pemilik akun sendiri. Jika kredit habis, server mengembalikan 402 Payment Required. Daftarkan event credit.low di webhook untuk mendapat peringatan sebelum kehabisan.

Temukan celah keamanan? Laporkan ke [email protected] sebelum mempublikasikannya. Kami menghargai responsible disclosure.
© 2026 Ruskomponen Beranda Dashboard Status Layanan FAQ