Error dan status
Bentuk error, tabel kode, dan cara menanganinya.
Ringkasan
Setiap kegagalan dijawab dengan bentuk JSON yang sama, dan code di dalamnya yang menentukan apa yang harus kamu lakukan.
{
"error": {
"message": "Pesan yang aman untuk ditampilkan.",
"type": "invalid_request_error",
"code": "model_not_found"
}
}Body-nya selalu JSON. Status HTTP memberi tahu kelasnya, dan code memberi tahu yang sebenarnya terjadi. Percayai code, karena satu status bisa berarti dua hal berbeda.
Di permukaan Anthropic (POST /v1/messages), error memakai envelope Anthropic, dan validation di sana 400, bukan 422. Lihat Format Anthropic.
Tabel error
| HTTP | type | code | Artinya |
|---|---|---|---|
| 400 | invalid_request_error | invalid_request | Request tidak bisa diproses. |
| 401 | authentication_error | invalid_api_key | API key hilang atau tidak dikenali. |
| 401 | authentication_error | not_authenticated | Sesi konsol tidak ada atau sudah berakhir. |
| 401 | authentication_error | invalid_credentials | Email atau kata sandi salah saat masuk. |
| 402 | billing_error | insufficient_balance | Saldo dompet tidak cukup. |
| 402 | billing_error | spending_limit_exceeded | Batas belanja key ini sudah habis. |
| 403 | permission_error | forbidden | Tidak diizinkan untuk tindakan ini. |
| 404 | invalid_request_error | model_not_found | Model tidak ada, atau path-nya tidak cocok dengan rute mana pun. |
| 409 | invalid_request_error | conflict | Bertentangan dengan keadaan saat ini. |
| 413 | invalid_request_error | request_too_large | Body request melewati batas ukuran (30 MiB). |
| 422 | invalid_request_error | invalid_request | Body request gagal validasi. |
| 429 | rate_limit_error | rate_limit_exceeded | Batas per menit terlewati. |
| 429 | rate_limit_error | concurrency_exceeded | Batas request bersamaan terlewati. |
| 500 | server_error | internal_error | Kesalahan di sisi kami. |
| 501 | server_error | not_implemented | Rutenya ada, kemampuannya belum dibangun. |
| 502 | server_error | empty_completion | Model menjawab, tokennya sudah diproses, tapi tidak ada output yang bisa dibaca. |
| 502 | server_error | upstream_unavailable | Model di balik layar tidak bisa dihubungi. |
| 504 | server_error | upstream_timeout | Model di balik layar kehabisan waktu. |
Empat yang perlu diperhatikan
empty_completion punya aturan biaya sendiri, dan itu mengikuti apakah tokenmu sempat diproses. Model menjawab 200 tapi tidak menghasilkan apa pun yang bisa dibaca. Kalau ada pemakaian yang tercatat, NusaRouter tetap menagihkan token yang sudah diproses, dan pesan errornya menyebutkan itu. Kalau tidak ada pemakaian yang tercatat, penahanannya dilepas penuh. Ia sengaja dibedakan dari upstream_unavailable: yang satu berarti modelnya jalan tapi tidak bicara, yang lain berarti modelnya tidak bisa dihubungi. Yang pertama kamu ulangi atau naikkan max_tokens; yang kedua kamu tunggu.
not_implemented (501) berbeda dari internal_error (500). 500 berarti sesuatu seharusnya bekerja tapi tidak. Ada gangguan. 501 berarti kemampuan itu memang belum dibangun. Kalau kamu menerima 501, mengulang tidak akan menolong.
model_not_found juga muncul untuk path yang tidak cocok dengan rute mana pun. Jadi kalau kamu salah mengetik path (misalnya lupa /v1), yang muncul model_not_found, bukan pesan tentang rute. Periksa URL-nya sebelum menyimpulkan bahwa id modelmu salah.
concurrency_exceeded berarti ada requestmu yang masih berjalan. Ia berbagi status 429 dengan rate_limit_exceeded, tapi penanganannya berbeda: yang pertama diatasi dengan membatasi paralelisme, yang kedua dengan menunggu.
Yang tidak pernah kami kirim
Pesan error dibangun dari daftar kode yang tetap. Teks mentah dari model di balik layar tidak pernah diteruskan apa adanya, karena teks itu sering memantulkan konteks request. Di dalamnya bisa ada alamat internal, nama host, dan referensi akun.
Kalau sesuatu gagal di sisi kami, yang kamu terima pesan umum dengan status 500. Detailnya masuk ke log kami, bukan ke responsemu. Itu disengaja: detail internal di response cara paling umum sebuah sistem membocorkan dirinya sendiri.
Menangani error
import time
from openai import OpenAI, APIStatusError
client = OpenAI(base_url="https://api.nusarouter.web.id/v1", api_key="sk-nusa-...")
for attempt in range(5):
try:
r = client.chat.completions.create(
model="deepseek-flash-v4.1",
messages=[{"role": "user", "content": "Halo!"}],
)
break
except APIStatusError as e:
body = e.response.json().get("error", {})
code = body.get("code")
# Yang tidak akan membaik dengan diulang: hentikan, jangan ulangi.
if code in ("invalid_request", "model_not_found", "insufficient_balance",
"spending_limit_exceeded", "not_implemented"):
raise
# Batas laju: ikuti retry-after, jangan menebak jedanya.
if e.status_code == 429:
time.sleep(float(e.response.headers.get("retry-after", "1")))
continue
# 5xx dari sisi kami: mundur eksponensial.
time.sleep(2 ** attempt)
else:
raise RuntimeError("menyerah setelah 5 percobaan")Aturan yang paling sering diabaikan: jangan mengulang error yang tidak akan membaik dengan diulang. Mengulang invalid_request atau model_not_found hanya menghasilkan request yang sama gagal berkali-kali, dan untuk error saldo ia menambah beban tanpa mengubah apa pun.
Troubleshooting
| Gejala | Penyebab | Solusi |
|---|---|---|
401 | Key tidak terkirim atau sudah dicabut. | lihat Autentikasi |
404 model_not_found | Id model salah, atau path-nya tidak cocok dengan rute mana pun. | cek id dari katalog, dan cek URL-nya berakhir /v1/chat/completions |
402 | Saldo dompet habis, atau batas belanja key habis. | lihat code-nya: insufficient_balance berarti top up, spending_limit_exceeded berarti naikkan batas key |
429 | Batas per menit atau batas konkurensi. | tunggu retry-after, atau batasi paralelisme; lihat Batas laju |
502 | Model tidak bisa dihubungi (upstream_unavailable), atau menjawab tanpa output yang bisa dibaca (empty_completion). | cek pesan errornya untuk tahu yang mana. Kalau pesannya menyebut biaya, mengulang berarti membayar lagi, jadi perbaiki dulu penyebabnya |
504 | Model kehabisan waktu. | ulangi dengan request yang lebih pendek, atau tanpa streaming untuk jawaban panjang |