---
title: Menangani error
source: https://se-hari.com/docs/menangani-error
updated: 2026-08-16T01:05:08.241996+00:00
---

> Semua error memakai satu amplop yang sama. Cara membacanya, mana yang layak diulang, dan mana yang tidak akan pernah berhasil.

Setiap kegagalan di Se-Hari API memakai amplop yang sama, apa pun endpoint dan status HTTP-nya:

```json
{
  "error": {
    "code": "insufficient_credits",
    "message": "Kredit tidak cukup. Dibutuhkan 3 kredit, tersisa 1.",
    "docs_url": "https://se-hari.com/docs/error/insufficient-credits",
    "request_id": "req_a1b2c3d4e5f6",
    "details": { "required": 3, "available": 1 }
  }
}
```

## Cabangkan pada code, bukan message

`code` adalah **kontrak publik**. Ia hanya berubah lewat versi API baru, jadi aman dipakai sebagai dasar percabangan logika.

`message` ditujukan untuk manusia dan bisa kami perbaiki kapan saja — memperjelas kalimat, menambahkan angka, menyesuaikan nada. Kode yang mencocokkan teks pesan akan rusak pada perbaikan pertama, dan rusaknya tanpa peringatan.

```javascript
// BENAR
if (error.code === 'insufficient_credits') { … }

// SALAH — akan rusak begitu pesannya diperbaiki
if (error.message.includes('Kredit tidak cukup')) { … }
```

`details` berisi konteks yang bisa dibaca mesin dan bentuknya berbeda per kode: field mana yang ditolak, berapa kredit yang kurang, id data yang sudah ada. Ia sering memuat jawaban yang sebenarnya Anda cari — terutama pada `409 conflict`, di mana `details` menunjuk data yang sudah menempati posisi itu.

## request_id

Setiap respons membawa `request_id`, baik di body error maupun di header `X-Request-Id` pada respons yang berhasil. Ini satu-satunya cara kami menemukan kembali request Anda di log.

Catat dia. Laporan "API-nya error kemarin siang" nyaris tidak bisa ditindaklanjuti; laporan dengan `request_id` bisa ditelusuri dalam hitungan menit.

Anda juga boleh mengirim `X-Request-Id` sendiri, dan kami akan memakainya alih-alih membuat baru — berguna kalau sistem Anda sudah punya id korelasi sendiri.

```javascript
const res = await fetch(url, {
	headers: {
		Authorization: `Bearer ${process.env.SEHARI_API_KEY}`,
		'X-Request-Id': idKorelasiAnda
	}
});
```

## Mana yang layak diulang

Ini pembedaan paling penting dalam menangani error, dan yang paling sering diabaikan. Retry yang salah sasaran menghabiskan kuota rate limit sambil menyembunyikan masalah yang sebenarnya butuh perhatian.

**Jangan pernah diulang tanpa perubahan** — request yang sama akan ditolak dengan cara yang sama:

`400` semua varian, `401`, `403`, `404`, `405`, `409`, `410`, `413`, `415`, `422`.

**Diulang setelah menunggu:**

`429` — tunggu selama `Retry-After`, tidak kurang.

**Diulang dengan `Idempotency-Key` yang sama:**

`500`, `502`, `503`. Ketiganya mungkin meninggalkan pekerjaan setengah jadi, dan key yang sama adalah satu-satunya cara memastikan percobaan berikutnya tidak menghasilkan duplikat.

```javascript
const PERCUMA_DIULANG = new Set([400, 401, 403, 404, 405, 409, 410, 413, 415, 422]);

async function panggilDenganRetry(url, opsi, maks = 3) {
	const key = opsi.idempotencyKey ?? crypto.randomUUID();

	for (let i = 0; i < maks; i++) {
		const res = await fetch(url, {
			...opsi,
			headers: {
				Authorization: `Bearer ${process.env.SEHARI_API_KEY}`,
				'Idempotency-Key': key,
				...opsi.headers
			}
		});

		if (res.ok) return res.json();

		const { error } = await res.json();
		if (PERCUMA_DIULANG.has(res.status)) {
			throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
		}

		const tunggu = res.status === 429
			? Number(res.headers.get('retry-after') ?? 60) * 1000
			: 2 ** i * 1000;
		await new Promise((r) => setTimeout(r, tunggu));
	}

	throw new Error('Gagal setelah beberapa percobaan');
}
```

## Mencatat error dengan berguna

Log yang hanya menuliskan "request gagal" tidak akan pernah membantu siapa pun. Empat hal yang wajib ikut tercatat, dan semuanya sudah tersedia di setiap respons: `code`, `message`, `request_id`, dan `details` — ditambah endpoint mana yang dipanggil.

`request_id` adalah yang paling penting dan yang paling sering hilang. Ia satu-satunya titik temu antara laporan Anda dan log kami; tanpanya, penelusuran berubah menjadi tebak-tebakan tentang waktu dan endpoint.

Yang tidak boleh ikut tercatat: isi header `Authorization`. API key di dalam log adalah kebocoran yang menunggu waktu, terutama karena log sering dikirim ke layanan pihak ketiga dan disimpan jauh lebih lama daripada yang siapa pun sadari. Kalau pustaka HTTP Anda mencatat seluruh header secara bawaan, matikan atau saring lebih dulu.

## Kegagalan asinkron

Notulen diproses di belakang layar, jadi kegagalannya tidak muncul sebagai status HTTP. Ia muncul sebagai `status: "failed"` pada notulen, dan sebagai event webhook `note.failed`.

Payload event itu memuat `retryable`. Bacalah field itu sebelum memutuskan mengulang: notulen yang gagal karena kredit habis akan berhasil segera setelah top up, sedangkan yang gagal karena rekamannya tidak punya audio tidak akan pernah berhasil berapa kali pun diulang.

## Daftar lengkap

Ada 22 kode error, masing-masing punya halamannya sendiri dengan penyebab dan perbaikannya: [semua kode error](/docs/error).

`docs_url` di dalam setiap amplop menunjuk langsung ke halaman yang relevan, jadi Anda bisa mencetaknya di log dan menyusulnya nanti.

## Selanjutnya

- [Semua kode error](/docs/error)
- [Idempotency](/docs/idempotency) — supaya retry aman
- [Rate limit](/docs/rate-limit) — pola retry yang tidak memperburuk keadaan