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
{
"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
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:
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.
#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. Kalau Anda memproses data besar, sisipkan jeda kecil antar halaman, atau persempit rentangnya dengan from dan to lalu jalankan per periode.
#Selanjutnya
invalid_cursor— kalau cursor ditolak- Rate limit — jangan menelusuri terlalu cepat
- Referensi API — filter yang tersedia di tiap endpoint