Lompat ke konten utama
Dokumentasi AdForm
llms.txt Pusat Bantuan

Autentikasi

Cara memakai API key AdForm — format key adk_, header X-API-Key, scope yang aktif, bentuk response error, rate limit, dan contoh curl, PHP, dan JavaScript.

Semua endpoint API AdForm yang bisa dipanggil pihak ketiga memakai API key yang dikirim di header X-API-Key. Tidak ada OAuth, tidak ada token yang perlu di-refresh, tidak ada login berjangka.

Satu key terikat ke satu tenant. Konteks tenant ditentukan sepenuhnya oleh key — Anda tidak perlu (dan tidak bisa) mengirim ID tenant sendiri.

#Format key

Teks
adk_0123456789abcdef0123456789abcdef0123456789abcdef
  • Prefix tetap adk_.
  • Diikuti 48 karakter heksadesimal huruf kecil (a–f, 0–9).
  • Total panjang 52 karakter.
  • Server memvalidasi bentuk ini sebelum menyentuh database. Key yang bentuknya tidak cocok langsung ditolak 401 Malformed API key.

Key disimpan di sisi AdForm hanya sebagai hash SHA-256. Nilai aslinya ditampilkan satu kali saat dibuat dan tidak bisa dilihat lagi. Yang tersisa di catatan kami cuma 12 karakter pertama (adk_0123456) sebagai penanda. Kalau key hilang, cabut key lama lalu buat penggantinya.

#Mendapatkan API key

Pemilik atau admin yang mempunyai izin pengaturan integrasi dapat membuat key customers:read langsung dari dashboard:

  1. Buka Pengaturan → Webhook.
  2. Pilih tab API Key.
  3. Isi nama konektor dan tanggal kedaluwarsa.
  4. Klik Buat key, lalu salin nilai lengkap yang muncul. Nilai itu hanya tampil satu kali.

Key dari layar ini sengaja hanya mempunyai scope customers:read, yaitu izin minimum untuk konektor CRM/CS yang perlu melakukan customer lookup. Tanggal kedaluwarsa wajib dipilih saat membuatnya. Simpan key di secret manager atau environment variable server, bukan di kode.

Kalau integrasi Anda membutuhkan scope lain, ajukan lewat Pusat Bantuan dan sebutkan nama tenant, nama integrasi, serta scope yang benar-benar dibutuhkan. Jalur administratif ini tetap mempertahankan kompatibilitas untuk integrasi produk, pesanan, pelanggan, atau tim yang sudah ada, termasuk key lama tanpa tanggal kedaluwarsa.

Untuk mencabut key yang bocor atau tidak lagi dipakai, buka tab API Key yang sama dan klik Cabut. Pencabutan berlaku seketika pada request berikutnya dan tidak dapat dibatalkan.

#Mengirim key

Header, bukan query string:

HTTP
GET /api/products.php?action=embed&id=12 HTTP/1.1
Host: adform.id
X-API-Key: adk_0123456789abcdef0123456789abcdef0123456789abcdef

Jangan pernah menaruh key di URL, di kode frontend, di aplikasi mobile, atau di repositori publik. Key tidak dibatasi per-domain maupun per-IP: siapa pun yang memegangnya bisa memakainya dari mana saja atas nama tenant Anda.

#Scope

Scope menentukan operasi apa yang boleh dipanggil key. Yang benar-benar aktif dan diperiksa server hari ini ada enam:

ScopeMembukaCatatan
products:readGET /api/products.php?action=embedScope default kalau tidak diminta scope lain.
products:writePOST /api/products.php?action=duplicateMembuat produk baru di tenant. Berikan hanya kalau memang perlu.
customers:readPOST /api/customer_lookup.phpPencarian pelanggan lewat nomor telepon. Hanya untuk konektor CRM/CS. Jangan diberikan ke integrasi katalog — scope ini membuka nama, riwayat pesanan, dan pesan pembuka pelanggan. Lihat Pencarian Pelanggan.
orders:readGET /api/order_status.phpMembaca satu pesanan berdasarkan nomornya. Tidak termasuk data pribadi pembeli — nama, nomor, surel, dan alamat berada di balik customers:read.
orders.status:writePATCH /api/order_status.phpMemindahkan status pesanan antara pending dan closing. Tidak bisa menandai lunas, membatalkan, mengubah harga, atau menghapus.
*Semua operasi API keyWildcard, melewati semua pengecekan scope. Hindari kecuali benar-benar butuh.

Aturan pencocokan scope adalah kecocokan string persis. Tidak ada pola glob: products:* bukan scope yang valid dan tidak akan cocok dengan apa pun.

Scope lain bersifat cadangan (reserved), belum aktif. Sistem penerbitan key masih menerima beberapa nama scope lain yang direncanakan untuk endpoint yang belum ada — products:delete, orders:write, customers:write, team:read, team:write. Tidak ada endpoint yang memeriksa scope di luar enam baris tabel di atas, jadi memilikinya tidak memberi akses apa pun. Jangan bangun logika di atas scope selain keenam itu.

Perhatikan bahwa orders:write bukan bentuk luas dari orders.status:write dan tidak mencakupnya. Pencocokan scope adalah kecocokan string persis: key ber-orders:write ditolak 403 oleh /api/order_status.php. Yang Anda perlukan untuk mengubah status adalah orders.status:write.

Satu key bisa memegang beberapa scope sekaligus, misalnya products:read + products:write. Sebaliknya, jangan menumpuk scope yang tidak dipakai: kunci untuk sinkronisasi katalog tidak butuh customers:read, dan kunci untuk konektor CS tidak butuh products:write.

#Response error

Semua error autentikasi berupa JSON dengan satu field error.

StatusBodyPenyebab
401{"error":"API key required (X-API-Key header)"}Header X-API-Key tidak dikirim atau kosong.
401{"error":"Malformed API key"}Bentuk key tidak cocok adk_ + 48 hex huruf kecil.
401{"error":"Invalid API key"}Bentuknya benar tapi tidak dikenal (salah ketik, key sudah dihapus).
401{"error":"API key revoked"}Key sudah dinonaktifkan.
401{"error":"API key expired"}Key punya tanggal kedaluwarsa dan sudah lewat.
403{"error":"Tenant inactive"}Akun tenant pemilik key sedang nonaktif.
403{"error":"Missing scope: products:write"}Key valid, tapi tidak punya scope yang dibutuhkan endpoint. Bentuk pesannya sama untuk scope lain, mis. Missing scope: customers:read.

Contoh scope ditolak:

JSON
{
  "error": "Missing scope: products:write"
}

Pesan hanya menyebut satu scope pertama yang kurang, bukan daftar lengkap. Kalau endpoint butuh beberapa scope dan key Anda kurang dua, perbaiki satu lalu coba lagi.

Perlakukan 401 dan 403 sebagai kesalahan konfigurasi, bukan error sementara — jangan retry otomatis. Yang layak di-retry cuma 429 dan 5xx.

#Rate limit

Dua endpoint, dua aturan yang berbeda. Jangan menyamakan keduanya.

EndpointBatasDihitung per
/api/products.php60 request / 60 detikAlamat IP
/api/customer_lookup.php30 request / 60 detikAPI key
/api/customer_lookup.php600 request / 1 jamAPI key
/api/customer_lookup.php4.000 request / 24 jamAPI key
/api/customer_lookup.php120 request / 60 detikKombinasi API key + alamat IP (rem sekunder)
/api/customer_lookup.php1.200 request / 60 detikAlamat IP saja, sebelum key dibaca

#/api/products.php — per IP

  • Hitungannya per IP, bukan per key. Dua key berbeda dari satu server berbagi jatah yang sama.
  • Jatah itu juga dipakai bersama trafik lain ke endpoint yang sama dari IP tersebut.
  • Kalau ambang terlampaui, IP diblokir sampai jendela berikutnya.

#/api/customer_lookup.php — per key

  • Hitungannya melekat pada API key, bukan pada IP. Menyebar panggilan ke banyak server tidak menambah jatah: satu key tetap satu jatah.
  • Ketiga jendela (menit, jam, hari) berlaku bersamaan. Yang pertama tersentuh yang menolak.
  • Batas 120/60 detik melekat pada pasangan key + IP, bukan pada IP saja. Dua key dari satu IP keluar punya jatah 120 masing-masing.
  • Batas 1.200/60 detik per IP adalah satu-satunya yang dipakai bersama semua key dari IP itu. Ia berjalan sebelum key dibaca dan hanya melindungi server dari banjir.
  • Ada satu rem tambahan berbasis database, 45/60 detik, yang menangkap ledakan permintaan paralel. Klien yang memanggil berurutan tidak akan pernah menyentuhnya.
  • Endpoint ini punya waktu jawab minimum 150 milidetik yang disengaja. Jangan memasang timeout klien di bawah 2 detik.

#Response saat terlampaui

Sama untuk kedua endpoint:

HTTP
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{"error":"Terlalu banyak permintaan. Coba lagi nanti."}

Header Retry-After selalu ikut pada 429 dan berisi jumlah detik, bukan tanggal HTTP. Nilainya adalah lebar jendela yang tersentuh (60, 3600, atau 86400), atau sisa detik blokir kalau Anda mencoba lagi saat masih diblokir. Hormati nilainya.

Kalau integrasi Anda butuh throughput lebih tinggi, hubungi kami dulu sebelum menaikkan beban. Rinciannya di Batas Penggunaan.

#Contoh

#curl

Terminal
curl -sS \
  -H "X-API-Key: adk_0123456789abcdef0123456789abcdef0123456789abcdef" \
  "https://adform.id/api/products.php?action=embed&id=12"

Dengan body JSON (duplikat produk):

Terminal
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"

#PHP

PHP
<?php
$apiKey = getenv('ADFORM_API_KEY'); // jangan hardcode key di dalam kode

$ch = curl_init('https://adform.id/api/products.php?action=embed&id=12');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_HTTPHEADER     => [
        'X-API-Key: ' . $apiKey,
        'Accept: application/json',
    ],
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);

if ($status !== 200) {
    // $data['error'] berisi pesan dari AdForm, mis. "Missing scope: products:read"
    throw new RuntimeException("AdForm API {$status}: " . ($data['error'] ?? $body));
}

echo $data['embed']['form_url'];   // https://adform.id/t/12/kaos-polos-hitam

#JavaScript (Node 18+)

JavaScript
const API_KEY = process.env.ADFORM_API_KEY; // jangan pernah dipakai di browser

async function getEmbed(productId) {
  const res = await fetch(
    `https://adform.id/api/products.php?action=embed&id=${productId}`,
    { headers: { 'X-API-Key': API_KEY, Accept: 'application/json' } }
  );

  const data = await res.json();

  if (!res.ok) {
    // 401/403 = perbaiki konfigurasi. 429 = tunggu Retry-After lalu ulangi.
    const err = new Error(`AdForm API ${res.status}: ${data.error ?? 'unknown error'}`);
    err.status = res.status;
    err.retryAfter = Number(res.headers.get('Retry-After')) || null;
    throw err;
  }

  return data;
}

const produk = await getEmbed(12);
console.log(produk.embed.iframe_with_pixel);

#Praktik yang kami sarankan

  • Satu key per integrasi, diberi nama jelas. Kalau satu bocor, cabut yang itu saja.
  • Minta scope seminimal mungkin. Butuh baca saja? products:read cukup.
  • Simpan key di environment variable atau secret manager, bukan di file konfigurasi yang ikut ter-commit.
  • Log status dan pesan error dari AdForm saat gagal — itu yang kami butuhkan kalau Anda melapor.
  • Rotasi key secara berkala, dan segera setelah ada pergantian orang di tim yang memegangnya.

Ketik minimal dua huruf.

↑↓ pindah Enter buka Esc tutup