Resource IDs
The prefixed identifier scheme used in every page. Read →
Every list endpoint in kirim.dev uses the same opaque cursor
contract — phone-scoped resources like
GET /v1/{phone_number_id}/messages and root-level resources like
GET /v1/labels behave identically.
GET /v1/106540352242922/messages?limit=25&cursor=eyJpZCI6Im1zZ18wMUhYWVoiLCJ0cyI6IjIwMjYtMDUtMjNUMTA6MDA6MDAuMDAwWiJ9 HTTP/1.1| Query param | Default | Range | Notes |
|---|---|---|---|
limit | 25 | 1–100 | Page size. Out-of-range → 400 invalid_limit. |
cursor | — | opaque | Echo next_cursor from the previous response, verbatim. |
Resource-specific filters (status, conversation_id, direction,
created_after, …) live alongside cursor — see each endpoint’s
reference for the exact set.
{ "data": [ { "id": "msg_01HXYZ…" }, { "id": "msg_01HXYY…" } ], "has_more": true, "next_cursor": "eyJpZCI6Im1zZ18wMUhYWVoiLCJ0cyI6IjIwMjYtMDUtMjNUMTA6MDA6MDAuMDAwWiJ9", "request_id": "req_01HXZ0ABCDEFGHJKMNPQRSTVWX"}| Field | Meaning |
|---|---|
data | Array of resources (zero or more). |
has_more | true if at least one more page is available. |
next_cursor | Opaque string to pass back on the next call. null when has_more is false. |
request_id | Echoes the X-Request-Id header. |
import { Kirim } from '@kirimdev/sdk'
const kirim = new Kirim({ apiKey: process.env.KIRIM_KEY! })const phone = kirim.phoneNumbers('106540352242922')
// Async iterator — pages fetched lazily.for await (const message of phone.messages.list({ conversation_id: 'cnv_01HXYZABCDEFGHJKMNPQRSTVWX', limit: 50,})) { console.log(message.id, message.status)}
// Or materialise (small lists only):const labels = await kirim.labels.list().all()URL="https://api.kirim.dev/v1/106540352242922/messages?limit=50&conversation_id=cnv_01HXYZABCDEFGHJKMNPQRSTVWX"
while [ -n "$URL" ]; do RESP=$(curl -sS -H "Authorization: Bearer $KIRIM_KEY" "$URL") echo "$RESP" | jq -c '.data[]'
HAS_MORE=$(echo "$RESP" | jq -r '.has_more') CURSOR=$(echo "$RESP" | jq -r '.next_cursor')
if [ "$HAS_MORE" = "true" ]; then URL="https://api.kirim.dev/v1/106540352242922/messages?limit=50&conversation_id=cnv_01HXYZABCDEFGHJKMNPQRSTVWX&cursor=$CURSOR" else URL="" fidoneimport httpx
def paginate(client: httpx.Client, url: str, **filters): cursor = None while True: params = {"limit": 50, **filters} if cursor: params["cursor"] = cursor resp = client.get(url, params=params) resp.raise_for_status() body = resp.json() yield from body["data"] if not body["has_more"]: return cursor = body["next_cursor"]
with httpx.Client(headers={"Authorization": f"Bearer {KIRIM_KEY}"}) as client: for msg in paginate( client, "https://api.kirim.dev/v1/106540352242922/messages", conversation_id="cnv_01HXYZABCDEFGHJKMNPQRSTVWX", ): print(msg["id"], msg["status"])400 invalid_cursor.status=open to status=resolved
between pages invalidates the cursor — start a fresh request.(created_at DESC, id DESC) means a row inserted while you’re
paginating either appears on a page you’ve already seen (and is
missing from later pages — accept this) or on a page you’ll see
later, always with deterministic ordering.Different resources have different natural orderings — pagination itself is the same.
| Endpoint | Order |
|---|---|
GET /v1/{phone_number_id}/messages | (created_at DESC, id DESC) |
GET /v1/{phone_number_id}/contacts | (created_at DESC, id DESC) |
GET /v1/{phone_number_id}/conversations | (last_message_at DESC NULLS LAST, id DESC) |
GET /v1/{phone_number_id}/templates | (created_at DESC, id DESC) |
GET /v1/labels | (created_at DESC, id DESC) |
GET /v1/accounts | (created_at DESC, id DESC) |
GET /v1/webhook_subscriptions | (created_at DESC, id DESC) |
GET /v1/webhook_deliveries | (created_at DESC, id DESC) |
There is no ?sort= parameter — the default is the only order. A
future ?sort=… parameter can be added non-breakingly with the
current default still applying when unset.
?limit=100 then drop most of the results client-side.
Apply the resource-specific filters server-side; you save your read
budget and reduce latency.next_cursor. A bug that forgets to pass
cursor will paginate the first page repeatedly. Always exit when
has_more is false.