Paging results
Which nima endpoints return pages, the page parameters each takes, the 3000-row ceiling, and which lists are capped arrays instead of pages.
Seven endpoints you can call with an API key or token return a page. Every other list-shaped response they give is a bounded array, a text window, or a cursor.
Paging is offset based. There are no cursors on the list endpoints and no
Link header. Some endpoints spell the page size limit and some spell it
pageSize, and a wrong spelling is ignored without an error.
Paging is on for every workspace. The outbound-action list stays empty until a workspace owner or admin turns outbound dispatch on for the workspace; see Actions. For the operations list, see Operations.
Paginated endpoints and their page parameters
Seven endpoints take page and a page size and answer with data and meta.
| Path | Scope | MCP tool | Page parameters |
|---|---|---|---|
GET /v1/signals | signals:read | search_signals | page, limit |
GET /v1/beacons | signals:read | list_beacons | page, limit |
GET /documents | documents:read | list_specs | page, pageSize, sort |
GET /documents/{id}/versions | versions:read | none | page, pageSize, sort |
GET /v1/operations | documents:read | list_operations | page, pageSize |
GET /v1/inbox | inbox:read | get_inbox | page, limit |
GET /v1/dispatch/actions (alias GET /v1/actions) | actions:approve | list_pending_actions | page, limit |
/documents carries no /v1 prefix. That is its real path today.
A document keeps only its 10 most recent versions, so
/documents/{id}/versions always fits on one page at the default size.
actions:approve cannot be minted for an API key or OAuth client today, so
GET /v1/dispatch/actions is reachable only with a signed-in session. See
Scopes.
| Parameter | Default | Minimum | Maximum over HTTP |
|---|---|---|---|
page | 1 | 1 | none on /v1/operations; on the other six, page × page size must stay at or under 3000 (see the 3000-row paging ceiling) |
limit | 20 | 1 | 100 |
pageSize | 20 | 1 | 100 |
Through MCP, search_signals, list_beacons and list_specs cap the page
size at 50. list_operations, get_inbox and list_pending_actions allow 100.
The two names are not interchangeable. pageSize sent to /v1/signals,
/v1/beacons, /v1/inbox or /v1/dispatch/actions is ignored and you get 20
rows. limit sent to /documents or /v1/operations is ignored and you get
20 rows. Read meta.pageSize to see what the server actually used.
Page response shape
Every paginated endpoint answers with data (the page) and meta (counts).
curl -s "https://api.getnima.io/v1/signals?page=2&limit=25&sortBy=newest" \
-H "Authorization: Bearer $NIMA_API_KEY"{
"data": [
{
"id": "3f8a1c62-9d47-4b21-8e0a-5c7d2b64e913",
"preview": "Bulk export times out on workspaces over 40k rows",
"status": "ready",
"classification": "bug",
"impact": "high",
"source": "slack",
"sourceCount": 3,
"createdAt": "2026-09-18T09:14:02.441Z"
}
],
"meta": { "page": 2, "pageSize": 25, "totalItems": 148, "totalPages": 6 }
}One item is shown and its fields are abbreviated. The full object is on the resource page.
| Field | Type | Meaning |
|---|---|---|
data | array | The page. Empty array past the last page, never null. |
meta.page | number | The page you got, 1 based. |
meta.pageSize | number | Rows per page after clamping, which is not always what you asked for. |
meta.totalItems | number | Rows matching the query. |
meta.totalPages | number | totalItems / pageSize rounded up, and exactly 0 when nothing matched. |
meta.countMode | "exact" or "estimated" | How totalItems was obtained. Absent on almost every response, and absent means exact. |
The same meta comes back through the MCP tools. search_signals,
list_beacons and list_specs trim each item in data to a summary; the other
list tools return the same items as HTTP. The operations list returns only the
first line of each operation's endDetail, at most 300 characters, on both surfaces; call
get_operation for the full text.
Signal grouping changes the response shape
GET /v1/signals accepts groupBy=impact, groupBy=product_area or
groupBy=classification. Any of those replaces the response with a different
object, and meta loses everything but the total:
{
"groups": [
{ "key": "high", "label": "High Impact", "count": 12, "data": [] },
{ "key": "low", "label": "Low Impact", "count": 8, "data": [] }
],
"meta": { "totalItems": 148 }
}Each group's data holds its signals, left out above. The groups are built from
the rows on the current page only, so count is a count within that page and
never a count across the query. groupBy=none returns the normal paged shape.
Any other value returns a single group keyed all. Grouping is an HTTP
parameter; the search_signals tool does not accept it.
The 3000-row paging ceiling
page multiplied by the effective page size must be 3000 or less on
/v1/signals, /v1/beacons, /documents, /documents/{id}/versions,
/v1/inbox and /v1/dispatch/actions. The check runs before the query, so it
costs you nothing but a 400. The page size is clamped to 100 first, which means a
larger page size buys no extra depth.
GET /v1/operations is the exception: it has no ceiling and pages by plain
offset.
| Page size | Last reachable page |
|---|---|
| 20 | 150 |
| 25 | 120 |
| 50 | 60 |
| 100 | 30 |
curl -s "https://api.getnima.io/v1/signals?page=200&limit=20" \
-H "Authorization: Bearer $NIMA_API_KEY"{
"statusCode": 400,
"error": "VALIDATION_ERROR",
"message": "Cannot page beyond 3000 rows. Narrow the filter instead of paging deeper.",
"details": { "page": 200, "pageSize": 20, "maxOffset": 3000 },
"timestamp": "2026-09-22T11:04:18.902Z",
"path": "/v1/signals?page=200&limit=20"
}There is no export endpoint and no cursor to fall back on. To reach rows past
the ceiling, narrow the query instead: filter by status, classification,
source or impact, or sort by newest and poll the top of the list rather
than walking to the bottom of it.
Walk a paginated list in a loop
To read every page, repeat the request with page increasing until a page comes
back shorter than the page size. Keep the filters and the page size identical
across pages; changing either mid-walk reshuffles what lands on which page.
page=1
while [ "$page" -le 150 ]; do
body=$(curl -s "https://api.getnima.io/v1/signals?page=$page&limit=20" \
-H "Authorization: Bearer $NIMA_API_KEY")
echo "$body" | jq -c '.data[]'
[ "$(echo "$body" | jq '.data | length')" -lt 20 ] && break
page=$((page + 1))
doneStop on a short page rather than on totalPages, because totalPages can be an
estimate (next section). Every ordering on the signal, beacon and document lists
ends in a unique tiebreak key, so page boundaries hold for as long as the
underlying rows do. Version history orders on creation time alone.
None of these endpoints carries a route limit of its own; all fall to the default
of 100 requests per 60 seconds, counted per client IP address rather than per
key. A tight walk of 150 pages will hit it. A 429 carries a Retry-After
header in seconds. See Rate limits.
Estimated counts on filtered signal lists
meta.countMode appears on GET /v1/signals only, and only when the request
carried a filter whose predicate no index can drive: the fields origin,
productArea, stakeholder, inBeacon and hasSpec, or any or group with
more than one condition. Every other request, and every other endpoint, counts
exactly and omits the field.
When countMode is "estimated", totalItems is the query planner's guess
and totalPages is derived from that guess. Both can move between two pages
of the same query. Treat them as a rough size, not as a loop bound.
A short page is always the last page, so it reports "exact" with a real total
no matter what the estimate said. If the estimate is unavailable the request
falls back to an exact count silently, and countMode is absent again.
Sort parameters on paginated lists
| Endpoint | Parameter | Values | Default | Direction |
|---|---|---|---|---|
/v1/signals | sortBy | priority, newest, volume | priority | sortOrder=asc or desc, honoured on newest and volume only |
/v1/beacons | sortBy | rank, newest, evidence | rank | always descending, there is no sortOrder |
/documents | sort | JSON array, see below | position, then most recently updated | fixed |
/documents/{id}/versions | sort | JSON array, see below | newest first | fixed |
/v1/operations, /v1/inbox, /v1/dispatch/actions | none | none | newest first | fixed |
An unrecognised sortBy is not an error on either list. It falls back to the
default, so sortBy=rank on signals silently returns the priority order.
priority is always descending and ignores sortOrder.
sort on the two document endpoints is a JSON-encoded array of
{"field": "...", "direction": "asc"|"desc"}. An entry whose field is not a
real column of the listed resource is dropped without an error, and the entries
that survive are appended after the endpoint's own ordering, so they can
only break ties. The document list already ends in a unique key, which means
sort changes nothing there at all.
Invalid page parameters and the errors they return
Signals and beacons parse leniently and never reject a bad page or limit:
| Sent | You get |
|---|---|
limit=0 | 20 |
limit=abc | 20 |
limit=25xyz | 25 |
limit=-5 | 1 |
limit=1000 | 100 |
page=0, page=-3, page=abc | 1 |
The other endpoints validate strictly and return a 400 for page=0, a page size
of 0, a page size above 100, or a non-numeric value. That covers /documents,
/documents/{id}/versions, /v1/operations, /v1/inbox and
/v1/dispatch/actions. On /documents, /documents/{id}/versions,
/v1/inbox and /v1/dispatch/actions the 400 body carries
error: "Bad Request" and no field name, so match on the status code. On
/v1/operations the 400 carries error: "VALIDATION_ERROR" and the message
Invalid operation list query.
Unknown query parameters are ignored everywhere over HTTP. Through MCP the same tools reject an unknown argument outright with an invalid-arguments error.
| Status | error | When |
|---|---|---|
400 | VALIDATION_ERROR | Past the 3000-row ceiling, a malformed filter, an unusable source cursor, an invalid /v1/operations query. |
400 | Bad Request | A page parameter failed strict validation on the document, inbox or actions lists. |
403 | INSUFFICIENT_PERMISSIONS | The key or token is missing the scope in the table above. |
429 | RATE_LIMITED | Over the request budget. Retry-After says how many seconds to wait. |
Capped lists that are not paginated
These operations return a plain array or object with a fixed maximum. They take
no page, and rows past the cap are not reachable.
| Operation | Shape | Cap |
|---|---|---|
GET /v1/watches (list_watches) | Array of your own watches | 200. Active first, then paused and expired, newest first within each. |
GET /v1/scheduled-messages (list_scheduled_messages) | Array of your own scheduled messages | 200. Pending reminders by delivery time first, then recent history. |
GET /v1/events (get_history) | Object with the subject and an events array, newest first | limit 1 to 200, default 50. Needs subjectKind and subjectId. |
GET /v1/identities/unlinked (list_unlinked_identities) | { "identities": [...] }, newest first | limit 1 to 100, default 50. Admin only. |
GET /v1/code-intel/blast-radius (blast_radius) | Dependent files, tests and specs | At most 400 impacted files, then truncated is true and the answer is a prefix. Input takes at most 50 paths and symbols combined, depth 1 to 4, default 2. |
GET /v1/code-intel/data-model (get_data_model) | Tables per repository | Without table: 400 tables, then truncated. With table: 20 matching tables. table is at most 200 characters. |
GET /documents/{id}/delivery | Object with the spec's card and a pullRequests array | pullRequests holds at most 20, most recently updated first. The 21st is unreachable. |
GET /v1/code-intel/search | Object with repositories, droppedRepositories and a results array of file hits | results: limit 1 to 20, default 8. Symbols per hit are capped at 8. |
GET /v1/knowledge-graph/ask | Markdown text by default; compact JSON with mode=agent | limit 1 to 20, default 8. |
GET /prd/{documentId}/search, GET /prd/{documentId}/criteria | Arrays | Unbounded. Whatever the document holds comes back in one response. |
GET /v1/signals/facets, GET /v1/beacons/facets | One counts object | Not a list. Over a 500ms budget it returns "mode": "timeout" with total, facets and arrays all null. |
Continue reading a document or a signal source
Two operations continue through a large body instead of paging: the document Markdown window and the signal source cursor.
GET /documents/{id}/markdown takes offset and limit over the
characters of the serialized document. limit maxes out at 12000 and defaults
to 8000 when you send only an offset. Send neither and you get the whole
document with no pagination object at all. Send either and the response adds
pagination with offset, limit, totalCharacters and nextOffset, where
nextOffset is null on the last window.
GET /v1/signals/{id}/source returns pages of about 40,000 bytes with a
nextCursor you pass back as cursor. The cursor is bound to one signal, one
source and one part; reusing it anywhere else returns a 400 telling you to
restart without it. If the source document changes while you are paging, the
next call returns a 400 rather than a spliced document, and you start over.
Errors
The single JSON error envelope of the nima API, every error code, the 4xx details you can branch on, the 5xx rules, and the edge cases where the API tells you less than it should.
Rate limits
The three throttle tiers, which routes sit on each, how buckets are keyed, and how to back off, including through MCP.