Pagination

The API uses two pagination styles. Which one you get depends on the endpoint, so check this table before writing a paging loop.

EndpointStyleParameters
GET /blog/postsOffsetlimit, offset
GET /changelogCursorlimit, cursor
GET /subscribersOffsetlimit, offset
GET /docs/tree, GET /docs/{slug}None—
GET /comments, GET /reactionsNone—

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, or null on offset endpoints.
  • totalCount — total matching items, after filters like tag.

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

EndpointDefaultMax
/blog/posts20100
/changelog20100
/subscribers50200

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.