Errors
Semua endpoint API publik return JSON error dengan format konsisten:
{ "detail": "Human-readable message", "code": "stable_machine_code", "request_id": "ab12cd34" }
detail: pesan untuk debugging / display ke developer (jangan tampilkan ke end-user mentahan, paraphrase aja).code: stable string yang bisa lo switch on untuk handling.request_id: 16-char hex untuk debug. Sertakan kalau lapor bug ke admin.
HTTP status codes
| Status | Code | Arti |
|---|---|---|
| 200 / 201 | — | Success |
| 400 | bad_request / validation_error |
Request malformed atau field invalid |
| 401 | unauthorized |
API key kosong atau invalid |
| 403 | forbidden |
Akun suspended, role insufficient |
| 404 | not_found |
Invoice / endpoint gak ditemukan |
| 409 | conflict |
(Future) duplicate idempotency dengan payload beda |
| 422 | validation_error |
Pydantic validation fail (pesan detail di field errors) |
| 429 | rate_limit_exceeded |
Too many requests, lihat header Retry-After |
| 500 | internal_error |
Server crash, lapor admin dengan request_id |
| 502 | upstream_error |
BRI portal unreachable / error |
| 503 | service_unavailable |
(Future) maintenance mode |
Validation errors
Field-level errors dari Pydantic:
{ "detail": "Validation failed", "code": "validation_error", "errors": [ { "loc": ["body", "amount"], "msg": "Input should be greater than 0", "type": "greater_than" } ], "request_id": "ab12cd34" }
Pattern: handle di Python
import httpx class GatewayError(Exception): def __init__(self, code: str, detail: str, status: int): self.code = code self.detail = detail self.status = status super().__init__(f"[{code}] {detail}") def call(method, path, **kwargs): r = httpx.request(method, f"{API}{path}", **kwargs) if 200 <= r.status_code < 300: return r.json() try: body = r.json() except ValueError: body = {"detail": r.text, "code": "unknown"} raise GatewayError( code=body.get("code", "unknown"), detail=body.get("detail", "Unknown error"), status=r.status_code, ) try: inv = call("POST", "/api/v1/invoices", json={"amount": 50000}) except GatewayError as e: if e.code == "rate_limit_exceeded": retry() elif e.code == "unauthorized": rotate_key() elif e.code == "upstream_error": # BRI portal lagi error — fallback ke method lain atau retry ... else: log_to_sentry(e) raise
Pattern: handle di Node.js
async function call(method, path, opts = {}) { const r = await fetch(`${API}${path}`, { method, ...opts }); const body = await r.json().catch(() => ({})); if (r.ok) return body; const err = new Error(body.detail || 'Unknown'); err.code = body.code || 'unknown'; err.status = r.status; err.requestId = body.request_id; throw err; } try { const inv = await call('POST', '/api/v1/invoices', { headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 50000 }), }); } catch (e) { if (e.code === 'rate_limit_exceeded') { /* backoff */ } else if (e.code === 'unauthorized') { /* rotate key */ } else throw e; }
Lapor bug
Kalau lo nemu bug atau response gak konsisten, kirim:
request_id(dari error response atau headerX-Request-Id)- Endpoint + method
- Sample request body (mask data sensitif)
- Expected vs actual response
Admin bisa cari log dengan request_id itu.