The Penbox API provides pagination and sorting capabilities on all list endpoints to help you manage large datasets and control the order of results.
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 } ` } }
);
Parameter Type Default Max Description page[number]integer 1 - Page number (starts at 1) page[size]integer 10 100 Number of results per page
The maximum page[size] is 100 . If you need to retrieve more results, use multiple paginated requests.
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:
Field Type Description meta.total_countinteger Total number of results matching your filters meta.total_pagesinteger Total number of pages available meta.page_sizeinteger Number of results per page meta.current_pageinteger Current page number dataarray Array of resource objects
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 ();
}
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
Format Description Example sort=field_nameAscending order (A-Z, oldest-newest, low-high) sort=titlesort=-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
created_at - Creation timestamp
updated_at - Last update timestamp
Various custom fields based on form template
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 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
Always implement pagination
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' );
}
Use total_count for UI feedback
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