Pagination and Incremental Synchronization

Many of the list endpoints in Ashby's API (for example, candidate.list and job.list) implement pagination and incremental synchronization. This guide describes how to:

  1. Implement the client-side logic needed to iterate through multiple pages of results, and
  2. Only fetch resources that have been updated since the previous API request

Full sync

Paginated APIs default to a "full" sync, returning data for all the resources of the requested object type. Full syncs can be slow. Ideally, clients should only need to perform one initial full sync, and then switch to more-efficient incremental syncs (see below).

To begin a full sync, POST an empty JSON object to the target endpoint. If the request succeeds, it will return an object like:

{
  "success": true,
  "results": [
    // An array of the requested object type
  ],
  "moreDataAvailable": true,
  "nextCursor": "some-token-value"
}

The length of the results array will be automatically capped by the API. If you'd like to specify the number of results returned per page, you can do so by specifying a limit in your API request:

{
  "limit": 100
}

If the moreDataAvailable field in the response is true, then there are additional pages of objects to fetch. To fetch the next page, re-send a request to the endpoint, this time passing the value of nextCursor from the latest response as a parameter:

{
  "limit": 100,
  "cursor": "some-token-value"
}

Repeat this cycle on the client side, replacing cursor with the latest nextCursor on each request, until you receive a response with moreDataAvailable set to false. This signals that you've fetched all available records of the targeted type, completing the full sync. In this case, the response will contain a new syncToken field:

{
  "success": true,
  "results": [
    // Array of requested object type
  ],
  "moreDataAvailable": false,
  "syncToken": "sync-token-value"
}

You should store this value of syncToken for use in your next sync.

Example

To list all the candidates in my organization, I would begin with the request:

curl -XPOST https://api.ashbyhq.com/candidate.list -u "${API_KEY}:" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{ "limit": 25 }'

Returning:

{
  "success": true,
  "results": [
    // 25 objects
  ],
  "moreDataAvailable": true,
  "nextCursor": "Rl"
}

I'd then send a follow-up request:

curl -XPOST https://api.ashbyhq.com/candidate.list -u "${API_KEY}:" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{ "limit": 25, "cursor": "Rl" }'

Returning:

{
  "success": true,
  "results": [
    // <= 25 objects
  ],
  "moreDataAvailable": false,
  "syncToken": "mqXvvQBWO"
}

I would then persist the syncToken value of "mqXvvQBWO" for later use.

Incremental sync

Incremental sync allows you to fetch only the resources that have been modified since your last sync request. To do this, include the syncToken returned at the end of your last sync flow in your initial API request:

{
  "syncToken": "sync-token-value"
}

The API will return a response like:

{
  "success": true,
  "results": [
    // An array of the requested object type
  ],
  "moreDataAvailable": true,
  "nextCursor": "some-token-value",
  "syncToken": "sync-token-value"
}

Note here that as long as moreDataAvailable is true, the syncToken returned by the API will be the same as the syncToken passed in the request.

To fetch the next page of results, pass the latest values of nextCursor and syncToken in your request:

{
  "cursor": "some-token-value",
  "syncToken": "sync-token-value"
}

Repeat this flow, passing a new cursor and the same syncToken in each request, until you receive a response with moreDataAvailable set to false. This response will contain a new syncToken value for you to persist for the next set of requests:

{
  "success": true,
  "results": [
    // Array of requested object type
  ],
  "moreDataAvailable": false,
  "syncToken": "new-sync-token-value"
}

Example

To list all the candidates in my organization that have been updated since my last sync, I would take the syncToken value from that sync and pass it in a request:

curl -XPOST https://api.ashbyhq.com/candidate.list -u "${API_KEY}:" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{ "limit": 25, "syncToken": "mqXvvQBWO" }'

Returning:

{
  "success": true,
  "results": [
    // 25 objects
  ],
  "moreDataAvailable": true,
  "nextCursor": "Rl",
  "syncToken": "mqXvvQBWO",
}

I'd then send a follow-up request:

curl -XPOST https://api.ashbyhq.com/candidate.list -u "${API_KEY}:"
-H "Accept: application/json" -H "Content-Type: application/json"
-d '{ "limit": 25, "syncToken": "mqXvvQBWO", "cursor": "Rl" }'

Returning:

{
  "success": true,
  "results": [
    // <= 25 objects
  ],
  "moreDataAvailable": false,
  "syncToken": "PjJ33rQZl"
}

I would then persist the new syncToken value of "PjJ33rQZl", replacing the previous value of "mqXvvQBWO", for later use.

Sync token expiration

Sync tokens returned by the API can be invalidated for various reasons, including token expiration. When the API is passed an invalid token, it will return a response like:

{
  "success": false,
  "errors": ["sync_token_expired"]
}

If your client receives this response, it should begin a new full sync to re-fetch all data using valid tokens.

To minimize the risk of sync token expiration, we recommend performing an incremental sync at least once per week.