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

# Pagination

> Navigate through large result sets efficiently with cursor-based and page-based pagination.

## Overview

DocuTrust API endpoints that return lists of resources support pagination to keep response sizes manageable. The primary pagination method is **cursor-based**, which provides stable, performant iteration even as new records are created or deleted.

Some endpoints additionally support **page-based** pagination for simpler random-access use cases.

## Cursor-Based Pagination

Cursor-based pagination uses resource IDs as cursors. You pass an `after` or `before` parameter to fetch the next or previous page of results.

### Parameters

| Parameter | Type    | Default | Description                                                                                        |
| --------- | ------- | ------- | -------------------------------------------------------------------------------------------------- |
| `limit`   | integer | `10`    | Number of items to return per page. Minimum `1`, maximum `100`.                                    |
| `after`   | integer | --      | Return items with an ID greater than this value. Use the `next` cursor from the previous response. |
| `before`  | integer | --      | Return items with an ID less than this value. Use the `prev` cursor from the previous response.    |

<Note>
  You may pass `after` or `before`, but not both in the same request. If neither is provided, the first page of results is returned.
</Note>

### Response Format

Every paginated response includes a `data` array and a `pagination` object:

```json theme={null}
{
  "data": [
    {
      "id": 447,
      "slug": "7k8m9n0p1q2r",
      "status": "completed",
      "created_at": "2026-04-08T09:12:00Z",
      "completed_at": "2026-04-08T11:45:00Z",
      "template_id": 15,
      "submitters": [
        {
          "id": 890,
          "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "email": "jane.doe@example.com",
          "name": "Jane Doe",
          "role": "First Party",
          "status": "completed"
        }
      ]
    },
    {
      "id": 448,
      "slug": "8l9m0n1o2p3q",
      "status": "completed",
      "created_at": "2026-04-08T10:30:00Z",
      "completed_at": "2026-04-08T12:15:00Z",
      "template_id": 22,
      "submitters": [
        {
          "id": 891,
          "uuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "email": "bob.jones@acme.com",
          "name": "Bob Jones",
          "role": "Signer",
          "status": "completed"
        }
      ]
    },
    {
      "id": 449,
      "slug": "9m0n1o2p3q4r",
      "status": "pending",
      "created_at": "2026-04-08T11:00:00Z",
      "completed_at": null,
      "template_id": 15,
      "submitters": [
        {
          "id": 892,
          "uuid": "c3d4e5f6-a7b8-9012-cdef-123456789012",
          "email": "alice.wong@globex.com",
          "name": "Alice Wong",
          "role": "First Party",
          "status": "sent"
        }
      ]
    }
  ],
  "pagination": {
    "count": 3,
    "next": 449,
    "prev": 447
  }
}
```

### Pagination Object Fields

| Field   | Type            | Description                                                                                               |
| ------- | --------------- | --------------------------------------------------------------------------------------------------------- |
| `count` | integer         | Number of items returned in the current page (equal to the length of `data`).                             |
| `next`  | integer or null | The cursor to use as the `after` parameter to fetch the next page. `null` when there are no more results. |
| `prev`  | integer or null | The cursor to use as the `before` parameter to fetch the previous page. `null` when on the first page.    |

### Example: Fetching the First Page

```bash theme={null}
curl -X GET "https://your-instance.spitshake.io/api/submissions?limit=10" \
  -H "X-Auth-Token: YOUR_API_TOKEN"
```

### Example: Fetching the Next Page

Use the `next` value from the previous response as the `after` parameter:

```bash theme={null}
curl -X GET "https://your-instance.spitshake.io/api/submissions?limit=10&after=449" \
  -H "X-Auth-Token: YOUR_API_TOKEN"
```

### Example: Fetching the Previous Page

Use the `prev` value from the current response as the `before` parameter:

```bash theme={null}
curl -X GET "https://your-instance.spitshake.io/api/submissions?limit=10&before=447" \
  -H "X-Auth-Token: YOUR_API_TOKEN"
```

## Iterating Through All Results

To retrieve every record, loop until `pagination.next` is `null`.

<CodeGroup>
  ```javascript Node.js theme={null}
  async function fetchAllSubmissions(apiToken) {
    const baseUrl = 'https://your-instance.spitshake.io/api/submissions';
    const allSubmissions = [];
    let afterCursor = null;

    while (true) {
      const params = new URLSearchParams({ limit: '100' });
      if (afterCursor) {
        params.set('after', afterCursor.toString());
      }

      const response = await fetch(`${baseUrl}?${params}`, {
        headers: { 'X-Auth-Token': apiToken },
      });

      if (!response.ok) {
        throw new Error(`API error: ${response.status} ${response.statusText}`);
      }

      const result = await response.json();
      allSubmissions.push(...result.data);

      if (result.pagination.next === null) {
        break;
      }

      afterCursor = result.pagination.next;
    }

    return allSubmissions;
  }
  ```

  ```python Python theme={null}
  import requests

  def fetch_all_submissions(api_token: str) -> list:
      base_url = "https://your-instance.spitshake.io/api/submissions"
      headers = {"X-Auth-Token": api_token}
      all_submissions = []
      after_cursor = None

      while True:
          params = {"limit": 100}
          if after_cursor is not None:
              params["after"] = after_cursor

          response = requests.get(base_url, headers=headers, params=params)
          response.raise_for_status()

          result = response.json()
          all_submissions.extend(result["data"])

          if result["pagination"]["next"] is None:
              break

          after_cursor = result["pagination"]["next"]

      return all_submissions
  ```

  ```ruby Ruby theme={null}
  require 'net/http'
  require 'json'
  require 'uri'

  def fetch_all_submissions(api_token)
    base_url = 'https://your-instance.spitshake.io/api/submissions'
    all_submissions = []
    after_cursor = nil

    loop do
      params = { 'limit' => 100 }
      params['after'] = after_cursor if after_cursor

      uri = URI(base_url)
      uri.query = URI.encode_www_form(params)

      request = Net::HTTP::Get.new(uri)
      request['X-Auth-Token'] = api_token

      response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
        http.request(request)
      end

      raise "API error: #{response.code}" unless response.is_a?(Net::HTTPSuccess)

      result = JSON.parse(response.body)
      all_submissions.concat(result['data'])

      break if result['pagination']['next'].nil?

      after_cursor = result['pagination']['next']
    end

    all_submissions
  end
  ```
</CodeGroup>

## Page-Based Pagination

Some endpoints support traditional page-based pagination as an alternative. This is useful when you need to jump to a specific page or display a page count in a UI.

### Parameters

| Parameter  | Type    | Default | Description                                           |
| ---------- | ------- | ------- | ----------------------------------------------------- |
| `page`     | integer | `1`     | The page number to retrieve (1-indexed).              |
| `per_page` | integer | `10`    | Number of items per page. Minimum `1`, maximum `100`. |

### Response Format

Page-based responses include `total` and `total_pages` in the pagination object:

```json theme={null}
{
  "data": [
    {
      "id": 15,
      "name": "Employment Agreement",
      "slug": "employment-agreement-v2",
      "external_id": "tmpl_emp_2026",
      "folder_name": "HR Documents",
      "source": "web",
      "created_at": "2026-03-01T08:00:00Z",
      "updated_at": "2026-04-09T09:15:00Z"
    },
    {
      "id": 22,
      "name": "Non-Disclosure Agreement",
      "slug": "nda-standard",
      "external_id": "tmpl_nda_2026",
      "folder_name": "Legal",
      "source": "web",
      "created_at": "2026-03-10T14:00:00Z",
      "updated_at": "2026-04-07T16:45:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 10,
    "total": 47,
    "total_pages": 5
  }
}
```

### Page-Based Pagination Object Fields

| Field         | Type    | Description                                 |
| ------------- | ------- | ------------------------------------------- |
| `page`        | integer | The current page number.                    |
| `per_page`    | integer | The number of items per page.               |
| `total`       | integer | The total number of items across all pages. |
| `total_pages` | integer | The total number of pages available.        |

### Example Request

```bash theme={null}
curl -X GET "https://your-instance.spitshake.io/api/templates?page=2&per_page=25" \
  -H "X-Auth-Token: YOUR_API_TOKEN"
```

<Warning>
  Page-based pagination can return inconsistent results if items are created or deleted between requests. Use cursor-based pagination when iterating through large or frequently-changing collections.
</Warning>

## Best Practices

<AccordionGroup>
  <Accordion title="Prefer cursor-based pagination">
    Cursor-based pagination is more performant and stable than page-based pagination. Results remain consistent even when records are created or deleted between page fetches. Use it as your default approach.
  </Accordion>

  <Accordion title="Use the maximum limit for bulk exports">
    Set `limit=100` when iterating through all records to minimize the number of API calls and stay within rate limits.
  </Accordion>

  <Accordion title="Do not construct cursors manually">
    Always use the `next` and `prev` values returned by the API. Cursor values are opaque and their format may change without notice.
  </Accordion>

  <Accordion title="Handle empty pages gracefully">
    When `pagination.next` is `null`, there are no more records. Do not make additional requests.
  </Accordion>

  <Accordion title="Respect rate limits between pages">
    When iterating through very large collections, monitor the `X-RateLimit-Remaining` header and add brief delays if needed. See the [Errors guide](/guides/errors) for rate limit details.
  </Accordion>
</AccordionGroup>
