Pada AI workspace self-hosted seperti Odysseus atau sistem sejenis, pekerjaan inferensi, ekstraksi dokumen, indexing, dan otomasi sering tidak cocok dijalankan secara sinkron dalam satu request HTTP. Durasi eksekusi bisa lama, hasilnya belum tentu langsung tersedia, dan kegagalan jaringan dapat membuat klien salah mengira job gagal padahal proses tetap berjalan.

Karena itu, kontrak API untuk AI Workspace sebaiknya dirancang dengan pola submit job async: klien membuat job, menerima job id, lalu mengambil status lewat polling atau menerima notifikasi lewat webhook. Agar integrasi aman dan tahan gangguan, kontrak juga harus mengatur idempotency key, deduplikasi, retry dengan backoff, timeout, error schema yang konsisten, serta versioning yang jelas.

Mengapa job async lebih cocok untuk AI workspace

Operasi AI umumnya punya karakteristik berikut:

  • Durasi tidak pasti: inferensi bisa cepat, tetapi bisa juga lambat saat model besar sedang sibuk atau antrean padat.
  • Beban komputasi tinggi: request bisa diteruskan ke queue, worker GPU, atau layanan internal lain.
  • Ketergantungan berlapis: file store, vector store, model server, scheduler, dan callback endpoint pihak lain.
  • Kegagalan parsial: request HTTP sukses diterima, tetapi proses backend gagal di tahap berikutnya.

Jika semua dipaksa sinkron, Anda akan berhadapan dengan timeout gateway, koneksi klien putus, retry liar dari klien, dan duplikasi eksekusi. Pola async memisahkan acceptance dari completion: server cukup menyatakan job diterima atau tidak, sedangkan hasil akhir dipantau lewat resource status atau webhook.

Kontrak dasar: create job, status polling, dan webhook

1. Endpoint create job

Endpoint ini menerima permintaan pekerjaan dan mengembalikan identitas job yang stabil. Jika sistem memakai deduplikasi berbasis idempotency key, request yang identik tidak boleh membuat job baru berulang kali.

POST /v1/jobs
Content-Type: application/json
Idempotency-Key: 8c9d4e6b-6db5-4f55-8d0a-12f4f1a4b901
Authorization: Bearer <token>

{
  "type": "document_summarization",
  "input": {
    "document_url": "https://storage.internal/docs/abc.pdf",
    "language": "id"
  },
  "callback": {
    "url": "https://consumer.example.com/webhooks/ai-jobs",
    "secret_ref": "whsec_consumer_a"
  },
  "metadata": {
    "workspace_id": "ws_123",
    "request_id": "req_20260607_001"
  }
}

Contoh respons ketika job diterima:

HTTP/1.1 202 Accepted
Content-Type: application/json
Location: /v1/jobs/job_01JXYZ...

{
  "job_id": "job_01JXYZ...",
  "status": "queued",
  "created_at": "2026-06-07T10:15:30Z",
  "status_url": "/v1/jobs/job_01JXYZ...",
  "webhook_registered": true,
  "idempotency_key": "8c9d4e6b-6db5-4f55-8d0a-12f4f1a4b901"
}

Mengapa 202 Accepted? Karena server menyatakan request sudah diterima untuk diproses, bukan bahwa hasil akhir sudah selesai. Ini lebih jujur daripada mengembalikan 200 seolah pekerjaan telah tuntas.

2. Endpoint status polling

Polling tetap penting walaupun webhook tersedia. Webhook bisa gagal, diblokir firewall, atau gagal diverifikasi. Polling adalah jalur pemulihan yang sederhana.

GET /v1/jobs/job_01JXYZ...
Authorization: Bearer <token>

Contoh respons:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "job_id": "job_01JXYZ...",
  "type": "document_summarization",
  "status": "completed",
  "created_at": "2026-06-07T10:15:30Z",
  "started_at": "2026-06-07T10:15:41Z",
  "finished_at": "2026-06-07T10:16:12Z",
  "result": {
    "summary": "Ringkasan dokumen...",
    "tokens_input": 12540,
    "tokens_output": 620
  },
  "error": null,
  "metadata": {
    "workspace_id": "ws_123",
    "request_id": "req_20260607_001"
  }
}

Status sebaiknya terbatas dan stabil, misalnya:

  • queued
  • running
  • completed
  • failed
  • canceled
  • expired jika hasil atau job memiliki masa berlaku tertentu

Hindari terlalu banyak status internal seperti validating_model_cache_binding di kontrak publik. Itu menyulitkan konsumen dan membuat versi API rapuh.

3. Webhook callback

Webhook berguna untuk memberi tahu konsumen ketika status berubah tanpa polling terus-menerus. Namun webhook harus dianggap sebagai best-effort notification, bukan satu-satunya sumber kebenaran.

POST /webhooks/ai-jobs
Content-Type: application/json
X-Webhook-Id: wh_01JXYZ_EVT_01
X-Webhook-Timestamp: 2026-06-07T10:16:13Z
X-Webhook-Signature: t=2026-06-07T10:16:13Z,v1=9f3d...
X-Event-Type: job.completed

{
  "event_id": "evt_01JXYZ...",
  "event_type": "job.completed",
  "event_version": "1.0",
  "occurred_at": "2026-06-07T10:16:12Z",
  "delivery_attempt": 1,
  "job": {
    "job_id": "job_01JXYZ...",
    "status": "completed",
    "result": {
      "summary": "Ringkasan dokumen..."
    },
    "metadata": {
      "workspace_id": "ws_123",
      "request_id": "req_20260607_001"
    }
  }
}

Webhook payload sebaiknya membawa:

  • event_id unik untuk deduplikasi delivery
  • event_type yang eksplisit
  • event_version agar evolusi payload terkontrol
  • occurred_at sebagai waktu kejadian bisnis, bukan waktu kirim HTTP
  • delivery_attempt untuk observabilitas retry
  • job_id dan status akhir yang relevan

Idempotency key dan deduplikasi: wajib, bukan opsional

Masalah klasik pada integrasi async adalah klien mengirim request, koneksi timeout, lalu klien retry. Jika server tidak punya mekanisme idempoten, satu aksi bisnis bisa menghasilkan dua atau lebih job identik.

Cara kerja idempotency key

Klien mengirim Idempotency-Key yang unik untuk satu niat bisnis. Server menyimpan pasangan berikut:

  • idempotency key
  • fingerprint request yang relevan, misalnya hash body tertentu
  • hasil respons pertama, termasuk job_id
  • masa berlaku key

Jika key yang sama datang lagi dengan payload yang sama, server mengembalikan respons semula, bukan membuat job baru. Jika key sama tetapi payload berbeda, server sebaiknya menolak request karena indikasi bug atau penyalahgunaan klien.

Contoh respons konflik:

HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "error": {
    "code": "IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD",
    "message": "Idempotency-Key sudah dipakai untuk payload yang berbeda.",
    "retryable": false
  }
}

Deduplikasi di sisi consumer

Webhook juga harus diproses secara idempoten. Simpan event_id atau kombinasi job_id + status + occurred_at untuk mencegah eksekusi efek samping berulang, misalnya:

  • mengirim email dua kali
  • menandai invoice dua kali
  • menulis hasil akhir ke database lebih dari sekali

Prinsip praktis: producer mencegah duplikasi pembuatan job, consumer mencegah duplikasi pemrosesan event.

Retry aman: backoff, timeout, dan aturan kapan boleh mengulang

Retry pada create job

Retry boleh dilakukan jika klien tidak tahu apakah server sudah menerima request, misalnya karena timeout jaringan atau koneksi terputus. Namun retry harus memakai idempotency key yang sama.

Strategi yang aman:

  • gunakan timeout klien yang realistis, jangan terlalu pendek
  • retry hanya untuk error jaringan, 408, 429, atau 5xx tertentu
  • gunakan exponential backoff dengan jitter
  • batasi total percobaan agar tidak menambah beban saat sistem sedang buruk

Contoh pseudo-config klien:

{
  "timeout_ms": 10000,
  "max_attempts": 4,
  "backoff": "exponential_with_jitter",
  "retry_on": [408, 429, 500, 502, 503, 504]
}

Jangan retry buta pada semua 4xx. Misalnya 400 Bad Request dan 401 Unauthorized biasanya menunjukkan masalah yang tidak akan selesai hanya dengan mengulang.

Retry pada webhook delivery

Producer webhook perlu retry ketika callback endpoint gagal merespons atau mengembalikan status yang menandakan kegagalan sementara. Pola umum yang aman:

  • anggap 2xx sebagai sukses
  • retry untuk timeout, koneksi gagal, 408, 429, dan 5xx
  • hindari retry agresif pada 4xx karena sering berarti bug permanen, kecuali ada alasan khusus
  • catat jumlah percobaan dan waktu next retry

Webhook retry juga harus idempoten, karena kemungkinan besar event yang sama akan dikirim lebih dari sekali.

Timeout yang realistis

Ada dua timeout yang perlu dibedakan:

  • Request timeout antara klien dan API create-job
  • Job execution timeout antara scheduler dan worker

Keduanya tidak sama. Request timeout 10 detik tidak berarti job harus selesai dalam 10 detik. Job bisa sah berjalan beberapa menit selama API sudah menerima dan mengantrikannya.

Kesalahan umum: menyamakan timeout HTTP dengan timeout bisnis. Akibatnya klien menganggap proses gagal padahal worker sedang memproses job secara normal.

Edge case nyata yang harus ditangani

1. Request timeout, tetapi job tetap jalan

Ini kasus paling penting. Klien mengirim POST /v1/jobs, koneksi putus sebelum respons diterima, lalu klien tidak tahu apakah job sudah dibuat. Solusinya:

  1. klien retry dengan Idempotency-Key yang sama
  2. server mengembalikan job_id yang sama jika request awal sebenarnya sudah diterima
  3. jika klien masih ragu, lakukan polling berdasarkan informasi yang tersedia atau gunakan endpoint lookup jika memang disediakan

Tanpa idempotency, edge case ini langsung berubah menjadi duplikasi inferensi yang mahal.

2. Webhook duplikat

Webhook bisa terkirim dua kali karena retry producer, race condition, atau consumer mengembalikan respons lambat sehingga producer mengira delivery gagal. Consumer tidak boleh mengandalkan asumsi “satu event hanya datang sekali”.

Praktik yang aman:

  • simpan event_id sebagai kunci unik di database
  • jika event sudah pernah diproses, balas 200/204 tanpa mengulang efek samping
  • pisahkan penerimaan webhook dari pemrosesan bisnis dengan queue internal

3. Event out-of-order

Dalam sistem terdistribusi, job.running bisa sampai setelah job.completed karena retry atau rute jaringan yang berbeda. Karena itu, consumer harus memperlakukan event sebagai notifikasi, lalu memeriksa aturan transisi status.

Aturan praktis:

  • status terminal seperti completed, failed, canceled tidak boleh diturunkan kembali ke running
  • gunakan occurred_at dan/atau nomor versi status bila tersedia
  • jika urutan meragukan, lakukan GET status ke sumber kebenaran

4. Callback gagal diverifikasi

Webhook tanpa verifikasi tanda tangan mudah dipalsukan. Tetapi verifikasi juga bisa gagal karena secret salah, body berubah oleh proxy, atau implementasi HMAC salah mengambil payload.

Jika callback gagal diverifikasi:

  • jangan proses payload bisnis
  • catat detail verifikasi yang gagal, tetapi jangan log secret mentah
  • balas status error yang jelas, misalnya 401 atau 403
  • sediakan mekanisme rotasi secret dengan masa transisi jika perlu

Untuk verifikasi HMAC, signature harus dihitung dari raw request body, bukan JSON yang sudah diparse dan diserialisasi ulang. Perubahan spasi atau urutan field dapat membuat signature berbeda.

Skema error yang konsisten

Salah satu sumber integrasi rapuh adalah error response yang tidak konsisten antar endpoint. Gunakan skema yang seragam agar klien bisa memutuskan kapan retry, kapan menampilkan pesan, dan kapan menghentikan proses.

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Terlalu banyak request dalam periode singkat.",
    "retryable": true,
    "details": {
      "scope": "workspace",
      "limit_window": "60s"
    },
    "request_id": "srv_req_01JXYZ..."
  }
}

Field yang berguna:

  • code: kode stabil untuk logic mesin
  • message: deskripsi ringkas untuk manusia
  • retryable: petunjuk eksplisit bagi klien
  • details: konteks tambahan, opsional
  • request_id: penting untuk tracing dan dukungan operasional

Status code yang umum dipakai

  • 202 Accepted: job diterima untuk diproses
  • 200 OK: status job berhasil diambil, atau webhook lookup berhasil
  • 400 Bad Request: payload tidak valid
  • 401 Unauthorized: token atau signature tidak valid
  • 403 Forbidden: akses ditolak
  • 404 Not Found: job tidak ditemukan atau tidak terlihat oleh caller
  • 409 Conflict: konflik idempotency atau transisi status tidak valid
  • 422 Unprocessable Entity: payload secara sintaks valid tetapi tidak lolos aturan domain
  • 429 Too Many Requests: rate limit
  • 5xx: kegagalan server atau dependency sementara

Versioning contract agar perubahan tidak mematahkan integrasi

Kontrak API untuk AI Workspace akan berubah: ada field baru, tipe job baru, atau event webhook baru. Jika perubahan ini tidak diatur, integrasi akan pecah diam-diam.

Aturan versioning yang aman

  • gunakan versi mayor di path atau header, misalnya /v1/jobs
  • anggap penambahan field baru sebagai perubahan non-breaking jika consumer diharapkan toleran terhadap field yang tidak dikenal
  • jangan ubah arti field lama tanpa menaikkan versi mayor
  • versikan juga event webhook, misalnya event_version
  • dokumentasikan field yang opsional dan yang dijamin selalu ada

Contoh perubahan yang biasanya aman:

  • menambah field metrics pada hasil job
  • menambah event type baru yang tidak memengaruhi event lama

Contoh perubahan yang berisiko mematahkan integrasi:

  • mengubah format status dari string menjadi object
  • menghapus field job_id dari webhook
  • mengganti arti failed menjadi mencakup pembatalan manual

Tabel keputusan singkat

KondisiTindakan ProducerTindakan Consumer
POST create job timeoutSimpan hasil berdasar idempotency keyRetry dengan key yang sama
Webhook 500 / timeoutRetry dengan backoff dan delivery_attemptPastikan handler idempoten
Webhook duplikatBoleh terjadi karena retryDeduplikasi berdasarkan event_id
Event datang tidak berurutanKirim occurred_at yang jelasTolak downgrade status, fallback ke polling
Signature webhook tidak validCatat kegagalan deliveryJangan proses payload, balas 401/403
Payload create job sama, key samaKembalikan job_id lamaAman untuk retry
Payload beda, key samaBalas 409 ConflictPerbaiki bug klien

Panduan implementasi producer-consumer agar tidak rapuh

Checklist untuk producer API AI workspace

  • Gunakan 202 Accepted untuk job async, bukan 200 seolah selesai.
  • Wajib dukung Idempotency-Key pada create job.
  • Simpan mapping key ke respons awal dan fingerprint payload.
  • Sediakan endpoint GET /jobs/{id} yang stabil dan mudah dipoll.
  • Webhook harus ditandatangani dengan secret per consumer atau per endpoint.
  • Tambahkan event_id, event_type, event_version, occurred_at, dan delivery_attempt.
  • Retry webhook dengan backoff dan batas maksimum percobaan.
  • Log request_id, job_id, dan status transisi untuk debugging.
  • Definisikan status terminal dengan jelas dan jangan mengubahnya seenaknya.
  • Dokumentasikan SLA praktis, timeout, retensi job, dan kebijakan retry.

Checklist untuk consumer integrasi

  • Selalu kirim Idempotency-Key unik per niat bisnis.
  • Retry create-job hanya pada kondisi yang memang retryable.
  • Gunakan backoff dengan jitter, bukan loop retry rapat.
  • Anggap webhook bisa duplikat, telat, atau datang tidak berurutan.
  • Verifikasi signature menggunakan raw body.
  • Simpan event_id untuk deduplikasi.
  • Jangan lakukan kerja berat langsung di handler webhook; masukkan ke queue internal.
  • Jika status event meragukan, lakukan polling ke endpoint job.
  • Monitor rasio timeout, retry, dan webhook failure.
  • Uji edge case jaringan, bukan hanya skenario sukses.

Contoh alur yang tahan gangguan

  1. Aplikasi consumer mengirim POST /v1/jobs dengan Idempotency-Key.
  2. AI workspace menerima request, menyimpan record idempotency, membuat job_id, dan mengembalikan 202 Accepted.
  3. Worker internal memproses job melalui queue.
  4. Jika selesai, producer memperbarui status job menjadi terminal dan mengirim webhook.
  5. Consumer menerima webhook, memverifikasi signature, lalu menyimpan event_id untuk deduplikasi.
  6. Jika webhook gagal diproses internal, consumer tetap membalas sesuai kebijakan yang dipilih; idealnya webhook cepat di-ack lalu diproses lewat queue lokal.
  7. Jika webhook tidak pernah sampai atau gagal diverifikasi, consumer melakukan polling GET /v1/jobs/{job_id}.

Pola ini cocok untuk AI workspace self-hosted yang mengorkestrasi banyak komponen, termasuk model server, queue worker, dan layanan otomasi lain. Pendekatan ini juga selaras dengan kebutuhan sistem seperti Odysseus: fleksibel, dapat dioperasikan sendiri, dan tidak bergantung pada koneksi HTTP sinkron yang rapuh.

Debugging tips yang sering menyelamatkan waktu

  • Kasus “job ganda”: cek apakah klien menghasilkan idempotency key baru saat retry timeout.
  • Kasus webhook selalu invalid signature: pastikan framework tidak mengubah body sebelum verifikasi.
  • Kasus status terlihat mundur: audit event out-of-order dan pastikan transisi status dilindungi.
  • Kasus polling selalu queued: cek worker queue macet, lease tidak dilepas, atau scheduler gagal meng-claim job.
  • Kasus webhook sering timeout: kecilkan kerja di endpoint webhook dan pindahkan ke background worker.
  • Kasus retry storm: pastikan ada backoff, jitter, dan rate limit yang masuk akal di kedua sisi.

Penutup

Kontrak API yang baik untuk AI workspace bukan sekadar menentukan endpoint, tetapi mendefinisikan perilaku saat jaringan buruk, proses lambat, atau event datang dua kali. Untuk integrasi async yang aman, fondasinya jelas: create job dengan idempotency key, status polling yang stabil, webhook terverifikasi, deduplikasi event, retry dengan backoff, skema error konsisten, dan versioning yang disiplin.

Jika Anda sedang membangun atau mengintegrasikan AI workspace self-hosted seperti Odysseus, mulailah dari kontrak ini lebih dulu. Worker, model, dan otomasi bisa berubah, tetapi kontrak integrasi yang tahan gangguan akan menjaga seluruh sistem tetap dapat diprediksi.