Pagination
The API uses two pagination styles. Which one you get depends on the endpoint, so check this table before writing a paging loop.
| Endpoint | Style | Parameters |
|---|---|---|
GET /blog/posts | Offset | limit, offset |
GET /changelog | Cursor | limit, cursor |
GET /subscribers | Offset | limit, offset |
GET /docs/tree, GET /docs/{slug} | None | — |
GET /comments, GET /reactions | None | — |
The envelope
Paginated list endpoints return:
{
"data": [],
"pageInfo": { "hasNextPage": true, "endCursor": "djItMS0wLm1keA" },
"totalCount": 58
}hasNextPage— whether more items exist beyond this page. This is the flag to loop on.endCursor— the cursor for the next page, ornullon offset endpoints.totalCount— total matching items, after filters liketag.
GET /subscribers is the exception: it returns only data. Page until you get fewer
rows than you asked for.
Offset paging
async function allPosts(projectId: string) {
const out = [];
const limit = 100; // the maximum
for (let offset = 0; ; offset += limit) {
const res = await fetch(
`https://api.inkform.dev/api/v1/blog/posts?projectId=${projectId}&limit=${limit}&offset=${offset}`,
);
const { data, pageInfo } = await res.json();
out.push(...data);
if (!pageInfo.hasNextPage) return out;
}
}Cursor paging
async function allEntries(projectId: string) {
const out = [];
let cursor: string | null = null;
for (;;) {
const url = new URL('https://api.inkform.dev/api/v1/changelog');
url.searchParams.set('projectId', projectId);
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const { data, pageInfo } = await fetch(url).then((r) => r.json());
out.push(...data);
if (!pageInfo.hasNextPage) return out;
cursor = pageInfo.endCursor;
}
}Cursors are opaque. Don't decode, construct, or persist them across days — pass back exactly what you were handed.
Limits
| Endpoint | Default | Max |
|---|---|---|
/blog/posts | 20 | 100 |
/changelog | 20 | 100 |
/subscribers | 50 | 200 |
Out-of-range values are clamped, not rejected — limit=5000 quietly gives you the
maximum. Don't infer your page size from what you sent; count what came back.
Edge cases
Content changing mid-page. Both styles read a list that a publish can change underneath you. With offsets, an insert can shift items across page boundaries and you may see one twice or miss one. With cursors, deleting the item a cursor points at restarts paging from the beginning rather than erroring.
For a consistent snapshot of a large collection, fetch it in one pass with a high
limit, rather than paging slowly while edits land.
Don't parallelise aggressively. Firing every page at once is the quickest route to a
429. Page sequentially, or bound concurrency to two or three — see
Rate limits.