Error Handling
PortalData API menggunakan kode error standar HTTP dan format response yang konsisten. Pelajari cara menangani error dengan baik di aplikasi Anda.
Format Response Error
Semua error response menggunakan format JSON yang konsisten:
{
"error": "error_code",
"message": "Human readable error message",
"details": {
// Optional: additional error details
}
}| Field | Tipe | Deskripsi |
|---|---|---|
error | string | Kode error yang dapat digunakan untuk handling programatik |
message | string | Pesan error yang dapat dibaca manusia |
details | object | undefined | Detail tambahan (misalnya, field mana yang invalid) |
HTTP Status Codes
API menggunakan HTTP status code standar untuk menunjukkan hasil request:
| Status | Nama | Deskripsi |
|---|---|---|
| 200 | OK | Request berhasil |
| 400 | Bad Request | Request tidak valid (parameter salah, dll) |
| 401 | Unauthorized | API key tidak ada atau tidak valid |
| 403 | Forbidden | Tidak memiliki akses ke resource |
| 404 | Not Found | Resource tidak ditemukan |
| 429 | Too Many Requests | Rate limit atau kuota bulanan terlampaui |
| 500 | Internal Server Error | Error di sisi server |
Kode Error
Berikut adalah semua kode error yang mungkin dikembalikan API:
unauthorizedHTTP 401API key tidak ada, tidak valid, atau sudah expired.
Penyebab umum:
- Header
X-API-Keytidak disertakan - API key salah ketik atau sudah dihapus
- API key sudah expired
{
"error": "unauthorized",
"message": "Invalid or expired API key."
}rate_limit_exceededHTTP 429Batas request per menit terlampaui. Tunggu sebelum retry.
Cara menangani:
- Gunakan nilai dari header
Retry-Afteruntuk menunggu - Implementasi exponential backoff
- Pertimbangkan upgrade tier jika sering terjadi
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Upgrade your plan for higher limits.",
"retry_after": 30,
"upgrade_url": "https://portaldata.id/pricing"
}monthly_limit_exceededHTTP 429Kuota bulanan habis. Request tidak akan diproses sampai bulan berikutnya atau upgrade tier.
Cara menangani:
- Upgrade tier untuk mendapatkan kuota lebih besar
- Tunggu sampai awal bulan berikutnya
- Implementasi caching untuk mengurangi request
{
"error": "monthly_limit_exceeded",
"message": "Monthly request limit of 1000 exceeded. Please upgrade your plan."
}not_foundHTTP 404Resource yang diminta tidak ditemukan.
Penyebab umum:
- ID atau kode resource salah
- Resource sudah dihapus atau tidak aktif
- Typo pada URL endpoint
{
"error": "not_found",
"message": "Region not found"
}validation_errorHTTP 400Parameter request tidak valid.
Penyebab umum:
- Nilai parameter di luar range yang diizinkan
- Format parameter salah
- Parameter wajib tidak disertakan
{
"error": "validation_error",
"message": "Validation failed",
"details": {
"errors": {
"level": ["Invalid level. Must be one of: province, city, district, village"],
"limit": ["Limit must be between 1 and 100"]
}
}
}internal_errorHTTP 500Terjadi error di sisi server. Tim kami akan otomatis diberitahu.
Cara menangani:
- Retry dengan exponential backoff
- Cek status page jika error terus terjadi
- Hubungi support jika masalah berlanjut
{
"error": "internal_error",
"message": "An internal error occurred"
}Contoh Error Handling
async function fetchData(endpoint) {
try {
const response = await fetch(`https://api.portaldata.id/api/v1${endpoint}`, {
headers: { 'X-API-Key': process.env.PORTALDATA_API_KEY }
});
// Parse response body
const data = await response.json();
// Check if response is OK
if (!response.ok) {
// Handle specific error codes
switch (data.error) {
case 'unauthorized':
throw new Error('API key tidak valid. Periksa konfigurasi.');
case 'rate_limit_exceeded':
const retryAfter = data.retry_after || 30;
console.log(`Rate limited. Retry dalam ${retryAfter} detik.`);
await sleep(retryAfter * 1000);
return fetchData(endpoint); // Retry
case 'monthly_limit_exceeded':
throw new Error('Kuota bulanan habis. Silakan upgrade.');
case 'not_found':
return null; // Resource tidak ada, return null
case 'validation_error':
console.error('Validation errors:', data.details?.errors);
throw new Error(`Validasi gagal: ${data.message}`);
case 'internal_error':
throw new Error('Server error. Coba lagi nanti.');
default:
throw new Error(data.message || 'Unknown error');
}
}
return data;
} catch (error) {
// Network error atau unexpected error
console.error('Request failed:', error);
throw error;
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}import time
import requests
class PortalDataError(Exception):
"""Base exception for PortalData API errors"""
def __init__(self, error_code, message, details=None):
self.error_code = error_code
self.message = message
self.details = details
super().__init__(message)
class UnauthorizedError(PortalDataError):
pass
class RateLimitError(PortalDataError):
def __init__(self, message, retry_after=30):
super().__init__('rate_limit_exceeded', message)
self.retry_after = retry_after
class NotFoundError(PortalDataError):
pass
def fetch_data(endpoint, max_retries=3):
"""Fetch data from PortalData API with error handling"""
url = f'https://api.portaldata.id/api/v1{endpoint}'
headers = {'X-API-Key': os.environ['PORTALDATA_API_KEY']}
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
data = response.json()
if response.ok:
return data
# Handle errors based on error code
error_code = data.get('error')
message = data.get('message', 'Unknown error')
if error_code == 'unauthorized':
raise UnauthorizedError(error_code, message)
elif error_code == 'rate_limit_exceeded':
retry_after = data.get('retry_after', 30)
if attempt < max_retries - 1:
print(f'Rate limited. Waiting {retry_after}s...')
time.sleep(retry_after)
continue
raise RateLimitError(message, retry_after)
elif error_code == 'monthly_limit_exceeded':
raise PortalDataError(error_code, message)
elif error_code == 'not_found':
raise NotFoundError(error_code, message)
elif error_code == 'validation_error':
raise PortalDataError(error_code, message, data.get('details'))
elif error_code == 'internal_error':
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # Exponential backoff
continue
raise PortalDataError(error_code, message)
else:
raise PortalDataError(error_code, message)
raise Exception('Max retries exceeded')
# Penggunaan
try:
regions = fetch_data('/regions?level=province')
print(regions)
except NotFoundError:
print('Data tidak ditemukan')
except RateLimitError as e:
print(f'Rate limited. Retry dalam {e.retry_after} detik')
except PortalDataError as e:
print(f'API Error: {e.error_code} - {e.message}')Best Practices
Selalu Cek HTTP Status
Jangan hanya parse JSON body. Selalu cek HTTP status code terlebih dahulu untuk menentukan apakah request berhasil.
Handle Error Spesifik
Gunakan field error untuk handling programatik. Field message bisa berubah, tapi error akan konsisten.
Implementasi Retry Logic
Untuk error yang bersifat sementara (429, 500), implementasi retry dengan exponential backoff.
Log Error untuk Debugging
Log error response lengkap (termasuk details) untuk memudahkan debugging masalah di production.