> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signa.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Cursor-based pagination for navigating large result sets

Signa uses **cursor-based pagination** for all list and search endpoints. Cursors provide stable, consistent iteration over datasets that may change between requests: no skipped records, no duplicates, no performance degradation at depth.

<Note>
  **Fixed-size bulk endpoints don't paginate.** Endpoints whose result size
  is bounded by the request itself never cursor, but their envelopes differ:
  bulk create ([`POST /v1/watches/bulk`](/api-reference/monitoring/watches/bulk))
  and bulk lookup ([`POST /v1/alerts/lookup`](/api-reference/monitoring/alerts/lookup))
  return an `object: "list"` envelope with `data` only (no `has_more`, no
  `pagination`), while batch get
  ([`POST /v1/trademarks/batch`](/api-reference/trademarks/batch-trademarks))
  keeps the full list envelope with the fields pinned: `has_more` is always
  `false` and `pagination.cursor` is always `null`.
</Note>

## Basic Usage

Your first request specifies a `limit` (items per page). The response includes a `cursor` and `has_more` flag:

```bash theme={null}
curl -H "Authorization: Bearer sig_YOUR_KEY" \
  "https://api.signa.so/v1/trademarks?limit=50"
```

**Response:**

```json theme={null}
{
  "object": "list",
  "data": [
    { "id": "tm_7d4e1f2a", "object": "trademark", "mark_text": "SIGNA", ... },
    { "id": "tm_3b8c9d0e", "object": "trademark", "mark_text": "AURORA", ... }
  ],
  "has_more": true,
  "pagination": {
    "cursor": "eyJpZCI6IjNiOGM5ZDAw..."
  },
  "request_id": "req_abc123"
}
```

To fetch the next page, pass the `cursor` from the previous response:

```bash theme={null}
curl -H "Authorization: Bearer sig_YOUR_KEY" \
  "https://api.signa.so/v1/trademarks?limit=50&cursor=eyJpZCI6IjNiOGM5ZDAw..."
```

When `has_more` is `false`, you have reached the end of the result set. The `cursor` field will be `null`.

***

## Parameters

| Parameter       | Type    | Default | Description                                                                |
| --------------- | ------- | ------- | -------------------------------------------------------------------------- |
| `limit`         | integer | 20      | Items per page. Min 1, max 100.                                            |
| `cursor`        | string  | (none)  | Opaque cursor from a previous response.                                    |
| `sort`          | string  | varies  | Sort field with optional `-` prefix for descending (e.g., `-filing_date`). |
| `include_total` | boolean | `false` | Include `total_count` in the pagination object.                            |

***

## Response Shape

All list endpoints return the same structure.

**Default response (without `include_total`):**

```json theme={null}
{
  "object": "list",
  "data": [ ... ],
  "has_more": true,
  "pagination": {
    "cursor": "eyJpZCI6..."
  },
  "request_id": "req_abc123"
}
```

**With `?include_total=true`:**

```json theme={null}
{
  "object": "list",
  "data": [ ... ],
  "has_more": true,
  "pagination": {
    "cursor": "eyJpZCI6...",
    "total_count": 12847,
    "total_count_approximate": false
  },
  "request_id": "req_abc123"
}
```

Key details:

* `has_more` is always at the **top level** (not inside `pagination`).
* `pagination.cursor` is `null` when `has_more` is `false`.
* `pagination.total_count` is **only present** when `include_total=true` is passed as a query parameter. It is omitted by default.
* `pagination.total_count_approximate` is emitted alongside `total_count` whenever the total is present. It is `false` when the count is exact and `true` when the count was capped (for example, the search index caps total hits at 10,000 for deep searches). Treat any exact count as a hard number and any approximate count as "at least this many".

***

## Cursor Stability

Cursors encode a position in the result set, not an offset. This means:

**Records inserted after your cursor was created** will not appear in your current pagination session. You will not see duplicates or gaps.

**Records deleted after your cursor was created** are silently skipped. Your page may contain fewer items than the requested `limit` in rare cases.

**Concurrent updates** do not affect cursor validity. Even if the underlying data changes, your cursor continues from where it left off.

<Note>
  Cursors are scoped to a specific query, sort order, and filter combination. Changing any of these parameters invalidates the cursor: start a fresh pagination session instead.
</Note>

***

## Cursor Expiration

Cursors expire after **24 hours**. If you pass an expired cursor, you receive a `cursor_expired` error:

```json theme={null}
{
  "error": {
    "type": "cursor_expired",
    "title": "Cursor expired",
    "status": 400,
    "detail": "The pagination cursor has expired. Start a new query.",
    "retryable": false,
    "retry_after": null
  },
  "request_id": "req_abc123"
}
```

For long-running syncs, checkpoint the cursor and resume within the 24-hour window. If the cursor expires, restart from the beginning.

<Warning>
  Cursors are opaque strings. Do not parse, decode, or construct them. Their internal format may change without notice.
</Warning>

***

## Sorting

You can sort results using the `sort` parameter. The cursor encodes the sort position, so **you must use the same `sort` value for every page in a session**.

Supported sort fields vary by endpoint:

| Endpoint                        | Supported Sort Fields                                                                                                                                           | Default                                                     |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `GET` and `POST /v1/trademarks` | `filing_date`, `registration_date`, `expiry_date`, `renewal_due_date`, `updated_at`, `publication_date`, `termination_date`, `office_code`, `jurisdiction_code` | Relevance when `q` is present; unordered (fast) when no `q` |
| `GET /v1/owners`                | `name`, `updated_at`                                                                                                                                            | `name`                                                      |

Sort direction is specified with a `-` prefix for descending (no prefix = ascending):

```bash theme={null}
GET /v1/trademarks?sort=-filing_date&limit=50
GET /v1/trademarks?sort=filing_date&limit=50    # ascending
```

<Note>
  When a text query (`q`) is provided, the default sort is by relevance. When only filters are used (no `q`), results are returned in index order (fast, unordered). Providing an explicit `sort` overrides the default in both cases.
</Note>

***

## Total Count

By default, the `total_count` is not included in the response. To include it on any list endpoint, pass `include_total=true`:

```bash theme={null}
GET /v1/trademarks?q=nike&limit=50&include_total=true
GET /v1/owners?q=nike&limit=50&include_total=true
GET /v1/attorneys?q=smith&limit=50&include_total=true
GET /v1/firms?q=smith&limit=50&include_total=true
GET /v1/jurisdictions?include_total=true
```

```json theme={null}
{
  "object": "list",
  "data": [ ... ],
  "has_more": true,
  "pagination": {
    "cursor": "eyJpZCI6...",
    "total_count": 12847,
    "total_count_approximate": false
  }
}
```

The shape is the same for every list endpoint:

* `pagination.total_count`: the count itself (integer, only present when `include_total=true`).
* `pagination.total_count_approximate`: `false` when the count is exact; `true` when the underlying engine capped the count (the search index caps at 10,000 for deep search queries).

<Warning>
  Computing the total count requires a separate database or search-index query and can be expensive on large datasets. Only request it when you need to display a progress indicator or total count in your UI. Avoid including it on every page request.
</Warning>

***

## SDK Support

The TypeScript SDK wraps pagination with async iterators, `toArray()`, and manual page control. See the [TypeScript SDK](/sdk/typescript) reference for usage patterns.
