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
- 1Daftar akun di https://app.ruskomponen.uk/register
- 2Buat API Key di Dashboard → Developer → API Keys
- 3Kirim foto ke
POST /api/v1/ai/generatedengan headerX-API-Token: <token> - 4Poll
GET /api/v1/ai/status/{gen_id}hinggastatus == "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": "..." } }
REST API
/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)
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
}
/api/v1/ai/status/{gen_id}
Cek status satu job generate. Poll endpoint ini hingga status bernilai done atau failed.
Nilai status
pendingAntri, belum diprosesprocessingSedang diproses oleh GeminidoneSelesai — hasil siap diambilfailedGagal — kredit dikembalikan otomatisexpiredHasil 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"
}
/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"
}
]
}
/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.
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
/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..."
}
]
}
/api/v1/ai/balance
Cek saldo kredit dan informasi rate limit.
{
"ok": true,
"balance": 49,
"rate_limit_secs": 10,
"username": "johndoe"
}
/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 }
/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"
}
/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_id — 0 berarti tampilkan inisial huruf pertama username (bukan emoji). Untuk avatar_id lain, lihat GET /api/v1/avatars utk daftar emoji+warnanya.
/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.
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 iniavatar_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=5 → avatars[5] → gambar penguin, warna latar #3b82f6.
/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 terakhirfirmwareVersi firmware aktif robot, misal v1.9.35stateState 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"
}
]
}
/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 transferreviewingBukti sedang diverifikasi adminconfirmedDikonfirmasi — kredit sudah ditambahfailedPembayaran gagal di gateway (Tripay)expiredBatas waktu pembayaran habisrefundedDana dikembalikan — kredit dipotong kembalirejectedDitolak admin (manual topup){
"ok": true,
"topup_id": 5,
"status": "paid",
"credits": 55,
"amount_idr": 50000,
"paid_at": "2026-06-25 10:00:00"
}
/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"
}
Webhook
Setup Webhook
Webhook memungkinkan server mengirim notifikasi real-time ke URL Anda saat event tertentu terjadi, tanpa perlu polling.
- 1Di Dashboard → tab Developer → bagian Webhook → klik Tambah
- 2Masukkan URL endpoint yang akan menerima POST request dari server kami
- 3Pilih events yang ingin Anda terima
- 4Simpan Webhook Secret yang muncul — hanya ditampilkan sekali, dipakai untuk verifikasi signature
- 5Klik Test untuk kirim payload percobaan ke URL Anda
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/jsonX-RNV4-Signaturesha256=<hmac> — HMAC-SHA256 dari body menggunakan webhook secretX-RNV4-EventNama event, misal ai.generate.doneX-RNV4-DeliveryID unik delivery ini (hex string)X-RNV4-TimestampUnix timestamp saat pengirimanBody — 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
{
"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)
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)
Express.js webhook receiver + verifikasi signature
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = 'webhook-secret-anda-di-sini';
// Pakai raw body untuk verifikasi signature
app.use('/webhook', express.raw({ type: 'application/json' }));
function verifySignature(rawBody, headerSig) {
const expected = 'sha256=' + crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(headerSig || '')
);
}
app.post('/webhook', (req, res) => {
const sig = req.headers['x-rnv4-signature'] || '';
if (!verifySignature(req.body, sig)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const payload = JSON.parse(req.body);
const event = payload.event;
const data = payload.data;
if (event === 'ai.generate.done') {
console.log('Generate selesai:', data.gen_id, data.result_url);
// fetch(data.result_url, ...) untuk download gambar
} else if (event === 'topup.success') {
console.log('Topup berhasil:', data.credits, 'kredit');
}
res.json({ ok: true });
});
app.listen(3000, () => console.log('Webhook server running on :3000'));
PHP webhook receiver
<?php
$WEBHOOK_SECRET = 'webhook-secret-anda-di-sini';
// Baca raw body
$rawBody = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_RNV4_SIGNATURE'] ?? '';
$event = $_SERVER['HTTP_X_RNV4_EVENT'] ?? '';
// Verifikasi signature
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $WEBHOOK_SECRET);
if (!hash_equals($expected, $sig)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
$payload = json_decode($rawBody, true);
$data = $payload['data'] ?? [];
if ($event === 'ai.generate.done') {
$genId = $data['gen_id'];
$resultUrl = $data['result_url'];
// Download gambar: file_get_contents($resultUrl)
error_log("Generate #$genId selesai: $resultUrl");
} elseif ($event === 'topup.success') {
error_log("Topup berhasil: {$data['credits']} kredit");
}
http_response_code(200);
echo json_encode(['ok' => true]);
Referensi
Error Codes
bad_requestParameter tidak valid atau field wajib kosongunauthorizedAPI Token tidak valid atau tidak adapayment_requiredKredit tidak cukup untuk generateforbiddenResource ditemukan tapi bukan milik akun Anda (mis. hapus share orang lain)not_foundgen_id tidak ditemukan atau bukan milik akun AndagoneHasil sudah expired (>1 jam setelah selesai)rate_limitedTerlalu banyak request. Tunggu beberapa detik.Rate Limits
POST /ai/generate1 request per interval (default: 10 detik). Cek nilai aktual via GET /api/v1/ai/rate-limitGET /ai/status60 request / menit — cukup untuk poll tiap detikGET /ai/queue30 request / menitGET /ai/balance30 request / menitGET /ai/profile20 request / menitGET /ai/token-info20 request / menitGET /ai/robots20 request / menitGET /ai/rate-limit30 request / menitSaat 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
.envdan 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.