Referensi

Error

Semua error pakai format yang sama.

{ "error": { "code": "amount_too_small", "message": "amount is below the minimum", "request_id": "0af7651916cd43dd8448eb211c80319c" } }

error.request_id sama dengan header response X-Request-Id — sertakan saat menghubungi support supaya kami bisa menemukan request itu di log kami.

Cocokkan pada error.code, jangan pada error.message — kode itulah kontraknya, pesannya cuma teks dan kata-katanya bisa berubah. Tiga kode menambah field ketiga, error.fields, dengan key berupa path tiap field di body yang Anda kirim: validation_failed menyebut aturan yang dilanggar, payment_method_unavailable menyebut kode yang bermasalah, dan customer_required menyebut data pembeli yang kurang.

Berikut semua kode yang bisa dikembalikan /v1. Patokan kasarnya: 4xx selain 429 berarti request-nya sendiri yang salah dan akan tetap salah, jadi retry tanpa perubahan percuma; 429 dan 500 layak di-retry, dengan Idempotency-Key yang sama. Satu-satunya pengecualian adalah invalid_body dengan status 400, yang berarti body-nya tidak pernah sampai utuh — yang itu boleh di-retry juga.

Autentikasi & akses

unauthorized401

Tidak ada header Authorization: Bearer …, format header-nya salah, atau key-nya tidak dikenali — salah ketik, sudah dicabut, atau milik environment lain.

Yang perlu dilakukan: Kirim Authorization: Bearer kp_live_…. Kalau header-nya sudah benar, berarti key-nya mati — buat yang baru di dashboard. Mengulang key yang sama tidak akan pernah berhasil, dan lebih dari 30 percobaan gagal per menit dari satu IP akan dibalas rate_limited.

merchant_suspended403

Akun sedang disuspend. Permintaan pembayaran baru diblokir; membaca yang sudah ada tetap bisa.

Yang perlu dilakukan: Tidak ada yang bisa diubah di request untuk mengatasinya. Hubungi support Kasera Pay.

bad_origin403

Sebuah create — atau request apa pun yang bukan GET — datang dengan header Origin yang tidak ada di allow-list kami. Ini proteksi CSRF untuk browser, dan dicek paling awal: sebelum API key Anda, jadi request yang belum terautentikasi pun bisa kena. Request tanpa header Origin sama sekali — seperti yang dikirim server — langsung lolos, dan GET tidak pernah kena.

Yang perlu dilakukan: Panggil /v1 dari server Anda, bukan dari browser: Origin diisi oleh browser dan JavaScript sisi klien tidak bisa menghapusnya. Lagi pula, secret key Anda memang tidak boleh ada di browser. Perhatikan, ini 403 kedua di API ini — cocokkan pada error.code, jangan pada statusnya, untuk membedakannya dari merchant_suspended.

Validasi request

validation_failed422

Ada field body yang melanggar aturan: amount bukan bilangan bulat 1 sampai 1.000.000.000, description lebih dari 255 karakter, external_id atau merchant_ref lebih dari 64, email yang bukan email, payer.phone yang bukan E.164 (customer.phone bebas bentuk, maks 32 karakter), return_url yang bukan https atau lebih dari 2048 karakter, order_items lebih dari 50 baris, atau expires_in_minutes di luar 1–10080.

Yang perlu dilakukan: error.fields menyebut tiap field yang bermasalah beserta aturan yang dilanggar, dengan key berupa path field itu di body yang Anda kirim: expires_in_minutes, payer.phone, customer.phone, dan baris yang salah sebagai order_items.3.price — lengkap dengan indeksnya, dihitung dari 0. Semuanya pakai titik, tanpa kurung siku, jadi key-nya bisa langsung dipakai sebagai path getter. Perbaiki request-nya — body yang sama akan ditolak terus.

invalid_body400 · 422

400 kalau body sama sekali tidak bisa dibaca dari koneksi. 422 kalau byte-nya sampai tapi tidak bisa di-bind — entah karena bukan JSON, atau ada field dengan tipe JSON yang salah: "amount": "150000" itu JSON yang sah dan tetap ditolak. Body di atas 1 MB bukan kode ini; lihat body_too_large.

Yang perlu dilakukan: Baca pesannya: kalau tipenya tidak cocok, pesannya menyebut nama field dan tipe yang diharapkan, seperti amount must be a number, got string — angka yang diberi tanda kutip oleh library klien Anda tetap sebuah string. Selain itu, periksa JSON dan Content-Type-nya. Hanya yang 400 yang layak di-retry, itu pun karena masalahnya mungkin di koneksi.

body_too_large413

Body request lebih dari 1 MB. Langsung ditolak di batas itu dan tidak pernah di-parse, jadi masalahnya batas dari kami, bukan sintaks Anda — dulu body seperti ini sampai dalam keadaan terpotong lalu dilaporkan sebagai invalid_body, jadi Anda mencari kurung yang sebenarnya tidak pernah hilang.

Yang perlu dilakukan: Kirim lebih sedikit. Create biasanya jauh di bawah satu kilobyte; body sebesar ini biasanya berarti order_items membawa seluruh katalog, atau ada field yang diisi sesuatu yang bukan teks.

payment_method_unavailable422

Ada kode di payment_methods yang tidak ada, atau ada tapi tidak aktif di akun Anda. Kode yang tidak bisa Anda pakai ditolak, bukan diam-diam diabaikan: kalau tidak, salah ketik seperti va_bca_ bakal membuat checkout Anda kehilangan satu bank tanpa ketahuan.

Yang perlu dilakukan: error.fields menyebut kode yang bermasalah. Ambil daftar metode akun Anda dari GET /v1/payment_methods, jangan di-hard-code — metode yang aktif bisa berubah tanpa rilis di sisi Anda.

customer_required422

Metode yang mungkin dipakai pembayaran ini membutuhkan data pembeli yang tidak Anda kirim: Virtual Account butuh customer.name, kartu butuh customer.email. Metode mana yang diperiksa mengikuti apa yang Anda kirim — setiap kode di payment_methods, atau, kalau Anda tidak menyebut satu pun, satu metode yang akan dipakai pembayaran itu. Di Direct API — tanpa object checkout — ini jadi error, karena tidak ada halaman yang bisa menanyakannya. Kalau object checkout dikirim, ini baru jadi error kalau Anda juga membuang langkah customer dari checkout.steps, karena kalau tidak, formulirnya yang mengumpulkan data itu.

Yang perlu dilakukan: error.fields menyebut field yang kurang. Kirim di customer — bukan payer, yang tidak memenuhi syarat metode. Kalau tidak mau mengumpulkannya sendiri, pakai Checkout.

order_items_mismatch422

order_items dikirim dan jumlah price × quantity tidak sama dengan amount.

Yang perlu dilakukan: Samakan totalnya, atau buang order_items — isinya cuma untuk tampilan, dan yang dibayar pembeli tetap amount.

invalid_cursor422

starting_after di endpoint list berisi id payreq_ yang bukan milik Anda — punya akun lain, atau sudah tidak ada.

Yang perlu dilakukan: Pakai id terakhir dari data halaman sebelumnya, persis seperti yang dikembalikan. Nilai tanpa awalan payreq_ — uuid polos — ditolak dengan kode yang sama ini, bukan diam-diam dianggap halaman pertama.

invalid_filter422

created_after atau created_before di endpoint list bukan timestamp RFC3339.

Yang perlu dilakukan: Kirim bentuk lengkapnya, 2026-08-11T00:00:00+07:00, bukan 2026-08-11. Encode + pada offset zona waktu jadi %2B kalau HTTP client Anda belum melakukannya: di query string, + polos berarti spasi. Nilai yang tidak bisa di-parse ditolak, bukan diabaikan, jadi filter yang diterima memang benar-benar berlaku.

Idempotency

invalid_idempotency_key400

Header Idempotency-Key lebih dari 255 byte. Batasnya byte, bukan karakter, jadi key dari teks non-ASCII habis lebih cepat daripada kelihatannya. Tidak mengirim key sama sekali bukan error — artinya Anda memilih tanpa proteksi retry.

Yang perlu dilakukan: Pakai key yang lebih pendek. UUID hanya 36 byte dan selalu muat.

idempotency_conflict409

Key ini sudah menempel di salah satu permintaan pembayaran Anda, dan body kali ini berbeda dari body yang membuatnya. Perbandingannya persis byte yang dikirim, jadi urutan key yang bertukar atau spasi yang berubah pun terhitung body berbeda.

Yang perlu dilakukan: Pembayaran baru butuh key baru. Retry butuh body yang byte-nya identik. Yang asli tetap aman — tapi response ini tidak menyebutkannya: tidak ada id di body, dan kalau key-nya berasal dari header, bisa jadi tidak ada data di pembayaran itu yang bisa dicari. Cari dari catatan Anda sendiri untuk request pertama, atau list permintaan pembayaran terbaru lalu cocokkan created_at. Simpan id yang dikembalikan saat create, supaya kasus ini tidak jadi masalah.

Batas

rate_limited429

Lebih dari 300 request per menit untuk API key ini, atau lebih dari 30 autentikasi gagal per menit dari satu IP.

Yang perlu dilakukan: Tunggu sampai menitnya lewat lalu retry — dengan Idempotency-Key yang sama, supaya create yang ternyata sudah masuk dikembalikan, bukan dibuat dua kali.

unverified_count_cap429

Onboarding akun ini belum aktif, dan jatah permintaan pembayaran seumur akunnya sudah terpakai (default 10). Permintaan canceled tidak dihitung; yang expired dan failed tetap dihitung.

Yang perlu dilakukan: Menunggu tidak mengubah apa pun — jatah ini tidak pernah reset. Selesaikan verifikasi akun; akun terverifikasi tidak punya batas harian. Lihat /docs/api/limits.

unverified_amount_cap429

Onboarding akun ini belum aktif, dan request ini akan membuat total nominal yang bisa diterimanya melewati jatah seumur akun (default Rp1.000.000). Yang dihitung hanya permintaan pending dan succeeded.

Yang perlu dilakukan: Selesaikan verifikasi akun. amount yang lebih kecil mungkin masih muat, dan permintaan yang expired atau canceled melepaskan kembali jatahnya, tapi keduanya bukan pengganti verifikasi.

amount_too_small422

amount di bawah minimum metode — default QRIS Rp1.000, VA dan kartu Rp10.000.

Yang perlu dilakukan: Naikkan nominalnya. Ini terpisah dari aturan validation_failed, yang menolak apa pun di luar 1–1.000.000.000 berapa pun minimum akun Anda.

amount_too_large422

amount di atas maksimum akun — default Rp10.000.000.

Yang perlu dilakukan: Pecah pesanannya, atau minta support menaikkan plafonnya.

expiry_too_long422

expires_in_minutes melewati plafon akun — default 24 jam. Nilai di atas 10080 ditolak lebih awal sebagai validation_failed.

Yang perlu dilakukan: Minta masa berlaku yang lebih pendek, atau minta support menaikkan plafonnya.

expiry_too_short422

Masa berlakunya jatuh ke nol atau kurang. expires_in_minutes di bawah 1 ditolak lebih awal sebagai validation_failed, jadi di API ini satu-satunya jalan ke sini adalah default masa berlaku yang salah di sisi kami.

Yang perlu dilakukan: Kalau sampai muncul, itu konfigurasi kami dan bukan request Anda. Beri tahu kami.

Status

not_found404

Tidak ada permintaan pembayaran dengan id itu di akun Anda — id keliru, id milik akun lain, atau id yang dikirim tanpa awalan payreq_, yang ditolak tanpa pencarian.

Yang perlu dilakukan: Periksa id-nya, termasuk awalannya. "Bukan milik Anda" dan "tidak ada" sengaja dijawab sama, supaya ini tidak pernah membenarkan bahwa id milik orang lain itu nyata.

Refund

refund_not_supported422

Pembayarannya bukan kartu. Hanya kartu yang bisa di-refund lewat API; refund QRIS dan Virtual Account dilakukan manual.

Yang perlu dilakukan: Refund dari dashboard, atau di luar API. Tidak ada yang bisa diubah di request untuk mengatasinya.

not_refundable409

Pembayaran belum succeeded — masih pending, atau sudah expired, canceled, atau failed.

Yang perlu dilakukan: Hanya pembayaran succeeded yang memegang dana untuk dikembalikan. Periksa status di GET /v1/transactions/:id dulu.

refund_exceeds_amount422

amount melebihi sisa yang belum di-refund pada pembayaran itu. Setelah refund sebagian, amount yang dikosongkan tidak lagi berarti total semula.

Yang perlu dilakukan: Kirim nominalnya secara eksplisit, paling banyak sebesar sisanya. Tidak ada dana yang bergerak.

refund_refused502

Processor kartu menolak refund-nya. Tidak ada dana yang bergerak; nominalnya masih bisa di-refund.

Yang perlu dilakukan: Aman untuk di-retry, dengan Idempotency-Key yang sama atau yang baru. Kalau terus ditolak, beri tahu kami id refund-nya.

refund_unresolved502

Processor kartu tidak bisa dihubungi, jadi kami tidak tahu apakah refund-nya berhasil. Nominalnya ditahan pending pada catatan refund sampai diselesaikan manual oleh tim kami.

Yang perlu dilakukan: Jangan retry — percobaan kedua bisa me-refund dua kali. Hubungi support Kasera Pay dengan id pembayarannya.

Sisi kami

internal500

Ada yang gagal di sisi kami — database, dependensi, atau bug. Tidak ada detail karena memang tidak ada yang bisa Anda lakukan: bukan request Anda penyebabnya.

Yang perlu dilakukan: Retry dengan Idempotency-Key yang sama — justru untuk kasus inilah key itu ada, dan create yang ternyata sudah masuk dikembalikan, bukan dibuat dua kali. Kalau terus terjadi, itu urusan kami: beri tahu kami waktunya dan, kalau ada, id-nya.

upstream_unavailable502/503/504

Gateway di depan API tidak mendapat jawaban dari API — API sedang deploy, restart, atau down. Status-nya sesuai yang diterima gateway; body-nya tetap envelope yang sama, jadi error.code tetap bisa di-parse. Hanya kelompok error inilah yang dihasilkan di depan API, bukan oleh API.

Yang perlu dilakukan: Retry dengan backoff dan Idempotency-Key yang sama. Bukan request Anda penyebabnya, dan tidak ada yang sampai ke API.