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

# Pagination and Sorting

> Control result size and order with pagination and sorting parameters

The Penbox API provides pagination and sorting capabilities on all list endpoints to help you manage large datasets and control the order of results.

## Pagination

All list endpoints support pagination to manage large result sets efficiently.

### Basic Usage

```javascript theme={null}
// Method 1: Direct parameters
const params = new URLSearchParams({
  'page[number]': '1',
  'page[size]': '50',
  'workspace[slug]': 'acme-corp'
});

const response = await fetch(
  `https://connect.penbox.io/v1/cases?${params}`,
  { headers: { 'Authorization': `Bearer ${token}` } }
);

// Method 2: With JSON filter
const params = new URLSearchParams({
  'filter': JSON.stringify({
    attributes: { status: { $eq: 'pending' } }
  }),
  'page[number]': '1',
  'page[size]': '50'
});

const response = await fetch(
  `https://connect.penbox.io/v1/cases?${params}`,
  { headers: { 'Authorization': `Bearer ${token}` } }
);
```

### Pagination Parameters

| Parameter      | Type    | Default | Max | Description                |
| -------------- | ------- | ------- | --- | -------------------------- |
| `page[number]` | integer | 1       | -   | Page number (starts at 1)  |
| `page[size]`   | integer | 10      | 100 | Number of results per page |

<Note>
  The maximum `page[size]` is **100**. If you need to retrieve more results, use multiple paginated requests.
</Note>

### Response Format

All paginated endpoints return results in the same format:

```json theme={null}
{
  "meta": {
    "total_count": 150,
    "total_pages": 15,
    "page_size": 10,
    "current_page": 1
  },
  "data": [
    { "id": "abc-123", "title": "Case 1", "created_at": "2024-01-15T10:00:00Z" },
    { "id": "def-456", "title": "Case 2", "created_at": "2024-01-14T09:30:00Z" }
  ]
}
```

**Response Fields:**

| Field               | Type    | Description                                   |
| ------------------- | ------- | --------------------------------------------- |
| `meta.total_count`  | integer | Total number of results matching your filters |
| `meta.total_pages`  | integer | Total number of pages available               |
| `meta.page_size`    | integer | Number of results per page                    |
| `meta.current_page` | integer | Current page number                           |
| `data`              | array   | Array of resource objects                     |

### Pagination Examples

#### Iterating Through Pages

```javascript theme={null}
async function getAllCases(workspaceSlug) {
  const allCases = [];
  let currentPage = 1;
  let hasMorePages = true;

  while (hasMorePages) {
    const params = new URLSearchParams({
      'workspace[slug]': workspaceSlug,
      'page[number]': currentPage.toString(),
      'page[size]': '100' // Max page size for efficiency
    });

    const response = await fetch(
      `https://connect.penbox.io/v1/cases?${params}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    );

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

    // Check if there are more pages
    hasMorePages = currentPage < result.meta.total_pages;
    currentPage++;
  }

  return allCases;
}
```

#### Loading a Specific Page

```javascript theme={null}
async function getCasesPage(pageNumber, pageSize = 25) {
  const params = new URLSearchParams({
    'page[number]': pageNumber.toString(),
    'page[size]': pageSize.toString(),
    'archived': 'false'
  });

  const response = await fetch(
    `https://connect.penbox.io/v1/cases?${params}`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );

  return await response.json();
}
```

#### Pagination with Filters

```javascript theme={null}
async function getPendingCasesPaginated(page = 1) {
  const filter = {
    attributes: {
      parent_status: { $eq: 'pending' },
      archived_at: { $eq: null }
    }
  };

  const params = new URLSearchParams({
    'filter': JSON.stringify(filter),
    'page[number]': page.toString(),
    'page[size]': '50'
  });

  const response = await fetch(
    `https://connect.penbox.io/v1/cases?${params}`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );

  return await response.json();
}
```

## Sorting

Control the order of results with the `sort` parameter. Sorting works with both direct parameters and JSON filters.

### Basic Usage

```bash theme={null}
# Sort by creation date (descending - newest first)
GET /v1/cases?sort=-created_at

# Sort by title (ascending - A to Z)
GET /v1/case_templates?sort=title

# Combine with filters
GET /v1/cases?workspace[slug]=acme&sort=-updated_at

# Sort with pagination
GET /v1/forms?sort=-created_at&page[size]=50
```

### Sort Format

| Format             | Description                                     | Example            |
| ------------------ | ----------------------------------------------- | ------------------ |
| `sort=field_name`  | Ascending order (A-Z, oldest-newest, low-high)  | `sort=title`       |
| `sort=-field_name` | Descending order (Z-A, newest-oldest, high-low) | `sort=-created_at` |

<Tip>
  Prefix the field name with a minus sign (`-`) for descending order.
</Tip>

### Common Sort Fields by Endpoint

#### Cases (`/v1/cases`)

* `created_at` - Creation timestamp
* `updated_at` - Last update timestamp
* `title` - Case title
* `status` - Custom status value
* `parent_status` - Parent status
* `waiting_for` - Who the case is waiting for
* `reference` - Custom reference number
* `archived_at` - Archive timestamp

#### Case Templates (`/v1/case_templates`)

* `created_at` - Creation timestamp
* `updated_at` - Last update timestamp
* `title` - Template title
* `reference` - Custom reference
* `archived_at` - Archive timestamp

#### Forms (`/v1/forms`)

* `created_at` - Creation timestamp
* `updated_at` - Last update timestamp
* Various custom fields based on form template

#### Form Templates (`/v1/form_templates`)

* `created_at` - Creation timestamp
* `updated_at` - Last update timestamp

### Sorting Examples

#### Sort by Date

```javascript theme={null}
// Get newest cases first
const params = new URLSearchParams({
  'sort': '-created_at',
  'page[size]': '25'
});

const response = await fetch(
  `https://connect.penbox.io/v1/cases?${params}`,
  { headers: { 'Authorization': `Bearer ${token}` } }
);
```

#### Sort by Title

```javascript theme={null}
// Get case templates alphabetically
const params = new URLSearchParams({
  'sort': 'title',
  'archived': 'false'
});

const response = await fetch(
  `https://connect.penbox.io/v1/case_templates?${params}`,
  { headers: { 'Authorization': `Bearer ${token}` } }
);
```

#### Sort with JSON Filter

```javascript theme={null}
// Get active cases sorted by last update
const filter = {
  attributes: {
    parent_status: { $eq: 'in_progress' },
    archived_at: { $eq: null }
  }
};

const params = new URLSearchParams({
  'filter': JSON.stringify(filter),
  'sort': '-updated_at'
});

const response = await fetch(
  `https://connect.penbox.io/v1/cases?${params}`,
  { headers: { 'Authorization': `Bearer ${token}` } }
);
```

#### Dynamic Sorting

```javascript theme={null}
function getCases(sortField, sortDirection = 'asc') {
  const sortParam = sortDirection === 'desc' 
    ? `-${sortField}` 
    : sortField;

  const params = new URLSearchParams({
    'sort': sortParam,
    'page[size]': '50'
  });

  return fetch(
    `https://connect.penbox.io/v1/cases?${params}`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  ).then(r => r.json());
}

// Usage
const newestFirst = await getCases('created_at', 'desc');
const alphabetical = await getCases('title', 'asc');
```

## Combining Pagination and Sorting

Pagination and sorting work together seamlessly:

```javascript theme={null}
async function getCasesPaginatedAndSorted(options = {}) {
  const {
    page = 1,
    pageSize = 25,
    sortBy = 'created_at',
    sortDirection = 'desc',
    workspaceSlug = null
  } = options;

  const params = new URLSearchParams({
    'page[number]': page.toString(),
    'page[size]': pageSize.toString(),
    'sort': sortDirection === 'desc' ? `-${sortBy}` : sortBy
  });

  if (workspaceSlug) {
    params.set('workspace[slug]', workspaceSlug);
  }

  const response = await fetch(
    `https://connect.penbox.io/v1/cases?${params}`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );

  return await response.json();
}

// Usage examples
const firstPage = await getCasesPaginatedAndSorted({
  page: 1,
  pageSize: 50,
  sortBy: 'title',
  sortDirection: 'asc'
});

const recentUpdates = await getCasesPaginatedAndSorted({
  page: 1,
  sortBy: 'updated_at',
  sortDirection: 'desc',
  workspaceSlug: 'acme-corp'
});
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use appropriate page sizes">
    * Use larger page sizes (50-100) for batch processing
    * Use smaller page sizes (10-25) for UI display
    * Consider network latency and processing time
  </Accordion>

  <Accordion title="Always implement pagination">
    Never assume a dataset is small. Always implement pagination to avoid:

    * API timeouts
    * Memory issues
    * Poor user experience
  </Accordion>

  <Accordion title="Sort by indexed fields">
    Sorting by `created_at` or `updated_at` is typically faster than custom fields. Use these when possible for better performance.
  </Accordion>

  <Accordion title="Cache sorted results">
    If sorting by the same field repeatedly, consider caching the results to reduce API calls.
  </Accordion>

  <Accordion title="Handle empty results">
    Always check if the `data` array is empty:

    ```javascript theme={null}
    const result = await response.json();
    if (result.data.length === 0) {
      console.log('No results found');
    }
    ```
  </Accordion>

  <Accordion title="Use total_count for UI feedback">
    Show users how many total results exist:

    ```javascript theme={null}
    const { meta, data } = await response.json();
    console.log(`Showing ${data.length} of ${meta.total_count} results`);
    ```
  </Accordion>
</AccordionGroup>

## Complete Example: Data Table

Here's a complete example of implementing a paginated, sortable data table:

```javascript theme={null}
class CasesTable {
  constructor(token, workspaceSlug) {
    this.token = token;
    this.workspaceSlug = workspaceSlug;
    this.currentPage = 1;
    this.pageSize = 25;
    this.sortBy = 'created_at';
    this.sortDirection = 'desc';
  }

  async fetchCases() {
    const params = new URLSearchParams({
      'workspace[slug]': this.workspaceSlug,
      'page[number]': this.currentPage.toString(),
      'page[size]': this.pageSize.toString(),
      'sort': this.sortDirection === 'desc' 
        ? `-${this.sortBy}` 
        : this.sortBy
    });

    const response = await fetch(
      `https://connect.penbox.io/v1/cases?${params}`,
      { headers: { 'Authorization': `Bearer ${this.token}` } }
    );

    return await response.json();
  }

  async goToPage(pageNumber) {
    this.currentPage = pageNumber;
    return await this.fetchCases();
  }

  async nextPage() {
    this.currentPage++;
    return await this.fetchCases();
  }

  async previousPage() {
    if (this.currentPage > 1) {
      this.currentPage--;
    }
    return await this.fetchCases();
  }

  async sortBy(field, direction = 'asc') {
    this.sortBy = field;
    this.sortDirection = direction;
    this.currentPage = 1; // Reset to first page when sorting
    return await this.fetchCases();
  }

  async changePageSize(size) {
    this.pageSize = Math.min(size, 100); // Enforce max
    this.currentPage = 1; // Reset to first page
    return await this.fetchCases();
  }
}

// Usage
const table = new CasesTable(accessToken, 'acme-corp');

// Initial load
const firstPage = await table.fetchCases();
console.log(`Showing ${firstPage.data.length} of ${firstPage.meta.total_count} cases`);

// Navigate
await table.nextPage();
await table.goToPage(5);

// Sort
await table.sortBy('title', 'asc');
await table.sortBy('updated_at', 'desc');

// Change page size
await table.changePageSize(50);
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Advanced Filters" icon="filter" href="/api-reference/advanced-filters">
    Learn how to filter results with operators
  </Card>

  <Card title="List Cases" icon="folder-tree" href="/api-reference/cases/list-cases">
    Apply pagination to the Cases endpoint
  </Card>

  <Card title="List Forms" icon="file-lines" href="/api-reference/requests/list-requests">
    Paginate and sort forms
  </Card>

  <Card title="OpenAPI Spec" icon="code" href="/api-reference/openapi.json">
    See all available sort fields
  </Card>
</CardGroup>
