How Pagination, Filtering, and Sorting Work

Edited

Once you've made your first API call, the next thing you'll need is to page through results, filter by specific criteria, and sort the data. This article explains how these work across all Apideck Unified APIs.


In this article

  • How pagination works

  • Paginating through all results

  • Filtering results

  • Using updated_since for incremental syncs

  • Sorting

  • The fields parameter

  • Rate limits


How pagination works

All Apideck list endpoints use cursor-based pagination. Every response that contains multiple records includes a meta object with cursor information you use to request the next page:

{
  "status_code": 200,
  "status": "OK",
  "data": [ ... ],
  "meta": {
    "items_on_page": 50,
    "cursors": {
      "previous": null,
      "current": "em9oby1jcm06OnBhZ2U6OjE=",
      "next": "em9oby1jcm06OnBhZ2U6OjI="
    }
  },
  "links": {
    "previous": null,
    "current": "https://unify.apideck.com/crm/contacts?cursor=em9oby1jcm06OnBhZ2U6OjE%3D",
    "next": "https://unify.apideck.com/crm/contacts?cursor=em9oby1jcm06OnBhZ2U6OjI%3D"
  }
}

To get the next page, pass the next cursor as a query parameter:

GET https://unify.apideck.com/crm/contacts?cursor=em9oby1jcm06OnBhZ2U6OjI%3D

When there are no more results, meta.cursors.next will be null.

Setting the page size

Use the limit parameter to control how many records are returned per page:

GET https://unify.apideck.com/crm/contacts?limit=50

If you request more records than the downstream provider supports per page, Apideck may make multiple requests to the provider behind the scenes and combine the results, so you can request up to 200 items even if the provider limits responses to 100.

Why cursor-based (not page numbers)?

Apideck uses cursor-based pagination because it's more reliable when data changes between requests. With page-number pagination, if a record is added or deleted while you're paging, you might see duplicates or skip records. Cursors track a specific position in the dataset and avoid this problem.

The cursors are opaque, base64-encoded strings, you don't need to decode or construct them. Just pass them as-is.

Paginating through all results

Here's a common pattern using the Apideck SDK:

const { Apideck } = require('@apideck/unify');

const apideck = new Apideck({
  apiKey: process.env.APIDECK_API_KEY,
  appId: 'your-app-id',
  consumerId: 'your-consumer-id'
});

async function fetchAllContacts() {
  const allContacts = [];
  
  const result = await apideck.crm.contacts.list({
    serviceId: 'salesforce',
    limit: 100
  });
  
  for await (const page of result) {
    allContacts.push(...page.data);
  }
  
  return allContacts;
}

Without the SDK, the logic is straightforward: make the initial request, check if meta.cursors.next is not null, and if so, make another request with that cursor. Repeat until next is null.

Filtering results

Most list endpoints support filters through query parameters. The available filters vary by endpoint. Common examples:

# Filter contacts updated after a specific date
GET /crm/contacts?filter[updated_since]=2025-01-01T00:00:00Z

# Filter invoices by status
GET /accounting/invoices?filter[status]=authorised

# Filter employees by company
GET /hris/employees?filter[company_id]=12345

Check the API reference for each endpoint to see which filters are available. Not all filters are supported by all connectors, so have a look at the Coverage overview for connector-specific details.

Using updated_since for incremental syncs

If you're syncing data from a connected platform into your own system, the updated_since filter lets you fetch only records that have been created or modified after a given timestamp:

GET /accounting/invoices?filter[updated_since]=2025-06-01T12:00:00Z

A typical sync flow:

  1. On your first sync, fetch all records (no updated_since filter).

  2. Store the timestamp of when you started the sync.

  3. On subsequent syncs, pass that stored timestamp as updated_since.

  4. Update your stored timestamp after each successful sync.

This dramatically reduces API usage and keeps your data fresh. For details on how API usage is counted, see Unify API Usage Counting Explained.

Sorting

Some list endpoints support sorting:

GET /crm/contacts?sort[by]=updated_at&sort[direction]=desc

Available sort fields depend on the endpoint and the downstream connector. The API reference for each endpoint lists supported sort options.

The fields parameter

If you only need specific fields, use the fields parameter for a sparse response:

GET /crm/contacts?fields=id,first_name,last_name,email

This can improve performance by reducing payload size. For understanding why some fields may come back as null even without using fields, see Understanding Connector Coverage and Limitations.

Rate limits

Apideck doesn't impose its own rate limits on your API calls, but your requests are bound by the downstream providers' rate limits. For a summary of how this works, see Is there a rate limit for Unify? in the Account Management section.

Apideck returns standardized rate limit headers in every response to help you manage this:

  • x-downstream-ratelimit-limit - Maximum requests allowed in the window

  • x-downstream-ratelimit-remaining - Requests remaining in the current window

  • x-downstream-ratelimit-reset - When the window resets (if available from the provider)

These headers are normalized across all connectors, so you can write one set of rate-limiting logic regardless of which provider you're calling. If you hit a rate limit (HTTP 429), implement exponential backoff in your retry logic.

For full details on the header format and handling, see the Unified Rate Limits guide.


Related resources:

Was this article helpful?

Sorry about that! Care to tell us more?

Thanks for the feedback!

There was an issue submitting your feedback
Please check your connection and try again.