$ cli-blog docs

Pagination

Traverse content efficiently with cursors, or request exact numbered pages when the interface truly needs totals.

Posts, post revisions, authors, media, categories, and tags support cursor and numbered pagination. We strongly recommend cursor pagination. It does less database work, stays stable during long traversals, and fits load-more, synchronization, export, build, and agent workflows.

Use numbered pagination only when a person needs to see an exact page number or jump to a known page. Exact totals require additional database work, so numbered requests are slower than cursor requests over the same result set.

Choose a mode

NeedParametersRecommendation
Load more, sync, export, audit, or process every resultlimit and afterRecommended
Display “Page 3 of 12” or jump to a pagepage and optional per_pageUse only when exact totals improve the interface

Do not combine cursor and numbered parameters in one request.

Cursor pagination

Start with limit. The default is 20 and the maximum is 100.

curl "https://api.cli-blog.com/v1/posts?locale=en-US&status=published&limit=100" \
  --header "x-api-key: $CLI_BLOG_PUBLIC_KEY"

The list response contains the items and the position of the next page:

{
  "object": "list",
  "data": [],
  "has_more": true,
  "next_cursor": "<opaque-cursor>"
}

When has_more is true, send next_cursor as after and keep every other filter, field group, include, locale, and sort value unchanged:

curl "https://api.cli-blog.com/v1/posts?locale=en-US&status=published&limit=100&after=<opaque-cursor>" \
  --header "x-api-key: $CLI_BLOG_PUBLIC_KEY"

Stop when has_more is false and next_cursor is null.

Why one page stops at 100

The 100-item maximum is a platform safety decision, not a maximum content count. It bounds response bytes, JSON serialization, database work, client memory, and the effect one request can have on other organizations. A larger one-shot response would be slower and less reliable for both the caller and the service.

Retrieve 101 or 100,000 items by following cursors. The page size stays bounded while the total traversal can continue for as many pages as the result set requires.

Traverse every page with REST

This browser-safe example reads published posts with a public key and collects all pages:

const allPosts = [];
let after: string | null = null;

do {
  const url = new URL("https://api.cli-blog.com/v1/posts");
  url.searchParams.set("status", "published");
  url.searchParams.set("locale", "en-US");
  url.searchParams.set("fields", "summary");
  url.searchParams.set("limit", "100");
  if (after) url.searchParams.set("after", after);

  const response = await fetch(url, {
    headers: { "x-api-key": PUBLIC_CLI_BLOG_KEY },
  });

  if (!response.ok) throw new Error(`Cli Blog returned ${response.status}`);

  const page = await response.json();
  allPosts.push(...page.data);
  after = page.has_more ? page.next_cursor : null;
} while (after);

For large exports, process each page or item as it arrives instead of retaining the entire result set in memory.

Traverse every page with the Node SDK

Cursor-list resources expose paginate(), which follows next_cursor for you:

for await (const post of blog.posts.paginate({
  status: "published",
  locale: "en-US",
  limit: 100,
})) {
  await indexPost(post);
}

Posts, revisions, authors, media, categories, and tags all provide a cursor iterator in the Node SDK.

Keep cursors with their query

Treat cursor values as opaque. A cursor belongs to its organization, resource, filters, locale, field selection, includes, and sort. Do not decode it, edit it, or reuse it with a different query.

Changing the query while reusing a cursor returns 400 cursor_query_mismatch. A malformed value returns 400 invalid_cursor.

If a traversal must use new filters or a new sort, discard the cursor and start from the first page.

Numbered pagination

Add the required, one-based page parameter. per_page defaults to 20 and has a maximum of 100.

curl "https://api.cli-blog.com/v1/posts?locale=en-US&status=published&page=3&per_page=20" \
  --header "x-api-key: $CLI_BLOG_PUBLIC_KEY"

Numbered responses add exact metadata for the authorized, filtered result set:

{
  "object": "list",
  "data": [],
  "has_more": true,
  "next_cursor": "<opaque-cursor>",
  "page": 3,
  "per_page": 20,
  "total_items": 227,
  "total_pages": 12
}

Search, locale, workflow, author, category, tag, include/exclude, and relation-match filters are reflected in the exact count. Field groups and includes change the returned object shape, not the count.

If no items match, total_items and total_pages are both 0. A page beyond the final page returns 200 with empty data, the requested page number, the exact totals, has_more: false, and next_cursor: null.

Numbered pages describe the result set at request time. Inserts, deletes, or changes to the sort field between requests can move items between page numbers. Cursor mode is safer for a long-running traversal.

Continue with a cursor after a numbered page

When a numbered response has more results, it also returns next_cursor. To continue efficiently:

  1. Remove page and per_page.
  2. Add after with the returned next_cursor.
  3. Add limit and keep the same filters and sort.
  4. Continue until has_more is false.

This lets a user begin on an exact numbered page while a background job continues through the remaining results without repeated total counts.

Handle pagination errors

RequestResponseFix
per_page without page400 invalid_paginationAdd page or use limit
page or per_page with limit or after400 pagination_mode_conflictKeep only one mode
Malformed cursor400 invalid_cursorRestart from the first page
Cursor used with another query400 cursor_query_mismatchKeep the original query or restart
Number outside the documented range422 validation_errorUse a value from 1 through 100

Resource-specific filters

Apply pagination after choosing the filters for the resource:

On this page