Penanganan Error
Tabel kode error lengkap, dua format envelope error native, dan panduan retry yang aman.
Error dikembalikan sebagai JSON dengan code stabil yang dapat Anda percabangkan. Format envelope bergantung pada endpoint: endpoint kompatibel OpenAI memakai satu bentuk, endpoint kompatibel Anthropic memakai bentuk lain.
Format error#
/v1/chat/completions dan /v1/responses mengembalikan envelope berbentuk OpenAI:
{
"error": {
"message": "Insufficient balance. Top up PAYG credit or buy a Token Plan for this model.",
"type": "billing_error",
"code": "insufficient_balance",
"request_id": "req_..."
}
}/v1/messages mengembalikan envelope berbentuk Anthropic:
{
"type": "error",
"error": {
"type": "billing_error",
"code": "insufficient_balance",
"message": "Insufficient balance. Top up PAYG credit or buy a Token Plan for this model."
},
"request_id": "req_..."
}Kode error#
| Status | Code | Type | Arti |
|---|---|---|---|
| 400 | invalid_request | invalid_request_error | Body atau parameter request salah format atau tidak valid. Juga dikembalikan saat upstream menolak request dengan 400. |
| 401 | invalid_api_key | authentication_error | Key hilang, salah, dua header bernilai berbeda, atau key terblokir. Responsnya identik di semua kasus. |
| 402 | insufficient_balance | billing_error | Saldo Token Plan dan PAYG Anda tidak dapat menutupi request. Isi ulang sebelum retry. |
| 404 | model_not_found | not_found_error | Identifier model tidak ada atau disembunyikan. |
| 413 | request_too_large | invalid_request | Body request melebihi batas ukuran. |
| 429 | technical_limit | rate_limit_error | Passthrough 429 dari upstream — bukan rate limit zrouter. zrouter tidak menerapkan limit per key; ini menandakan upstream yang sedang di-limit. |
| 500 | internal_error | internal_error | Error internal yang tidak terduga. |
| 502 | upstream_error | upstream_error | Penyedia upstream gagal memproses request. |
| 503 | overloaded | server_error / overloaded_error | Batas kapasitas tercapai (shed in-flight). Retry nanti. Type bernilai server_error di endpoint OpenAI dan overloaded_error di /v1/messages. |
| 503 | circuit_open | upstream_error | Circuit breaker terbuka — upstream sementara tidak tersedia. Header Retry-After disertakan. |
Praktik terbaik retry#
Kegagalan sementara — 429, 5xx, 502, 503 — aman untuk di-retry. Ikuti aturan berikut agar tidak menagih ganda atau memperparah beban:
- Exponential backoff dengan jitter untuk
429,5xx,502, dan503— jangan loop interval tetap yang rapat. - Retry hanya sebelum byte pertama. Begitu stream berjalan, jangan retry di tengahnya.
- Jangan pernah retry
402. Isi saldo terlebih dahulu; retry tidak akan membantu. - Batasi retry pada 2–3 percobaan untuk outage yang berkepanjangan.
- Timeout non-streaming adalah 120 detik. Set timeout klien sedikit di bawahnya agar Anda melihat error yang bersih.
- Settlement idempoten dan debit-after. Anda ditagih hanya setelah inferensi berhasil — request gagal tidak pernah ditagih, sehingga retry yang aman tidak memakan biaya sampai berhasil.
Menangani error dalam kode#
Bercabanglah pada field code yang stabil — jangan pernah parse message yang ditujukan untuk manusia. Kedua keluarga SDK mengekspos status HTTP dan kode error, jadi satu pola mencakup semua endpoint:
import random
import time
from openai import OpenAI, APIStatusError
client = OpenAI(
api_key="YOUR_API_KEY", # key zr_... dari dashboard
base_url="https://api.zrouter.dev/v1",
)
RETRYABLE = {429, 500, 502, 503}
def chat(prompt: str, model: str = "<public-name>") -> str:
for attempt in range(3):
try:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content or ""
except APIStatusError as err:
code = err.code or "" # mis. "insufficient_balance"
if err.status_code == 402 and code == "insufficient_balance":
raise # jangan retry: isi PAYG atau beli Token Plan dulu
if err.status_code in RETRYABLE and attempt < 2:
# exponential backoff dengan jitter, maksimal 3 percobaan
time.sleep(min(2 ** attempt, 8) + random.random())
continue
raise
raise RuntimeError("unreachable")