Masalah integrasi API jarang muncul karena endpoint benar-benar tidak ada. Biasanya masalah muncul karena kontrak tidak ditulis cukup jelas: field mana wajib, error seperti apa yang stabil, apakah request aman di-retry, bagaimana verifikasi webhook, dan perubahan apa yang masih kompatibel. Engineer sering lebih mudah mengkritik desain yang sudah ada daripada merancang dari nol. Kabar baiknya, insting review itu bisa diubah menjadi checklist kontrak API yang sistematis.
Artikel ini fokus pada cara menulis dan me-review kontrak API sebelum tim backend, frontend, mobile, atau partner eksternal mulai integrasi. Tujuannya bukan membuat dokumen tebal, tetapi memastikan ada keputusan eksplisit untuk hal-hal yang paling sering memicu bug produksi.
Mengapa checklist kontrak API lebih berguna daripada spesifikasi yang terlalu umum
Dokumen API sering terlihat lengkap karena berisi daftar endpoint, contoh payload, dan status code. Namun integrasi tetap gagal jika pertanyaan penting tidak terjawab. Contoh:
- Apakah field boleh hilang, atau hanya boleh bernilai
null? - Jika client timeout, apakah aman mengirim ulang request yang sama?
- Apakah format error konsisten di semua endpoint?
- Jika provider mengirim webhook dua kali, bagaimana sistem penerima harus bereaksi?
- Perubahan apa yang dianggap breaking?
Checklist memaksa tim menjawab pertanyaan review yang biasanya baru muncul saat QA atau produksi. Ini penting karena memperbaiki kontrak setelah beberapa client terhubung jauh lebih mahal daripada mendefinisikannya di awal.
Checklist kontrak API sebelum integrasi dimulai
Bagian ini bisa langsung dipakai sebagai template review. Jika satu item belum terjawab, anggap kontrak belum siap untuk diintegrasikan.
1. Tujuan endpoint dan operasi bisnisnya jelas
Sebelum membahas field, pastikan satu endpoint mewakili satu operasi yang jelas. Misalnya, bedakan antara:
POST /paymentsuntuk membuat pembayaran baruPOST /payments/{id}/captureuntuk menangkap otorisasi pembayaranPOST /payments/{id}/canceluntuk membatalkan pembayaran
Kesalahan umum adalah membuat endpoint generik dengan banyak perilaku tersembunyi di satu payload. Sulit direview, sulit diuji, dan rawan perubahan diam-diam.
2. Field wajib vs opsional harus eksplisit
Ini salah satu sumber bug integrasi terbesar. Jangan hanya menulis contoh JSON. Tuliskan untuk setiap field:
- Apakah wajib saat create?
- Apakah opsional saat update?
- Apakah boleh
null? - Apakah boleh string kosong?
- Apakah server memberi default jika field tidak dikirim?
- Apakah field hanya output, hanya input, atau keduanya?
Aturan praktis: bedakan tiga keadaan yang sering tercampur:
- Tidak dikirim: client tidak mengirim field sama sekali
- Null: client mengirim field dengan nilai
null - Kosong: client mengirim string kosong atau array kosong
Ketiganya bisa punya arti berbeda, dan kontrak harus menyatakannya.
Contoh buruk vs baik untuk field
Buruk: dokumentasi hanya menyebut “email opsional”.
POST /users
{
"name": "Rina",
"email": null
}Masalahnya:
- Apakah
emailbolehnullatau harus dihilangkan jika tidak ada? - Apakah email unik?
- Apakah string kosong valid?
- Apakah field ini wajib pada alur tertentu?
Baik: kontrak mendefinisikan perilaku yang bisa diimplementasikan dan diuji.
POST /users
{
"name": "Rina",
"email": "[email protected]"
}name: wajib, string non-kosong, panjang maksimum ditentukan oleh serveremail: opsional saat create- Jika
emailtidak dikirim, server membuat user tanpa email - Jika
emaildikirim, nilainya harus format email valid email: nulltidak validemail: ""tidak valid- Field
id,created_athanya output dari server
Kontrak seperti ini membuat validasi, testing, dan integrasi lebih konsisten.
3. Model error harus konsisten dan dapat diproses mesin
Client tidak cukup hanya tahu bahwa request gagal. Client perlu tahu apakah error bisa diperbaiki, di-retry, atau harus dilaporkan ke operator.
Minimal, model error yang baik punya:
- Status HTTP yang sesuai
- Kode error stabil untuk logika aplikasi
- Pesan manusia yang bisa dibaca
- Detail field-level jika validasi gagal
- Request identifier atau trace identifier untuk debugging
Contoh buruk:
HTTP/1.1 400 Bad Request
{
"message": "Something went wrong"
}Pesan seperti ini tidak membantu client menentukan tindakan.
Contoh lebih baik:
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Payload tidak valid",
"details": [
{
"field": "email",
"issue": "invalid_format"
}
],
"request_id": "req_7f3a1c"
}
}Mengapa ini bekerja:
codestabil untuk percabangan di clientmessagebisa dipakai untuk log atau UI internaldetailsmembantu form validation atau debugging cepatrequest_idmemudahkan pencarian log antar sistem
Hindari menjadikan teks
messagesebagai kontrak utama. Teks mudah berubah. Gunakancodesebagai identifier yang stabil.
4. Idempotency key untuk operasi yang berisiko duplikat
Untuk operasi seperti membuat pembayaran, order, refund, atau booking, timeout jaringan bisa membuat client ragu apakah request pertama berhasil. Tanpa mekanisme idempotensi, retry dapat menciptakan data ganda.
Pola yang umum dan aman:
- Client mengirim
Idempotency-Keyunik per operasi bisnis - Server menyimpan hasil pertama untuk kombinasi kunci dan konteks yang relevan
- Jika request yang sama datang lagi, server mengembalikan hasil yang sama, bukan membuat resource baru
Contoh request:
POST /payments
Idempotency-Key: 6d8c1f8a-5f7b-4d23-8f4e-9c3ab21f7a11
Content-Type: application/json
{
"order_id": "ord_123",
"amount": 150000,
"currency": "IDR"
}Yang perlu ditulis di kontrak:
- Endpoint mana yang mendukung idempotency key
- Berapa lama key dianggap aktif
- Apakah payload harus identik untuk key yang sama
- Respons apa yang dikembalikan jika key yang sama dipakai dengan payload berbeda
Kesalahan umum: hanya menambahkan header idempotency tanpa mendefinisikan perilakunya. Hasilnya, client merasa aman melakukan retry padahal server belum benar-benar idempotent.
5. Retry safety harus didefinisikan, bukan diasumsikan
Tidak semua request aman diulang. Tim integrasi perlu tahu dengan jelas:
- Operasi mana yang aman di-retry otomatis
- Kondisi retry yang direkomendasikan, misalnya timeout atau respons 5xx
- Apakah client harus memakai backoff
- Apakah ada batas retry untuk mencegah lonjakan trafik
Panduan praktis:
GETbiasanya aman di-retry jika bersifat read-onlyPOSTtidak otomatis aman, kecuali ada kontrak idempotensi yang jelasPUTdanDELETEbisa lebih aman di-retry jika semantiknya benar-benar idempotent, tetapi tetap perlu definisi respons dan efek samping
Jika operasi memicu side effect ke sistem lain, nyatakan itu. Contoh: “request mungkin sudah diproses walau client menerima timeout.” Kalimat ini penting untuk strategi recovery di sisi client.
6. Timeout, SLA teknis, dan ekspektasi latensi
Kontrak API tidak harus memuat janji performa yang terlalu spesifik, tetapi minimal harus menjelaskan batas operasional dasar:
- Apakah endpoint ini sinkron atau asinkron?
- Apakah client disarankan timeout pendek atau menunggu lebih lama?
- Jika proses lama, apakah lebih baik balas
202 Acceptedlalu sediakan endpoint status?
Contoh buruk: endpoint ekspor data menjalankan proses berat secara sinkron tanpa penjelasan, lalu client memasang timeout 5 detik dan mulai me-retry agresif.
Contoh lebih baik:
POST /exportsmengembalikan202 Accepted- Respons berisi
job_id - Client memeriksa
GET /exports/{job_id} - Retry pada create ekspor memakai idempotency key
Pola ini mengurangi timeout palsu dan mencegah pekerjaan ganda.
7. Webhook signature dan replay protection
Jika integrasi melibatkan webhook, kontrak harus menjawab dua pertanyaan: bagaimana penerima yakin payload benar-benar dari pengirim, dan bagaimana mencegah replay request lama.
Minimal, definisikan:
- Header signature yang dikirim
- Algoritma atau mekanisme verifikasi secara umum
- Apakah signature dihitung dari raw body
- Apakah ada timestamp untuk membatasi replay
- Respons apa yang dianggap sukses oleh pengirim
- Kebijakan retry jika endpoint penerima gagal
Contoh kontrak yang lebih aman:
- Pengirim mengirim header
X-SignaturedanX-Timestamp - Penerima menghitung ulang signature dari raw request body dan secret bersama
- Penerima menolak request jika timestamp terlalu lama atau signature tidak cocok
- Penerima harus memproses event secara idempotent berdasarkan
event_id
Kesalahan umum:
- Memverifikasi payload JSON yang sudah di-parse ulang, padahal signature dihitung dari body mentah
- Tidak menyimpan
event_idsehingga event duplikat diproses dua kali - Menganggap sekali kirim pasti cukup, padahal webhook hampir selalu memerlukan retry
8. Versioning dan strategi perubahan
Versioning bukan sekadar menambahkan /v1 di URL. Tujuan utamanya adalah mengelola perubahan tanpa merusak client yang sudah ada.
Yang perlu diputuskan:
- Di mana versi dinyatakan: URL, header, atau media type
- Perubahan apa yang dianggap breaking
- Bagaimana masa transisi dan deprecasi diumumkan
- Apakah tim berkomitmen menjaga kompatibilitas ke belakang dalam satu versi mayor
Perubahan yang umumnya aman jika client ditulis defensif:
- Menambah field baru di respons
- Menambah endpoint baru
- Menambah kode error baru yang terdokumentasi dengan baik
Perubahan yang umumnya breaking:
- Menghapus field respons
- Mengubah tipe data field
- Mengubah makna field yang sudah ada
- Mengubah status code sukses utama tanpa transisi
- Menjadikan field yang dulu opsional menjadi wajib tanpa fallback
Jika API Anda dikonsumsi banyak klien, asumsi paling aman adalah: perubahan kecil di server tetap bisa menjadi perubahan besar di client.
9. Kompatibilitas perubahan harus diuji sebagai kontrak, bukan hanya unit test
Banyak tim punya test logika bisnis, tetapi tidak punya test kompatibilitas kontrak. Akibatnya, perubahan serializer, validator, atau mapping DTO bisa lolos ke produksi dan memutus integrasi.
Praktik yang berguna:
- Simpan contoh request/response kanonik sebagai artefak kontrak
- Tambahkan schema validation untuk payload penting
- Jalankan contract test antara provider dan consumer jika memungkinkan
- Pastikan perubahan pada field, error code, dan header ikut direview
Ini sangat membantu untuk mencegah bug “secara teknis endpoint masih hidup, tetapi kontraknya berubah.”
Tabel checklist review kontrak API
| Area | Pertanyaan review | Tanda bahaya |
|---|---|---|
| Tujuan endpoint | Apakah operasi bisnisnya satu dan jelas? | Satu endpoint menangani terlalu banyak skenario tersembunyi |
| Field | Apakah wajib, opsional, nullability, default, dan read/write sudah jelas? | Hanya ada contoh JSON tanpa aturan validasi |
| Error model | Apakah ada error code stabil, detail validasi, dan request ID? | Semua gagal dibalas dengan pesan generik |
| Idempotency | Apakah operasi create/capture/refund aman dari duplikasi? | Retry bisa membuat resource ganda |
| Retry safety | Kapan client boleh retry, dan pada status apa? | Tim berasumsi semua timeout aman diulang |
| Timeout | Apakah alur sinkron/asinkron dan ekspektasi respons jelas? | Operasi lama dipaksa sinkron tanpa polling/job status |
| Webhook | Bagaimana verifikasi signature, timestamp, dan deduplikasi event? | Webhook hanya mengandalkan IP atau tanpa replay protection |
| Versioning | Bagaimana strategi deprecasi dan definisi breaking change? | Perubahan kontrak dilakukan tanpa jalur transisi |
| Kompatibilitas | Apakah ada contract test atau schema check? | Hanya unit test internal provider |
| Observability | Apakah ada request ID, event ID, atau correlation ID? | Sulit menelusuri masalah lintas service |
Contoh kontrak buruk vs baik untuk operasi create yang sering timeout
Contoh buruk
POST /orders
{
"customer_id": "cust_1",
"items": [
{ "sku": "A-1", "qty": 2 }
]
}
HTTP/1.1 200 OK
{
"message": "success"
}Masalah desain:
- Tidak jelas field mana wajib dan batas validasinya
- Tidak ada
order_idyang dapat direferensikan - Tidak ada idempotency key untuk mencegah order ganda
- Tidak ada model error untuk validasi atau stok habis
- Status
200 OKterlalu umum untuk create
Contoh lebih baik
POST /orders
Idempotency-Key: 17d1e7c9-1f84-4e63-8f03-9e7d6f1d1b6b
Content-Type: application/json
{
"customer_id": "cust_1",
"items": [
{ "sku": "A-1", "qty": 2 }
],
"notes": "kirim sore"
}
HTTP/1.1 201 Created
{
"id": "ord_987",
"status": "pending",
"customer_id": "cust_1",
"items": [
{ "sku": "A-1", "qty": 2 }
],
"notes": "kirim sore",
"created_at": "2026-07-24T10:15:30Z"
}Aturan kontrak yang melengkapinya:
customer_idwajibitemswajib, minimal satu itemnotesopsional, jika tidak dikirim server menyimpan kosongIdempotency-Keywajib untuk create order dari client eksternal- Jika request sama diulang dengan key sama, server mengembalikan order yang sama
- Jika key sama tetapi payload berbeda, server mengembalikan error konflik
Contoh error konflik idempotensi:
HTTP/1.1 409 Conflict
{
"error": {
"code": "IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD",
"message": "Idempotency key sudah dipakai untuk payload lain",
"request_id": "req_a12bc3"
}
}Kontrak ini lebih aman karena perilaku timeout dan retry tidak diserahkan ke tebakan client.
Langkah verifikasi saat bekerja lintas tim
Kontrak API yang baik tidak cukup ditulis oleh satu tim lalu dilempar ke tim lain. Verifikasi lintas tim penting agar asumsi yang tersembunyi muncul sebelum coding dimulai.
1. Review dengan skenario, bukan hanya endpoint
Jangan bertanya “apakah endpoint ini sudah oke?” Tanyakan skenario konkret:
- Apa yang terjadi jika client kirim field opsional sebagai
null? - Apa respons saat timeout setelah server sempat memproses request?
- Bagaimana jika webhook terkirim dua kali?
- Apa yang harus dilakukan client untuk error validasi vs error sementara?
Skenario seperti ini mengubah review dari diskusi abstrak menjadi keputusan kontrak yang bisa diuji.
2. Minta contoh sukses dan gagal yang kanonik
Untuk setiap endpoint penting, siapkan minimal:
- Satu contoh request sukses
- Satu contoh error validasi
- Satu contoh error bisnis
- Satu contoh kasus retry atau idempotensi jika relevan
Jika tim tidak bisa menulis contoh gagal dengan jelas, biasanya kontraknya memang belum cukup matang.
3. Samakan istilah bisnis dan teknis
Sering kali dua tim memakai kata yang sama dengan arti berbeda, misalnya “cancel”, “void”, “expired”, atau “failed”. Pastikan status dan transisinya punya arti tunggal. Ini lebih penting daripada sekadar nama field yang rapi.
4. Putuskan pemilik kontrak
Satu tim harus bertanggung jawab menjaga kontrak tetap konsisten. Tanpa pemilik yang jelas, perubahan kecil bisa masuk dari banyak arah: backend mengubah serializer, frontend mengandalkan field tak terdokumentasi, atau partner menyimpan asumsi dari percakapan chat.
5. Tambahkan checklist ke pull request atau design review
Supaya kebiasaan review menjadi bagian dari proses, masukkan butir checklist ini ke template PR, RFC, atau design doc. Contohnya:
- Apakah field baru breaking untuk consumer lama?
- Apakah retry behavior sudah dijelaskan?
- Apakah error code baru sudah terdokumentasi?
- Apakah webhook event idempotent?
Tujuannya bukan menambah birokrasi, tetapi menangkap masalah kontrak di saat biaya perubahan masih murah.
Kesalahan yang paling sering terjadi
- Menganggap contoh payload sama dengan spesifikasi. Contoh hanya ilustrasi, bukan aturan lengkap.
- Menyatukan field opsional dan nullable. Padahal perilakunya bisa sangat berbeda.
- Berfokus pada endpoint sukses saja. Padahal integrasi lebih banyak gagal di jalur error.
- Tidak mendefinisikan idempotensi untuk create. Ini sering berujung pada duplikasi data saat gangguan jaringan.
- Menambahkan field respons tanpa memikirkan consumer yang ketat. Secara teori aman, tetapi beberapa parser atau mapping internal bisa rapuh.
- Webhook tanpa deduplikasi event. Hasilnya side effect dijalankan berkali-kali.
Penutup
Checklist kontrak API membantu mengubah kemampuan review yang sudah dimiliki banyak engineer menjadi alat desain yang konkret. Sebelum integrasi dimulai, pastikan kontrak menjawab hal-hal yang paling berbahaya: field wajib vs opsional, model error, idempotency key, retry safety, webhook signature, timeout, versioning, dan kompatibilitas perubahan.
Jika tim Anda sering berkata “nanti kita lihat saat implementasi”, biasanya itu tanda kontraknya belum siap. Lebih aman memaksa jawaban sejak review awal daripada membiarkan client dan server menebak perilaku satu sama lain di produksi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!