Konvensi API
Aturan yang berlaku di seluruh endpoint ber-API-key AdForm — base URL, bentuk response sukses dan galat, header, CORS, tanggal, nomor telepon, rupiah, dan kebijakan perubahan.
Halaman ini mengumpulkan aturan yang berlaku sama di semua endpoint AdForm yang bisa dipanggil dengan API key. Kalau satu halaman referensi endpoint tidak menyebut sesuatu, aturan di sini yang berlaku.
Ruang lingkupnya sempit dan kami sebut apa adanya: hari ini ada dua berkas endpoint yang memeriksa API key — /api/products.php (dua operasi, dipilih lewat query action) dan /api/customer_lookup.php (satu operasi, POST saja). Konvensi di bawah ditulis dari perilaku kode yang berjalan di produksi, bukan dari rencana.
Dua pengecualian yang berlaku khusus untuk /api/customer_lookup.php, dan disebutkan lagi di halaman Pencarian Pelanggan:
- Operasinya tidak dipilih lewat
action. Method HTTP-nya yang menentukan, dan hanyaPOSTyang diterima. - Batas lajunya dihitung per API key, bukan per alamat IP.
#Base URL
https://adform.idSemua endpoint berada di bawah /api/ dan diakses sebagai berkas PHP, misalnya /api/products.php. Tidak ada prefix versi (/v1/, /v2/). Operasi dipilih lewat query parameter, bukan lewat segmen path.
Hanya HTTPS. Tidak ada host terpisah untuk sandbox atau staging yang dibuka untuk pihak ketiga — semua panggilan mengenai data produksi tenant Anda.
#Bentuk request
#Autentikasi
Satu header:
X-API-Key: adk_0123456789abcdef0123456789abcdef0123456789abcdefTidak ada skema Authorization: Bearer, tidak ada tanda tangan request, tidak ada nonce. Detail format key ada di Autentikasi.
#Parameter
| Letak | Dipakai untuk |
|---|---|
| Query string | Memilih operasi (action) dan menunjuk objek (id). |
| Body JSON | Semua data yang dikirim (misalnya name, price, variants). |
Server membaca body sebagai JSON mentah dari php://input. Konsekuensinya:
- Body harus JSON yang sah.
application/x-www-form-urlencodeddanmultipart/form-datatidak dibaca — field-nya akan hilang tanpa pesan galat, dan Anda hanya melihat error "wajib diisi" untuk field yang sebenarnya sudah Anda kirim. - Body yang gagal di-parse diperlakukan sebagai objek kosong, bukan sebagai galat tersendiri.
- Kirim
Content-Type: application/json.
Contoh minimum yang benar:
curl -sS -X POST \
-H "X-API-Key: adk_0123456789abcdef0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"name":"Kaos Polos Hitam - Promo Juli"}' \
"https://adform.id/api/products.php?action=duplicate&id=12"#Method HTTP
Gunakan method yang tercantum di halaman referensi tiap operasi (GET untuk baca, POST untuk tulis).
Perilakunya berbeda di dua endpoint, dan bedanya penting:
/api/products.php: method tidak diperiksa. Yang menentukan operasi adalah nilaiaction. Memanggilaction=duplicatedenganGETtidak dibalas405; request itu tetap masuk lalu gagal di validasi body dengan400 name required. Jangan bergantung pada perilaku ini; ia bisa diperketat kapan saja./api/customer_lookup.php: method diperiksa keras. SelainPOST, semuanya dibalas405 {"error":"Method not allowed"}. Ini bukan kelonggaran yang bisa berubah — alasannya menjaga nomor telepon supaya tidak pernah masuk query string, dan karena itu tidak akan dilonggarkan.
#Bentuk response
#Sukses
Selalu JSON, selalu objek datar. Tidak ada envelope data, tidak ada meta, tidak ada pagination.
{
"success": true,
"id": 87,
"slug": "kaos-polos-hitam-promo-juli"
}- Field
successbernilaitrueada di setiap response sukses operasi API key. - Status
200untuk baca,201untuk pembuatan objek baru. - Field baru bisa ditambahkan sewaktu-waktu. Parser Anda harus mengabaikan field yang tidak dikenal, bukan menolaknya.
#Galat
Selalu JSON dengan satu field:
{ "error": "Missing scope: products:write" }Yang perlu Anda ketahui sejak awal:
- Tidak ada kode galat mesin (
code), tidak ada daftar galat per-field, tidak adarequest_id. Yang membedakan jenis galat adalah status HTTP ditambah tekserror. - Teks
errorbercampur bahasa: sebagian Inggris (berasal dari lapisan autentikasi), sebagian Indonesia (berasal dari validasi). Ini kondisi nyata hari ini. - Jangan mencocokkan teks
errorsecara persis untuk logika program. Cabangkan pada status HTTP; pakai teks hanya untuk log dan pesan ke manusia. - Field
successtidak ikut dikirim pada response galat. Jangan mengujidata.success === false; uji status HTTP.
Daftar lengkap ada di Kode Error.
#Header response
Yang dikirim di setiap response:
| Header | Nilai |
|---|---|
Content-Type | application/json; charset=utf-8 |
X-Content-Type-Options | nosniff |
Cache-Control | no-store, no-cache, must-revalidate, private |
Pragma | no-cache |
Tidak ada ETag, tidak ada Last-Modified, tidak ada dukungan permintaan bersyarat (If-None-Match). Response tidak boleh di-cache oleh perantara.
Header Retry-After hanya muncul pada 429. Tidak ada header sisa kuota — lihat Batas Penggunaan.
#Encoding
Response di-encode dengan JSON_UNESCAPED_UNICODE, jadi karakter non-ASCII muncul apa adanya (Ukuran M, bukan U...). Baca sebagai UTF-8.
#CORS — API key tidak bisa dipanggil dari browser
Ini pembatasan yang paling sering menyita waktu orang, jadi kami tulis eksplisit.
Server mengizinkan header berikut pada request lintas-origin:
Access-Control-Allow-Headers: Content-Type, X-Admin-Password, AuthorizationX-API-Key tidak ada di daftar itu. Artinya browser akan menggagalkan preflight OPTIONS untuk setiap fetch() lintas-origin yang membawa X-API-Key, dan request aslinya tidak pernah terkirim.
Kesimpulannya: panggil API key hanya dari server Anda. Itu memang yang benar dari sisi keamanan — key tidak dibatasi per-domain maupun per-IP, jadi key yang masuk ke kode frontend sama saja dengan key yang dibagikan ke publik.
Untuk konteks, origin yang diperlakukan sebagai tepercaya oleh lapisan CORS adalah domain AdForm sendiri (adform.id, www.adform.id, staging.adform.id, form.adstack.id, subdomain .adform.id dan .adstack.id) serta dua origin pengembangan lokal. Origin lain tetap dilayani, tetapi tanpa kredensial. Request OPTIONS dibalas 200 kosong.
#Tanggal dan zona waktu
Keadaan hari ini, tanpa dibagus-baguskan: dua operasi API key yang tersedia tidak mengembalikan satu pun field tanggal. Jadi belum ada format tanggal response yang perlu Anda parse.
Yang tetap perlu Anda pahami karena memengaruhi perilaku sistem:
- Waktu disimpan dalam UTC, dan zona waktu default proses PHP adalah UTC.
- Batas yang berbasis "hari" atau "bulan" dihitung menurut WIB (Asia/Jakarta, UTC+7) lalu dikonversi ke UTC. Misalnya kuota harian paket gratis di-reset pukul 00:00 WIB, yang setara 17:00 UTC hari sebelumnya.
- Kedaluwarsa API key dievaluasi dengan jam server, bukan jam pemanggil.
Kalau Anda mengirim tanggal (saat ini hanya relevan ketika meminta key dengan masa berlaku), pakai bentuk yang tidak ambigu seperti 2027-01-31 23:59:59 atau ISO 8601 lengkap dengan offset.
#Nomor telepon
AdForm menyimpan nomor Indonesia dalam bentuk kanonik berawalan 62, tanpa +, tanpa spasi, tanpa tanda hubung.
Normalisasi yang dijalankan server:
- Semua karakter selain angka dibuang (
+, spasi,-,., kurung). - Kalau hasilnya diawali
0, angka0itu diganti62. - Kalau hasilnya belum diawali
62,62ditambahkan di depan.
| Masukan | Tersimpan |
|---|---|
0812-3456-7890 | 6281234567890 |
+62 812 3456 7890 | 6281234567890 |
812 3456 7890 | 6281234567890 |
Dua catatan penting supaya tidak salah asumsi:
- Normalisasi ini tidak memvalidasi panjang. Masukan
1menghasilkan621. Kalau Anda punya sumber data sendiri, validasi panjang di sisi Anda sebelum mengirim. - Dua operasi API key yang ada sekarang tidak menerima maupun mengembalikan nomor telepon. Konvensi ini berlaku untuk nomor yang masuk lewat form order AdForm dan yang Anda lihat di dashboard.
#Uang dan mata uang
- Mata uang selalu rupiah (IDR). Tidak ada dukungan multi-mata-uang.
- Semua nilai uang adalah bilangan bulat rupiah, dikirim dan diterima sebagai angka JSON:
150000berarti Rp150.000. - Tidak ada desimal, tidak ada satuan sen, tidak ada pemisah ribuan, tidak ada simbol mata uang di dalam nilai.
"Rp150.000"dan150000.00bukan bentuk yang dipakai. - Nilai harga yang Anda kirim di-cast ke integer, dan nilai negatif dijadikan
0.-5000menjadi0, bukan galat. - Harga coret bernilai
0berarti "tanpa harga coret", bukan "gratis".
#Idempotensi dan pengulangan
Belum ada dukungan idempotency key. Operasi tulis tidak idempoten: memanggil duplikat produk dua kali menghasilkan dua produk.
Karena itu:
- Jangan mengulang otomatis operasi tulis yang gagal dengan
5xxatau timeout tanpa memeriksa dulu apakah objeknya sudah terbentuk. - Yang aman diulang otomatis hanya
429(setelah menunggu sesuaiRetry-After) dan operasi baca. - Simpan
iddari response sukses di sisi Anda sebagai penanda bahwa operasi sudah selesai.
#Isolasi tenant
Satu API key terikat ke satu tenant. Konteks tenant sepenuhnya ditentukan oleh key — tidak ada parameter tenant yang bisa (atau perlu) Anda kirim pada jalur API key. ID objek bersifat lokal per tenant: id=12 di tenant Anda tidak ada hubungannya dengan id=12 di tenant lain.
Objek yang bukan milik tenant pemilik key dibalas 404, bukan 403.
#Bagaimana perubahan kontrak diumumkan
Karena API ini belum diversikan, kami memakai aturan berikut:
- Perubahan aditif bisa terjadi kapan saja tanpa pengumuman. Field baru di response, operasi baru, parameter opsional baru. Integrasi Anda harus tahan terhadap field yang tidak dikenal.
- Perubahan yang merusak diumumkan lebih dulu di halaman Changelog situs ini, sebelum berlaku. Termasuk: menghapus atau mengubah nama field, mengubah tipe data, mengubah status HTTP untuk kasus yang sama, memperketat validasi, atau menurunkan batas laju.
- Kalau ada di dokumentasi ini, artinya sudah berjalan di produksi hari ini. Kami sengaja tidak menerbitkan kontrak yang belum final — halaman Webhook contohnya: statusnya ditulis "belum ada" alih-alih dikarang.
- Belum ada kebijakan deprecation formal dengan tenggat tetap. Kalau integrasi Anda kritis, beri tahu kami lewat Pusat Bantuan supaya Anda masuk daftar pemberitahuan langsung.
Halaman ini sendiri mencantumkan tanggal updated di bagian atas. Kalau berubah, ada yang berubah.