By default, all index (i.e., GET /resources) calls to our API will be paginated. To paginate the API, you can use the limit
and offset
query parameters.
GET /firms/abc123/leads?limit=20&offset=40
This would return page 3 with 20 records per page (records 41-60).
Default behavior
- Default limit: 20 records
- Maximum limit: 100 records
- Default offset: 0
- Default order:
['createdAt', 'DESC']
(newest first)
Paginated responses include metadata to help with navigation:
{
"data": [
// ... array of resources
],
"meta": {
"total": 150,
"count": 20,
"offset": 40,
"limit": 20
}
}
Query parameters
Ordering
You can customize the sort order using the order
parameter:
GET /firms/abc123/leads?order=[["email", "ASC"]]
GET /firms/abc123/leads?order=[["createdAt", "DESC"], ["email", "ASC"]]
Supported directions:
ASC
- Ascending order
DESC
- Descending order
Filtering
Use the filter
parameter to restrict results:
GET /firms/abc123/leads?filter={"email": "john.doe@example.com"}
GET /firms/abc123/leads?filter={"firstName": "John", "lastName": "Doe"}
Depending on your HTTP client, you may need to URL encode the JSON filter object.
Scopes
The Perch API uses scopes to simplify common or complicated queries:
GET /leads/abc123?scope=withDeleted
GET /firms/abc123/leads?scope=withDeleted&include=Plans
Common scopes:
withDeleted
- Include soft-deleted records
active
- Only active (non-deleted) records
Not all scopes are available for each resource. Check the specific endpoint documentation for supported scopes.
Use the include
parameter to join related resources:
GET /firms/abc123/leads?include=Plans
GET /leads/abc123?include=Plans.ClientProfiles
This reduces the number of API calls needed to get complete data.
Soft deletion
Many resources in our system support soft-deletion, which we call “paranoid” resources. This means that records will not be removed from the database, but instead get a timestamp value set to the record’s deletedAt
column.
Working with soft-deleted records
By default, deleted records are not returned:
GET /firms/abc123/leads
// Returns only active leads
To include deleted records, use the withDeleted
scope:
GET /firms/abc123/leads?scope=withDeleted
// Returns both active and deleted leads
Lead conversion and soft deletion
When a lead converts to a client profile:
- The
convertedAt
timestamp is set
- The
deletedAt
timestamp is set (automatically archived)
- Any associated plans are created and linked
To track converted leads, always use the withDeleted
scope:
GET /leads/abc123?scope=withDeleted
Best practices
- Use appropriate page sizes - Start with the default (20) and adjust based on your needs
- Implement client-side caching - Cache results to reduce API calls
- Use filters efficiently - Filter on the server side rather than fetching all records
- Include related data when needed - Use
include
to reduce round trips
- Handle large datasets - Use pagination rather than trying to fetch all records at once
- Combine parameters effectively - Use filtering, ordering, and pagination together
While Perch uses offset-based pagination, you can simulate cursor-based pagination using ordering and filtering:
// Get first page
let lastCreatedAt = null;
const firstPage = await fetch('/leads?order=[["createdAt","DESC"]]&limit=20');
// Get next page using the last item's timestamp
if (firstPage.data.length > 0) {
lastCreatedAt = firstPage.data[firstPage.data.length - 1].createdAt;
const nextPage = await fetch(
`/leads?order=[["createdAt","DESC"]]&limit=20&filter={"createdAt":{"$lt":"${lastCreatedAt}"}}`
);
}
Efficient lead fetching example
// Fetch recent converted leads with their plans
const response = await fetch(
`/firms/${firmId}/leads?` + new URLSearchParams({
scope: 'withDeleted',
include: 'Plans',
filter: JSON.stringify({ convertedAt: { $ne: null } }),
order: JSON.stringify([['convertedAt', 'DESC']]),
limit: 50
}), {
headers: { 'X-API-Key': apiKey }
}
);
const { data: leads, meta } = await response.json();
console.log(`Found ${meta.total} converted leads`);
Working with large datasets
async function fetchAllLeads(firmId) {
let allLeads = [];
let offset = 0;
const limit = 100; // Use maximum page size for efficiency
while (true) {
const response = await fetch(
`/firms/${firmId}/leads?limit=${limit}&offset=${offset}&scope=withDeleted`
);
const { data: leads, meta } = await response.json();
allLeads = allLeads.concat(leads);
// Check if we've fetched all records
if (leads.length < limit || offset + limit >= meta.total) {
break;
}
offset += limit;
// Add delay to respect rate limits
await new Promise(resolve => setTimeout(resolve, 100));
}
return allLeads;
}