GenuineAIGenuineAI
For usersFor developers
  • Overview
  • Guides
  • How-to
  • API reference
Documentation
  • Quickstart
  • Authentication
  • Errors
  • Rate limits
  • API reference
Platform
  • Platform overview
  • Solutions
  • Pricing
  • Sign in
Company
  • Status
  • Trust Center
  • Contact
  • Terms of Service
  • Privacy Policy

© 2026 GenuineAI Ventures LLC

support@genuinehq.com
Start here
QuickstartAuthentication
Working with the API
Objects and fieldsErrorsPaginationRate limitsMaintenance windowsVersioning and deprecation
Security
Security and tenancyRoles and permissionsPermission referenceSharing and access
Your plan
Plans and modules
Guides

Pagination

Every list endpoint answers with the same envelope:

Code
{ "data": [ … ], "has_more": true, "next_cursor": "eyJzIjoiMjAyNi0wOC0wOVQxMjozNDo1NloiLCJpIjoi…" }
Field
dataThe rows.
has_moreWhether more rows exist past this page.
next_cursorPass back as cursor to get the next page. null on the last page.

has_more is not inferred from the page being full. The API requests one row more than you asked for and reports whether it came back, so a full page with has_more: false is an accurate answer rather than an estimate.

Page with a cursor

Send the previous response's next_cursor back as cursor:

Stop when has_more is false:

The cursor is opaque. It is base64url and encodes a position in the sort order, but its contents are not part of the contract, so decoding it will break your client. A cursor the API cannot read is ignored rather than rejected, and you get the first page back.

A cursor belongs to the sort it was issued under. Change sort between pages and the position no longer means anything. Start the pass again instead.

Why cursors rather than offsets

Cost. Several of the tables you are most likely to page (messages, tasks, threads, usage) are partitioned by time, and a deep offset has to scan and discard rows across partitions to reach the requested starting point. It gets slower the further you go, and it degrades fastest for the workspaces with the most data. A keyset cursor costs the same on page 900 as on page 2.

Cursors are also stable under writes. With offset, a row inserted while you page shifts everything down by one and you read a row twice; delete a row and you skip one entirely. A cursor records a position in the data rather than a count of rows already seen.

offset is still accepted for page-number grids that need to jump to page 12, but it is not the recommended approach for integrations.

Sorting

Use sort=name for ascending and sort=-created for descending. Sortable columns are declared per resource. A column that is not sortable falls back to the resource's default order rather than being sorted on, so check the results rather than assuming.

Limits

ParameterDefaultMaximum
limit501000
limit_extended—10000

Where an endpoint accepts limit_extended, it is intended for exports and bulk reads rather than interactive listing.

Asking for more than the maximum returns the clamped page with has_more: true, so the loop above terminates correctly regardless of what you requested.

Filtering

Collections filter on their own columns, using the column name directly:

Code
GET /api/v1/agents?agent_active=true GET /api/v1/files?repo_type=ds&repo_id=<kb-id>&file_name=invoice

Each operation's page in the reference lists the filters it accepts. They are read from the validators that enforce them, so the list matches the endpoint's actual behavior.

Not every list is paginated yet

The envelope is being adopted resource by resource. A collection that has not been converted returns a bare JSON array, so check the shape rather than assuming:

This is a temporary compatibility check. New resources ship with the envelope, and converted resources do not revert.

Last modified on October 8, 2026
ErrorsRate limits
On this page
  • Page with a cursor
  • Why cursors rather than offsets
  • Sorting
  • Limits
  • Filtering
  • Not every list is paginated yet
JSON
curl "https://api.genuineai.app/api/v1/files?repo_type=media&limit=100" \ -H "X-Api-Key: gai_…" -H "X-Tenant-Id: <workspace-id>" curl "https://api.genuineai.app/api/v1/files?repo_type=media&limit=100&cursor=eyJzIjoi…" \ -H "X-Api-Key: gai_…" -H "X-Tenant-Id: <workspace-id>"
let cursor = null; do { const url = new URL("https://api.genuineai.app/api/v1/files"); url.searchParams.set("repo_type", "media"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const page = await fetch(url, { headers }).then(r => r.json()); await handle(page.data); cursor = page.next_cursor; } while (cursor);
const rows = Array.isArray(body) ? body : body.data;