DocsError Handling

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
  }
}
FieldTipeDeskripsi
errorstringKode error yang dapat digunakan untuk handling programatik
messagestringPesan error yang dapat dibaca manusia
detailsobject | undefinedDetail tambahan (misalnya, field mana yang invalid)

HTTP Status Codes

API menggunakan HTTP status code standar untuk menunjukkan hasil request:

StatusNamaDeskripsi
200OKRequest berhasil
400Bad RequestRequest tidak valid (parameter salah, dll)
401UnauthorizedAPI key tidak ada atau tidak valid
403ForbiddenTidak memiliki akses ke resource
404Not FoundResource tidak ditemukan
429Too Many RequestsRate limit atau kuota bulanan terlampaui
500Internal Server ErrorError di sisi server

Kode Error

Berikut adalah semua kode error yang mungkin dikembalikan API:

unauthorizedHTTP 401

API key tidak ada, tidak valid, atau sudah expired.

Penyebab umum:

  • Header X-API-Key tidak disertakan
  • API key salah ketik atau sudah dihapus
  • API key sudah expired
{
  "error": "unauthorized",
  "message": "Invalid or expired API key."
}
rate_limit_exceededHTTP 429

Batas request per menit terlampaui. Tunggu sebelum retry.

Cara menangani:

  • Gunakan nilai dari header Retry-After untuk 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 429

Kuota 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 404

Resource 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 400

Parameter 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 500

Terjadi 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

JavaScript
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));
}
Python
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.

Lihat Juga