Semua galat berbentuk JSON dengan satu field: ```json { "error": "Missing scope: products:write" } ``` Sebagian besar endpoint hanya mengirim field `error` itu: tanpa kode galat mesin, tanpa `request_id`, tanpa rincian per-field. Yang membedakan adalah **status HTTP** ditambah teks `error`. **Kecualinya `/api/order_status.php`**, yang mengirim dua field tambahan pada galat yang berasal dari endpoint itu sendiri: ```json { "error": "Pesanan tidak ditemukan.", "code": "order_not_found", "request_id": "9f2c41ab" } ``` `code` adalah penanda tetap yang aman dijadikan cabang program; `request_id` untuk penelusuran saat melapor. Daftar lengkapnya di [Status Pesanan](/docs/api-order-status). Galat yang berasal dari lapisan bersama di depan endpoint — `401` dan `403` dari pemeriksa kunci API, `429` dari pembatas laju — tetap hanya memuat `error`, termasuk untuk endpoint tersebut. **Cabangkan program Anda pada status HTTP, bukan pada teks `error`.** Teks bisa berubah kata; status tidak. Pakai teks untuk log dan untuk pesan ke manusia. Field `success` tidak dikirim pada response galat. Jangan menguji `data.success === false` — uji `res.ok` atau status HTTP. Daftar di bawah dikumpulkan dari kode yang berjalan di produksi. Cakupannya tiga berkas endpoint yang memeriksa API key: `/api/products.php` (`?action=embed` dan `?action=duplicate`), `/api/customer_lookup.php`, serta `/api/order_status.php`. ## Ringkasan status | Status | Arti | Ulangi otomatis? | |---|---|---| | `200` | Berhasil, operasi baca. | — | | `201` | Berhasil, objek baru dibuat. | — | | `400` | Permintaan Anda salah bentuk atau tidak lolos validasi. | Tidak. Perbaiki request. | | `401` | Masalah pada API key. | Tidak. Perbaiki konfigurasi. | | `403` | Key valid, tapi tidak berhak. | Tidak. Perbaiki scope atau status akun. | | `404` | Objek tidak ada di tenant pemilik key. | Tidak. | | `405` | Method HTTP salah. **Hanya `/api/customer_lookup.php`**, yang menerima `POST` saja. | Tidak. Perbaiki request. | | `413` | Body permintaan lebih dari 4 KB. **Hanya `/api/customer_lookup.php`**. | Tidak. Perbaiki request. | | `429` | Batas laju terlampaui. | Ya, setelah menunggu sesuai `Retry-After`. | | `500` | Kesalahan di sisi AdForm. | Untuk operasi baca ya, dengan jeda membesar. Untuk operasi tulis, periksa dulu. | Tidak ada `405` pada `/api/products.php`: yang menentukan operasi di sana adalah nilai `action`, bukan method HTTP. `/api/customer_lookup.php` sebaliknya — ia menolak semua method selain `POST` dengan `405`, supaya nomor telepon tidak pernah masuk query string. Tidak ada `402` pada jalur API key. `/api/customer_lookup.php` juga tidak pernah membalas `500` untuk kegagalan basis data. Kalau pencarian pesanan gagal di sisi kami, responsnya tetap `200` dengan `meta.degraded` bernilai `true`. Lihat [Pencarian Pelanggan](/docs/api-customer-lookup). ## Galat autentikasi Semua diperiksa sebelum request menyentuh data produk, dengan urutan seperti di tabel. | Status | `error` | Penyebab | Yang harus dilakukan | |---|---|---|---| | `401` | `API key required (X-API-Key header)` | Header `X-API-Key` tidak dikirim, atau nilainya string kosong. | Kirim header. Periksa apakah proxy atau klien HTTP Anda membuang header kustom. | | `401` | `Malformed API key` | Nilai key tidak cocok pola `adk_` diikuti 48 karakter heksadesimal huruf kecil. | Periksa key yang tersalin utuh, tanpa spasi/baris baru, dan tidak ter-uppercase. Diperiksa sebelum menyentuh database. | | `401` | `Invalid API key` | Bentuknya benar, tapi tidak ada di database. Salah ketik, atau key sudah dihapus. | Pastikan Anda memakai key untuk lingkungan yang benar. Kalau hilang, minta key baru. | | `401` | `API key revoked` | Key ada, tapi sudah dinonaktifkan. | Key ini tidak akan hidup lagi. Minta key baru. | | `401` | `API key expired` | Key punya tanggal kedaluwarsa dan tanggal itu sudah lewat menurut jam server. | Minta key baru, atau minta key tanpa masa berlaku. | | `403` | `Tenant inactive` | Akun tenant pemilik key sedang nonaktif. | Ini urusan akun, bukan integrasi. Pemilik akun perlu menghubungi AdForm. | Jangan mengulang otomatis satu pun dari daftar ini. Semuanya kesalahan konfigurasi, bukan gangguan sementara — mengulang hanya menghabiskan jatah batas laju Anda. Tidak ada header `WWW-Authenticate` pada response `401`. ## Galat scope | Status | `error` | Penyebab | |---|---|---| | `403` | `Missing scope: products:read` | Key valid, tapi tidak memuat scope `products:read`. Muncul pada `action=embed`. | | `403` | `Missing scope: products:write` | Key valid, tapi tidak memuat scope `products:write`. Muncul pada `action=duplicate`. | | `403` | `Missing scope: customers:read` | Key valid, tapi tidak memuat scope `customers:read`. Muncul pada `POST /api/customer_lookup.php`. | Pencocokan scope adalah **kecocokan string persis**. Tidak ada pola glob — `products:*` bukan scope yang sah dan tidak cocok dengan apa pun. Key yang memuat `*` melewati semua pemeriksaan scope. Pesan hanya menyebut **satu** scope pertama yang kurang, bukan daftar lengkap yang dibutuhkan. `403` di sini permanen sampai key diganti. Minta key baru dengan scope yang sesuai; scope key yang sudah ada tidak bisa ditambah lewat API. ## Galat batas laju | Status | `error` | Header tambahan | |---|---|---| | `429` | `Terlalu banyak permintaan. Coba lagi nanti.` | `Retry-After: ` | ```http HTTP/1.1 429 Too Many Requests Retry-After: 60 Content-Type: application/json; charset=utf-8 {"error":"Terlalu banyak permintaan. Coba lagi nanti."} ``` Ini satu-satunya galat yang layak diulang otomatis. Tunggu selama nilai `Retry-After` (dalam detik), lalu coba lagi, dengan jeda yang membesar kalau tetap kena. Pemeriksaan batas laju berjalan **sebelum** pemeriksaan API key. Request dengan key salah pun tetap memakan jatah. Jangan memakai percobaan berulang untuk menebak key atau ID — Anda hanya akan mengunci IP Anda sendiri. Tidak ada header sisa kuota (`X-RateLimit-Remaining` dan sejenisnya). Rinciannya di [Batas Penggunaan](/docs/batas-penggunaan). ## Galat validasi Semua berstatus `400`. ### Umum | `error` | Operasi | Penyebab | |---|---|---| | `id required` | `embed`, `duplicate` | Query `id` tidak dikirim, bukan angka, atau ≤ 0. | | `Unknown action: ` | — | Nilai `action` bukan `embed` atau `duplicate`. Contoh: `{"error":"Unknown action: list"}`. | | `phone wajib diisi` | Pencarian Pelanggan | Field `phone` tidak ada, bukan nilai skalar, atau hanya spasi. | | `phone tidak valid` | Pencarian Pelanggan | Setelah dibersihkan, nomornya tidak menghasilkan bentuk yang bisa dicocokkan. | ### Khusus pencarian pelanggan Selain dua baris `400` di atas, endpoint ini punya dua status yang tidak muncul di `/api/products.php`: | Status | `error` | Penyebab | |---|---|---| | `405` | `Method not allowed` | Method bukan `POST`. Disengaja: nomor telepon tidak boleh masuk query string. | | `413` | `Payload too large` | Body lebih dari 4 KB, dihitung dari header `Content-Length` maupun dari byte yang benar-benar dibaca. | ### Khusus duplikat produk | `error` | Penyebab | |---|---| | `name required` | Body tidak memuat `name`, atau `name` hanya spasi. Sering kali penyebab sebenarnya adalah body dikirim sebagai form-encoded — server hanya membaca JSON. | | `cannot derive slug from name` | `slug` tidak dikirim dan `name` tidak memuat karakter yang bisa dijadikan slug (misalnya hanya simbol atau hanya karakter non-latin). Kirim `slug` sendiri. | ### Validasi matriks harga varian Pemeriksaan ini ada supaya form tidak pernah menagih harga yang salah. Semua dibalas `400`. `