Referensi

Error

Semua error memakai satu bentuk.

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

Cocokkan pada error.code, jangan pada error.message — kode itulah kontraknya, pesannya cuma prosa dan bisa diubah kata-katanya. Hanya validation_failed yang menambah field ketiga, error.fields, yang menyebut aturan-aturan yang dilanggar, dengan kunci berupa jalur tiap field di body yang Anda kirim.

Berikut semua kode yang bisa dikembalikan /v1. Patokan kasarnya: 4xx selain 429 berarti request-nya sendiri yang salah dan akan tetap salah, jadi mengulanginya apa adanya 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 di-retry saja juga.

Autentikasi & akses

unauthorized401

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

Yang perlu dilakukan: Kirim Authorization: Bearer kp_live_…. Kalau headernya sudah benar, berarti key-nya mati — terbitkan 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 permintaan apa pun yang bukan GET — datang membawa header Origin yang tidak ada di daftar izin kami. Ini pengaman CSRF untuk browser, dan letaknya paling depan: diperiksa sebelum API key Anda, jadi bisa menjawab permintaan yang belum terautentikasi sekalipun. Panggilan tanpa header Origin sama sekali — persis yang dikirim sebuah server — lolos begitu saja, dan GET tidak pernah terkena.

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 pantas berada di browser. Perhatikan, ini 403 kedua di API ini — cocokkan pada error.code, jangan pada statusnya, untuk membedakannya dari merchant_suspended.

Validasi permintaan

validation_failed422

Ada field body yang melanggar aturannya: 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, 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 bermasalah beserta aturan yang dilanggarnya, dengan kunci berupa jalur 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. Semua pakai titik, tanpa kurung siku, jadi kuncinya bisa langsung dipakai untuk menelusuri body. 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 dipetakan — 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. Ditolak tepat di batas itu dan tidak pernah di-parse, jadi ini batas kami yang bicara, bukan sintaks Anda — dulu body seperti ini sampai dalam keadaan terpotong lalu dilaporkan sebagai invalid_body, yang membuat Anda mencari kurung yang sebenarnya tidak pernah hilang.

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

order_items_mismatch422

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

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

invalid_cursor422

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

Yang perlu dilakukan: Gunakan 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 daftar bukan timestamp RFC3339.

Yang perlu dilakukan: Kirim bentuk lengkapnya, 2026-08-11T00:00:00+07:00, bukan 2026-08-11. Encode + pada offset zona menjadi %2B kalau klien HTTP Anda belum melakukannya: di query string, + polos berarti spasi. Batas yang tidak bisa diurai ditolak, bukan diabaikan, jadi filter yang Anda lihat 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 tidak memakai perlindungan 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 tidak tersentuh — tapi respons ini tidak menyebutkannya: tidak ada id di dalam body, dan kalau key-nya berasal dari header, bisa jadi tidak ada apa pun di pembayaran itu yang bisa dijadikan bahan pencarian. Pulihkan dari catatan Anda sendiri atas percobaan pertama, atau ambil daftar permintaan terbaru lalu cocokkan pada created_at. Menyimpan id yang dikembalikan saat create itulah yang membuat ini tetap bukan masalah.

Batas

rate_limited429

Lebih dari 300 permintaan per menit pada 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 sebenarnya sudah masuk dikembalikan, bukan dibuat dua kali.

daily_count_cap429

Akun ini sudah menerbitkan permintaan pembayaran sebanyak batas hariannya.

Yang perlu dilakukan: Ini bukan batas laju yang tinggal ditunggu sebentar: jendelanya satu hari kalender Asia/Jakarta, jadi baru lepas tengah malam WIB. Minta support menaikkan batasnya kalau volumenya memang segitu.

daily_amount_cap429

Permintaan ini akan membuat total nominal yang diterbitkan hari ini melewati batas akun. Dihitungnya sama, per hari Asia/Jakarta.

Yang perlu dilakukan: Juga lepas tengah malam WIB, atau minta support menaikkannya. amount yang lebih kecil mungkin masih muat di sisa hari ini.

amount_too_small422

amount di bawah minimum akun — default 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.

Sisi kami

internal500

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

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

upstream_unavailable502/503/504

Gerbang di depan API tidak mendapat jawaban darinya — API sedang deploy, restart, atau mati. Statusnya sesuai yang dilihat gerbang; body-nya tetap amplop yang sama, jadi error.code tetap bisa di-parse. Inilah satu-satunya keluarga error 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.