Skip to main content

Cursor synchronization API

Once you have enabled the Cursor synchronization API in your account admin section, you can now use this information on this page to connect to the service.

Overview

Cursor APIs are designed to make data synchronization between systems efficient and reliable. Instead of retrieving all records every time you sync, a cursor API allows you to fetch only the new or updated data since your last request. This is achieved by using a “cursor”—a unique marker or token that represents a specific point in the data stream.

The Initial Cursor

When you first connect to the Cursor API, you typically start with an initial cursor. This cursor will be provided by the API, by default the API assumes you want to start at the beginning, syncing from the very start of the dataset. The API will then return the first batch of data along with a new cursor value. If you wish to start from a specific point you must request this along side your initial cursor request.

Fetching Data with the Cursor

Each time you make a request to the API, you include the current cursor value. The API responds with the next set of records and an updated cursor. This process continues, allowing you to “page” through the data efficiently. If there are no new records, the API will return an empty result set but still provide a cursor for your next check.

Keeping Track of Cursors

It is important to store the latest cursor value after each successful sync. This ensures that, if your integration is interrupted or restarted, you can resume syncing from exactly where you left off—without missing any data or processing the same records twice. Cursors are typically stored in your application’s database or persistent storage.

Example Workflow

  • Authenticate
  • Request the initial cursor (with or without a starting point).
  • Fetch data using the cursor. Note the request will provide data so this can be skipped.
  • Process the returned records in your system.
  • Store the new cursor value provided by the API.
  • Repeat the process on a schedule, always using the most recent cursor.

Integration

Authentication

Use the credentials provided to request a Bearer token from Focus. This request will provide you with both a token and a refresh token. The refresh token can then be used to request a refreshed token before the session ends.

Request example:

  • Request type: POST
  • Content-Type: application/json
  • Request URL: BASE_URL/authWithAPIKey
  • Body:
{ 
"apiKey": "MY-API-KEY-XSDSADSADSADAS",
"username": "MyUserName",
"clientId": 9999 // My Client Id
}

Request response:

  • Status: 200 OK
  • Content-Type: application/json
  • Body:
{
"token": "MY.TOKEN",
"refreshToken": "MY.REFRESH.TOKEN"
}

Get initial cursor

As per the overview above, the initial cursor request is only expected once. This request will set the synchronization point and allow you to sync data going forwards guaranteeing no data is missed.

Request example:

  • Request type: GET
  • Request URL: BASE_URL/client/recording/cursor
  • Params:
    • numberOfResults: 10 - If not supplied the default number of results is 100.
    • fromDate:2025-06-06T14:00:00Z - The UTC date and time to start your sync from.
  • Body: Empty
tip

If you do have an issue with your system or need to start again, you can use the fromDate value to only start back from the point required. If this is not supplied you will start back from the first communication stored in Focus.

Request response:

  • Status: 200 OK
  • Content-Type: application/json
  • Body:
{
"recordings": [
{
"interactionId": 110,
"startDatetimeUTC": "2025-05-01T09:01:10.956Z",
"endDatetimeUTC": "2025-05-01T09:09:13.902Z",
"durationInSeconds": 482.946,
"type": "teams",
"commentsCount": 0,
"transcriptSnippet": "The call focused on...",
"thirdPartyId": "6414aa9f-ZZZAAABBB|1682400c-ZZZAAABBB|1746ZZZAAABBB",
"endpoints": [
{
"person": {
"id": 10,
"fullName": "Jane Smith"
},
"endpoint": {
"id": 11,
"type": "teamsUserId",
"endpoint": "6414aa9f-ZZZAAABBB"
},
"user": null,
"isOriginator": true,
"isRecordingSubject": true
},
{
"endpoint": {
"id": 12,
"type": "teamsUserId",
"endpoint": "89fb9d01-ZZZAAABBB"
},
"isRecordingSubject": false,
"isOriginator": false
},
{
"endpoint": {
"id": 10,
"type": "teamsUserId",
"endpoint": "e3a9fb55-fZZZAAABBB"
},
"isRecordingSubject": false,
"isOriginator": false
}
],
"analysis": [
"Project Management"
],
"retentionGroupId": null,
"retainUntil": "2035-04-29T09:09:13.902Z",
"legalHolds": null,
"tags": [
"conversationPhases.categoryNames.Discussion",
"conversationPhases.categoryNames.Opening",
"conversationPhases.categoryNames.Closing",
"callCategory.categoryNames.Project_Management"
],
"isPrivate": false,
"isPrivateReason": null,
"mediaAccessStatus": "Permitted",
"dataGroups": null,
"optional": {}
},
{"NEXT CALL"}
],
"nextCursorId": "MY.NEXT.CURSOR"
}

info

The response has been simplified slightly and any real IDs have been anonymized. However the data and structure are still accurate. A fully data dictionary can be found within the appendix section of this help documents.

Once processed make sure your application keeps the nextCursorId value for use in the following API calls and to avoid downloading duplicate data.

Get next cursor

This request is very similar to the get initial cursor request but passes in the nextCursorId provided at the end of your first / initial request.

Request example:

  • Request type: GET
  • Request URL: BASE_URL/client/recording/cursor
  • Params:
    • numberOfResults: 10 - If not supplied the default number of results is 100
    • cursorId: MY.NEXT.CURSOR - Your cursor id from above
  • Body: Empty
tip

If you do have an issue with your system or need to start again, you can use the fromDate value to only start back from the point required. If this is not supplied you will start back from the first communication stored in Focus.

Request response:

  • Status: 200 OK
  • Content-Type: application/json
  • Body:
{
"recordings": [
{
"interactionId": 111,
"More data":"As above example"
},
{"NEXT CALL"}
],
"nextCursorId": "MY.NEXT.CURSOR"
}

Next

Awesome, well done, now just keep requesting the next cursor (remember to use the new nextCursorId each time) until you get an empty results set back. You will then be up to date with all communications from the focus platform.

Example empty response:

{
"recordings": [],
"nextCursorId": "MY.NEXT.CURSOR"
}

warning

Even though this result was empty, you should still use the resulting nextCursorId in your next check.