Skip to main content
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

// 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

ParameterTypeDefaultMaxDescription
page[number]integer1-Page number (starts at 1)
page[size]integer10100Number of results per page
The maximum page[size] is 100. If you need to retrieve more results, use multiple paginated requests.

Response Format

All paginated endpoints return results in the same format:
{
  "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:
FieldTypeDescription
meta.total_countintegerTotal number of results matching your filters
meta.total_pagesintegerTotal number of pages available
meta.page_sizeintegerNumber of results per page
meta.current_pageintegerCurrent page number
dataarrayArray of resource objects

Pagination Examples

Iterating Through Pages

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

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

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

# 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

FormatDescriptionExample
sort=field_nameAscending order (A-Z, oldest-newest, low-high)sort=title
sort=-field_nameDescending order (Z-A, newest-oldest, high-low)sort=-created_at
Prefix the field name with a minus sign (-) for descending order.

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

// 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

// 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

// 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

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:
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

  • Use larger page sizes (50-100) for batch processing
  • Use smaller page sizes (10-25) for UI display
  • Consider network latency and processing time
Never assume a dataset is small. Always implement pagination to avoid:
  • API timeouts
  • Memory issues
  • Poor user experience
Sorting by created_at or updated_at is typically faster than custom fields. Use these when possible for better performance.
If sorting by the same field repeatedly, consider caching the results to reduce API calls.
Always check if the data array is empty:
const result = await response.json();
if (result.data.length === 0) {
  console.log('No results found');
}
Show users how many total results exist:
const { meta, data } = await response.json();
console.log(`Showing ${data.length} of ${meta.total_count} results`);

Complete Example: Data Table

Here’s a complete example of implementing a paginated, sortable data table:
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