---
title: Paginasi
source: https://se-hari.com/docs/paginasi
updated: 2026-08-16T01:05:08.124124+00:00
---

> Semua daftar memakai cursor, bukan offset. Cara menelusuri seluruh halaman tanpa melewatkan atau menggandakan baris.

Semua endpoint daftar di Se-Hari API memakai **paginasi cursor**. Satu request mengembalikan maksimum 100 baris; untuk sisanya, Anda mengikuti cursor yang kami berikan.

## Bentuk responsnya

```json
{
  "data": [ … ],
  "has_more": true,
  "next_cursor": "MjAyNi0wOC0xNVQwOToxMjozNC42ODg1MjkrMDA6MDB8Mjc0ZDE4MDktNGVkNy00NDkyLWE2ZWUtNmUyMmM1MjY3NTQ4"
}
```

Tiga field, dan hanya `has_more` yang perlu Anda periksa untuk tahu kapan berhenti. Jangan mengandalkan panjang `data`: halaman terakhir bisa saja berisi tepat 100 baris, dan menganggap itu berarti "masih ada lagi" akan membuat satu request tambahan yang sia-sia — atau lebih buruk, menganggap halaman berisi 40 baris berarti selesai padahal tidak.

## Menelusuri semua halaman

```javascript
async function ambilSemua(path, params = {}) {
	const semua = [];
	let cursor = null;

	do {
		const qs = new URLSearchParams({ ...params, limit: '100' });
		if (cursor) qs.set('starting_after', cursor);

		const res = await fetch(`https://se-hari.com/api/v1${path}?${qs}`, {
			headers: { Authorization: `Bearer ${process.env.SEHARI_API_KEY}` }
		});

		if (!res.ok) {
			const { error } = await res.json();
			throw new Error(`${error.code}: ${error.message}`);
		}

		const halaman = await res.json();
		semua.push(...halaman.data);
		cursor = halaman.has_more ? halaman.next_cursor : null;
	} while (cursor);

	return semua;
}

const semuaNotulen = await ambilSemua('/notes', { status: 'completed' });
```

Versi shell, untuk sekadar mengambil semuanya:

```bash
cursor=""
while :; do
  url="https://se-hari.com/api/v1/notes?limit=100"
  [ -n "$cursor" ] && url="$url&starting_after=$cursor"

  resp=$(curl -s "$url" -H "Authorization: Bearer $SEHARI_API_KEY")
  echo "$resp" | jq -c '.data[]'

  [ "$(echo "$resp" | jq -r '.has_more')" = "true" ] || break
  cursor=$(echo "$resp" | jq -r '.next_cursor')
done
```

## Kenapa cursor, bukan offset

Offset (`?page=2`) terlihat lebih sederhana, tapi ia rusak pada data yang bertambah — dan rusaknya tanpa suara.

Bayangkan Anda menelusuri daftar notulen yang diurutkan dari yang terbaru. Anda mengambil halaman 1 (baris 1–100). Sementara Anda memproses hasilnya, dua notulen baru selesai dan masuk ke posisi teratas. Saat Anda meminta halaman 2 (baris 101–200), dua baris yang tadinya di posisi 99 dan 100 kini bergeser ke 101 dan 102 — dan Anda memprosesnya untuk **kedua kalinya**, sementara dua baris lain tidak pernah Anda lihat.

Tidak ada error. Tidak ada tanda apa pun. Anda hanya mendapat data yang salah.

Cursor menandai posisi baris, bukan nomor urut, sehingga baris yang masuk belakangan tidak menggeser apa pun. Pada situs ini, daftar notulen bertambah setiap hari — jadi itu bukan kasus langka melainkan kejadian harian.

## Menelusuri sambil memproses

Contoh di atas mengumpulkan semua baris ke dalam satu array sebelum mengembalikannya. Itu nyaman untuk ratusan baris, tapi menjadi masalah untuk puluhan ribu — seluruh isinya harus muat di memori sekaligus.

Kalau setiap baris diproses secara independen, proses per halaman saja dengan generator: baris diserahkan satu per satu, dan hanya satu halaman yang hidup di memori pada satu waktu.

Pola itu juga memberi jeda alami di antara halaman, karena pemrosesan tiap baris memakan waktu. Hasilnya, Anda jauh lebih kecil kemungkinannya menyentuh rate limit dibanding perulangan yang hanya mengambil dan menumpuk secepat mungkin. Untuk pekerjaan yang benar-benar besar, tambahkan jeda eksplisit antar halaman dan pertimbangkan mempersempit rentangnya dengan filter tanggal lalu menjalankannya per periode.

## Cursor bersifat buram

`next_cursor` adalah token yang isinya bukan urusan Anda. Kirim kembali persis seperti yang kami berikan.

Yang tidak boleh dilakukan:

- Membuatnya sendiri, misalnya diisi id baris terakhir
- Memotongnya untuk log atau menyimpannya di kolom yang terlalu pendek — cursor panjangnya sekitar 92 karakter dan bisa bertambah
- Meng-encode ulang; sebagian HTTP client meng-URL-encode dua kali kalau Anda tidak hati-hati
- Memakai cursor dari endpoint lain — setiap daftar punya ruang cursornya sendiri

Semuanya berakhir di [`invalid_cursor`](/docs/error/invalid-cursor).

## Kalau parameternya salah bentuk

Tiga hal ditolak yang mungkin Anda kira diterima, dan ketiganya ditolak justru supaya Anda tidak menerima jawaban yang salah tanpa tahu:

**Rentang terbalik.** `?from=2026-08-10&to=2026-08-01` menghasilkan `400`, bukan daftar kosong. Rentang terbalik selalu kosong, dan daftar kosong terlihat persis seperti "memang tidak ada datanya".

**Tanggal non-ISO.** `?from=10/08/2026` ditolak. JavaScript membaca format itu sebagai 8 Oktober, bukan 10 Agustus — jadi menerimanya berarti memberi Anda rentang dua bulan meleset, dengan status 200. Pakai `2026-08-10` atau `2026-08-10T00:00:00Z`.

**Angka non-desimal.** `?limit=0x10` dan `?limit=1e3` ditolak, meski JavaScript bisa membacanya. Parameter kosong (`?limit=`) diperlakukan sebagai tidak diisi, bukan sebagai nol — itu yang dikirim banyak form builder dan node otomasi untuk field yang dibiarkan kosong.

## Filter dan urutan

Semua daftar diurutkan dari yang **terbaru** lebih dulu. Filter yang tersedia berbeda per endpoint; yang umum:

| Parameter | Keterangan |
|---|---|
| `limit` | 1–100, bawaan 20 |
| `starting_after` | Cursor dari halaman sebelumnya |
| `status` | Menyaring berdasarkan status entitas |
| `from` / `to` | Rentang waktu ISO-8601 |

Filter tanggal memakai zona **WIB (UTC+7)** seperti seluruh Se-Hari. Menyaring dengan tanggal UTC polos akan memotong tujuh jam pertama setiap hari tanpa memberi tahu Anda.

## Batasi diri Anda sendiri

Menelusuri ribuan baris dalam satu perulangan rapat akan menyentuh [rate limit](/docs/rate-limit). Kalau Anda memproses data besar, sisipkan jeda kecil antar halaman, atau persempit rentangnya dengan `from` dan `to` lalu jalankan per periode.

## Selanjutnya

- [`invalid_cursor`](/docs/error/invalid-cursor) — kalau cursor ditolak
- [Rate limit](/docs/rate-limit) — jangan menelusuri terlalu cepat
- [Referensi API](/docs/api) — filter yang tersedia di tiap endpoint