Audit API contract diperlukan ketika tim ingin backend terus berevolusi tanpa membuat integrasi klien ikut rapuh. Masalahnya bukan sekadar endpoint masih merespons 200, tetapi apakah bentuk request, header wajib, arti error, perilaku retry, dan urutan kejadian yang diandalkan klien tetap konsisten saat implementasi internal berubah.

Dalam rekayasa perangkat lunak, banyak bug serius muncul bukan karena algoritmenya selalu salah, tetapi karena ada asumsi implisit yang tidak pernah diverifikasi dengan cukup ketat. Pelajarannya relevan untuk API: jika perilaku penting hanya “kebetulan bekerja” dan tidak ditulis sebagai kontrak eksplisit lalu diuji, perubahan kecil di backend dapat mematahkan integrasi secara diam-diam. Karena itu, audit API contract harus berfokus pada apa yang dijanjikan sistem, bukan hanya pada bagaimana implementasi saat ini kebetulan berperilaku.

Apa yang dimaksud dengan API contract

API contract adalah kesepakatan yang dapat diverifikasi antara penyedia API dan konsumennya. Kontrak ini mencakup lebih dari daftar endpoint. Ia harus mendefinisikan hal-hal berikut secara eksplisit:

  • Struktur request: path, query, header, body, tipe data, field wajib dan opsional.
  • Struktur response: status code, body sukses, body error, field yang stabil, field nullable, dan field yang dapat bertambah di masa depan.
  • Semantik: arti setiap status code, aturan validasi, aturan idempotensi, dan kapan klien boleh retry.
  • Keamanan: header autentikasi, skema token, cakupan izin, dan respons ketika auth gagal.
  • Peristiwa asinkron: webhook, urutan pengiriman, duplikasi event, penandatanganan payload, dan jaminan delivery.
  • Versioning dan kompatibilitas mundur: perubahan seperti apa yang dianggap breaking dan bagaimana migrasinya.

Jika poin-poin ini tidak tertulis dan tidak diuji, klien akan mulai bergantung pada perilaku tak terdokumentasi. Itulah sumber integrasi rapuh.

Ruang lingkup audit API contract

Saat mengaudit API contract agar integrasi tak rapuh, fokuskan pemeriksaan pada area yang paling sering berubah diam-diam ketika kode backend direfaktor.

1. Request dan response harus eksplisit

Pastikan setiap endpoint memiliki definisi yang jelas tentang input dan output. Hal yang perlu diaudit:

  • Apakah field wajib ditandai dengan jelas?
  • Apakah tipe data konsisten, misalnya amount selalu integer dalam satuan terkecil, bukan kadang string?
  • Apakah nilai enum terdokumentasi?
  • Apakah field nullable dibedakan dari field yang bisa absen?
  • Apakah urutan elemen array memiliki arti atau tidak?
  • Apakah field tambahan di response diperbolehkan tanpa mematahkan klien?

Kesalahan umum adalah backend menambah atau mengubah bentuk field karena dianggap “minor”, padahal klien melakukan parsing ketat. Misalnya, mengubah customer_id dari integer ke string mungkin aman di database, tetapi bisa breaking untuk SDK atau aplikasi mobile yang melakukan deserialisasi statis.

2. Error code dan error body harus punya semantik tetap

Banyak API mendokumentasikan jalur sukses tetapi mengabaikan kontrak error. Padahal klien sering membuat keputusan penting dari respons gagal.

Audit hal berikut:

  • Status code untuk validasi salah, autentikasi gagal, otorisasi gagal, resource tidak ditemukan, konflik, rate limit, dan error server.
  • Bentuk body error yang konsisten, misalnya memiliki code, message, dan bila perlu details.
  • Apakah ada kode error stabil yang bisa dipakai klien untuk logika program, bukan mengandalkan string pesan bebas?
  • Apakah response 5xx dibedakan dari 4xx sehingga klien tahu kapan harus retry?

Anti-pattern yang sering muncul adalah klien mengandalkan teks pesan seperti "invalid token" untuk memicu refresh token. Ini rapuh karena teks pesan mudah berubah, diterjemahkan, atau dipersingkat.

3. Auth header tidak boleh ambigu

Kontrak autentikasi harus jelas dan konsisten. Audit apakah API mendefinisikan:

  • Header yang dipakai, misalnya Authorization: Bearer <token>.
  • Perbedaan antara token tidak ada, token tidak valid, dan token valid tetapi tidak memiliki izin.
  • Masa berlaku token dan cara klien menghadapi token kedaluwarsa.
  • Header lain yang wajib, misalnya tenant header atau request signature.

Perubahan internal pada middleware sering tidak sengaja mengubah perilaku auth, misalnya sebelumnya endpoint mengembalikan 401, lalu setelah refaktor menjadi 403. Secara implementasi mungkin tampak kecil, tetapi secara kontrak itu dapat mengubah perilaku klien.

4. Idempotency key dan retry semantics

Endpoint yang memicu efek samping seperti pembayaran, pembuatan order, atau pengiriman email perlu kontrak eksplisit tentang retry. Jika jaringan gagal setelah request diterima, klien harus tahu apakah aman untuk mengirim ulang.

Audit poin berikut:

  • Endpoint mana yang mendukung idempotency key.
  • Di mana key dikirim, biasanya header seperti Idempotency-Key.
  • Berapa lama hasil key disimpan.
  • Apakah request dengan key yang sama tetapi payload berbeda ditolak.
  • Status code apa yang dikembalikan ketika hasil lama dikembalikan lagi.
  • Kapan klien boleh retry otomatis, dan kapan tidak boleh.

Tanpa kontrak ini, klien akan menebak-nebak. Hasilnya bisa duplikasi transaksi atau, sebaliknya, klien terlalu takut retry sehingga sistem tampak tidak andal.

5. Webhook delivery adalah kontrak juga

Tim sering disiplin pada REST API tetapi longgar pada webhook. Padahal webhook adalah integrasi antar-sistem yang sangat sensitif terhadap perubahan.

Audit webhook untuk memastikan:

  • Payload event punya skema yang terdokumentasi.
  • Setiap event memiliki ID unik.
  • Webhook bisa dikirim ulang dan penerima harus menangani duplikasi.
  • Ada mekanisme verifikasi seperti signature header.
  • Retry policy terdokumentasi secara fungsional, misalnya “akan dicoba ulang jika endpoint penerima mengembalikan non-2xx”, tanpa perlu angka spesifik jika belum menjadi komitmen publik.
  • Tidak ada asumsi bahwa urutan pengiriman selalu sama dengan urutan kejadian.

Asumsi “event pasti datang sekali dan berurutan” adalah sumber bug klasik. Kontrak yang aman justru mengasumsikan webhook dapat terlambat, duplikat, atau diterima di urutan berbeda.

Anti-pattern yang membuat integrasi rapuh

Mengandalkan perilaku tak terdokumentasi

Contohnya:

  • Klien membaca field internal yang kebetulan ikut keluar di response.
  • Klien mengandalkan urutan objek dalam array padahal kontrak tidak menjaminnya.
  • Klien menganggap field yang selama ini selalu ada pasti wajib, padahal dokumentasi menyebut opsional.
  • Backend mengubah body error tanpa menganggapnya breaking karena “hanya pesan”.
  • Webhook consumer menganggap satu event hanya akan terkirim sekali.

Perilaku yang tidak didokumentasikan bisa berubah kapan saja, terutama setelah refaktor, optimasi, pergantian ORM, migrasi serializer, atau perubahan gateway.

Mencampur detail implementasi dengan kontrak publik

Jika kontrak bocor terlalu jauh ke detail internal, perubahan backend menjadi mahal. Misalnya, API mengembalikan nama tabel, status proses internal, atau kode error yang langsung berasal dari library. Ini membuat kontrak ikut berubah ketika dependency internal berubah.

Menganggap perubahan “sekadar format” selalu aman

Mengganti integer menjadi string, mengubah format tanggal, mengganti nama header, atau menghapus field yang dianggap tidak dipakai sering dikira non-breaking. Dalam praktiknya, semua itu bisa mematahkan integrasi nyata.

Contoh breaking change vs non-breaking change

Audit akan lebih efektif jika tim punya definisi operasional tentang perubahan breaking dan non-breaking.

Contoh breaking change

  • Menghapus field response yang sebelumnya didokumentasikan.
  • Mengubah tipe field, misalnya total dari number menjadi string.
  • Mengganti status code sukses dari 201 ke 200 jika klien bergantung pada semantik create.
  • Mewajibkan header baru tanpa strategi migrasi.
  • Mengubah format error body sehingga parser klien gagal.
  • Mengubah skema signature webhook.
  • Mengubah arti enum yang sudah ada.

Contoh non-breaking change yang umumnya aman

  • Menambah field response opsional, selama klien diharapkan mengabaikan field yang tidak dikenali.
  • Menambah endpoint baru.
  • Menambah nilai enum hanya jika kontrak sejak awal menyatakan klien harus tahan terhadap nilai baru. Jika tidak, ini berpotensi breaking bagi klien dengan switch ketat.
  • Meningkatkan performa internal tanpa mengubah semantik API.

Catatan: “Non-breaking” selalu bergantung pada kontrak yang sudah dinyatakan. Menambah field memang lazim dianggap aman, tetapi hanya jika klien tidak menolak properti tambahan dan dokumentasi tidak menjanjikan struktur tertutup.

Checklist audit API contract

Gunakan checklist berikut saat meninjau endpoint yang sudah ada atau sebelum merilis perubahan backend.

  1. Skema request jelas: field wajib, opsional, nullable, enum, batas ukuran, format tanggal, dan aturan validasi tertulis.
  2. Skema response jelas: status sukses, field stabil, field opsional, pagination, dan contoh payload tersedia.
  3. Error terstandar: status code dan body error konsisten di seluruh endpoint.
  4. Auth terdokumentasi: header, skenario 401 vs 403, dan kebutuhan scope/role jelas.
  5. Idempotensi ditentukan: endpoint mutasi yang perlu aman terhadap retry mendukung key atau mekanisme setara.
  6. Retry semantics jelas: klien tahu kapan boleh retry dan kapan harus berhenti.
  7. Webhook aman: event ID, signature, deduplikasi, dan ekspektasi urutan terdokumentasi.
  8. Versioning konsisten: ada kebijakan perubahan breaking dan masa transisi.
  9. Backward compatibility diperiksa: perubahan yang diusulkan dibandingkan dengan kontrak sebelumnya, bukan hanya implementasi sekarang.
  10. Perilaku tak terdokumentasi dihapus: jika klien membutuhkan perilaku tertentu, jadikan itu kontrak resmi atau hentikan ketergantungan tersebut.
  11. Contoh negatif diuji: token salah, payload invalid, duplicate idempotency key, signature webhook tidak valid.
  12. Contract test berjalan di CI: perubahan yang melanggar kontrak harus gagal sebelum rilis.

Cara menerapkan contract test di CI

Dokumentasi saja tidak cukup. Audit API contract baru efektif jika kontrak diterjemahkan menjadi pengujian otomatis. Pendekatannya bisa dibagi menjadi tiga lapis.

1. Spec-first atau schema-based validation

Simpan spesifikasi API dalam format yang dapat diperiksa, misalnya OpenAPI atau skema JSON untuk payload penting. Lalu validasi bahwa server masih mematuhi spesifikasi itu.

Contoh potongan spesifikasi yang menegaskan auth header, idempotency key, dan bentuk respons:

openapi: 3.0.0
paths:
  /payments:
    post:
      parameters:
        - in: header
          name: Authorization
          required: true
          schema:
            type: string
        - in: header
          name: Idempotency-Key
          required: true
          schema:
            type: string
      responses:
        '201':
          description: Payment created
        '400':
          description: Validation error
        '401':
          description: Authentication failed
        '409':
          description: Idempotency conflict

Nilai utamanya bukan pada format dokumen, tetapi pada kemampuan membandingkan perubahan kontrak secara eksplisit dalam pull request.

2. Consumer-driven contract test

Jika ada banyak klien, terutama antar-service, pertimbangkan consumer-driven contract testing. Konsumen mendefinisikan ekspektasi yang benar-benar mereka pakai, lalu provider memverifikasi bahwa ekspektasi itu masih terpenuhi.

Pendekatan ini berguna ketika dokumentasi provider terlalu umum, sementara kegagalan nyata terjadi pada kombinasi field dan status tertentu yang dipakai konsumen. Kelemahannya, koordinasi antar-tim lebih kompleks dan kontrak bisa tersebar jika governance lemah.

3. Regression test untuk perilaku semantik

Tidak semua kontrak bisa ditangkap oleh skema statis. Retry, idempotensi, dan webhook sering memerlukan tes perilaku.

Contoh pseudo-test untuk idempotensi:

# request pertama
POST /payments
Authorization: Bearer token
Idempotency-Key: abc-123

{ "order_id": "ord_1", "amount": 5000 }

# request kedua dengan key dan payload sama
POST /payments
Authorization: Bearer token
Idempotency-Key: abc-123

{ "order_id": "ord_1", "amount": 5000 }

# ekspektasi:
# - tidak membuat payment baru kedua kali
# - response konsisten dengan hasil request pertama

Contoh pseudo-test untuk webhook consumer:

# kirim event yang sama dua kali
POST /webhook-endpoint
X-Signature: valid-signature

{ "event_id": "evt_123", "type": "payment.succeeded" }

POST /webhook-endpoint
X-Signature: valid-signature

{ "event_id": "evt_123", "type": "payment.succeeded" }

# ekspektasi:
# - efek samping diproses satu kali
# - request kedua tetap diakui tanpa error fatal

Gate yang sebaiknya ada di pipeline CI

  • Diff spesifikasi API dari branch utama dan tandai perubahan breaking.
  • Validasi response terhadap skema untuk jalur sukses dan gagal.
  • Jalankan contract test dari konsumen penting.
  • Jalankan regression test untuk idempotensi, auth, dan webhook.
  • Wajibkan review khusus jika ada perubahan status code, header, enum, atau body error.

Strategi versioning dan backward compatibility

Versioning bukan alasan untuk sembrono. API berversi tetap perlu kompatibilitas yang disiplin agar migrasi konsumen tidak mahal.

Kapan perlu versi baru

Buat versi baru ketika perubahan benar-benar breaking dan tidak dapat dimigrasikan secara aman dengan masa transisi. Contohnya, mengganti bentuk resource inti atau menghapus field penting yang tidak bisa dipertahankan.

Kapan cukup dengan perubahan kompatibel

Jika perubahan dapat dilakukan dengan menambah field opsional, memperkenalkan header baru sebagai opsional terlebih dahulu, atau menyediakan perilaku lama selama masa transisi, biasanya versi baru tidak perlu segera dibuat.

Prinsip backward compatibility yang praktis

  • Jangan hapus field yang telah dipublikasikan tanpa masa deprecasi yang jelas.
  • Jangan ubah arti field atau enum yang ada.
  • Tambahkan, jangan mengganti, jika masih mungkin.
  • Jika perlu mengubah autentikasi atau signature, dukung skema lama dan baru selama masa migrasi.
  • Komunikasikan tanggal deprecasi, tetapi tetap pakai test untuk menegakkan kompatibilitas sampai tanggal itu tiba.

Tips debugging saat integrasi mulai rapuh

Jika setelah refaktor ada laporan integrasi rusak, periksa dalam urutan berikut:

  1. Bandingkan payload aktual sebelum dan sesudah perubahan, bukan hanya status code.
  2. Periksa header yang diwajibkan, terutama auth, content type, idempotency key, dan signature.
  3. Periksa body error dan status code yang berubah.
  4. Periksa apakah serializer, middleware, atau gateway menambah atau menghapus field.
  5. Periksa retry dari klien: apakah request dikirim ulang dan menyebabkan duplikasi?
  6. Untuk webhook, cek duplikasi event, signature mismatch, dan asumsi urutan event.

Bug kontrak sering tersembunyi di lapisan yang dianggap “bukan logika bisnis”, seperti serializer, proxy, cache, API gateway, atau library HTTP. Karena itu, observabilitas request/response yang aman untuk diaudit sangat membantu.

Penutup

Audit API contract agar integrasi tak rapuh berarti memindahkan asumsi dari kepala developer ke kontrak yang tertulis dan diuji. Fokus utamanya adalah menjaga semantik yang dipakai konsumen tetap stabil: bentuk request/response, kode error, auth header, idempotency key, retry, webhook delivery, versioning, dan backward compatibility.

Jika tim hanya menguji bahwa endpoint “masih jalan”, perubahan backend akan tetap berisiko mematahkan integrasi secara diam-diam. Tetapi jika kontrak dibuat eksplisit dan diuji di CI, refaktor internal bisa dilakukan jauh lebih aman tanpa memaksa semua klien ikut pecah setiap kali implementasi berubah.