Skip to main content

Example: local synchronisation with the Cursor API

This page describes a high-level pattern for maintaining a complete, up-to-date copy of your Focus communications in your own store — a data warehouse, an eDiscovery index, or a surveillance platform.

It uses the Cursor synchronisation API, which is designed for exactly this job: instead of re-downloading everything each time, you fetch only the records that are new or changed since your last run. See the Cursor synchronisation reference for the exact request and response schemas.

When to use this pattern

Use a local synchronisation when you want your own copy of all communications — for reporting, long-term archive, or feeding another system. If instead you only need Focus data shown in context inside another application, the lighter third-party integration example is a better fit.

How a cursor sync works

A cursor is a marker that represents your position in the stream of communications. Each response hands you a nextCursorId; you store it, and pass it back on your next request to continue from exactly where you left off. This is what lets the sync be both efficient (you never re-fetch data you already have) and reliable (an interrupted run resumes cleanly).

The flow is a simple loop:

  1. Authenticate with POST /authWithApiKey to get a bearer token.
  2. Get a starting cursor. On the very first run, request an initial cursor — optionally with a fromDate if you want to start from a specific point rather than the beginning of your data.
  3. Fetch a batch of recordings using the current cursor.
  4. Process and store the returned records in your own database.
  5. Store the new nextCursorId. This is the most important step — it is how the next run knows where to resume.
  6. Repeat until the API returns an empty result set, at which point you are fully up to date.

Suggested architecture

The simplest robust design is a scheduled worker — a small job that runs on a timer rather than a long-running service.

Scheduler (every 4 hours)


Sync worker ──► authenticate ──► read stored cursor
│ │
│ ┌──────────────────┘
▼ ▼
fetch batch ──► store records ──► store new cursor ──► repeat until empty

A practical setup:

  • Create a job that runs on a schedule — for example, every 4 hours. Each run picks up everything that has happened since the previous run. Choose a cadence that matches how fresh your copy needs to be; every few hours is a reasonable default for most compliance and reporting use cases.
  • Persist the cursor between runs. Keep the latest nextCursorId in your own database or a small state file. On each run, read it back and continue from there. Store the new cursor only after a batch has been successfully processed, so a crash mid-run is safe to retry.
  • Loop within a run until the results are empty. A single scheduled run should keep requesting the next batch until it gets an empty result set — that way each run drains the full backlog, however much has accumulated.
  • Always keep the returned cursor, even when the batch is empty. An empty response still returns a cursor; use it next time.

Downloading media

Metadata and media are synced independently. Once you have the metadata for a communication, you can pull its audio on demand with POST /client/recording.playBackByThirdPartyId, keyed on the thirdPartyId returned in each cursor record.

A common approach is to sync all metadata first, then run a separate, lower-priority job that works through any records whose audio you have not yet downloaded. Keeping media downloads out of the main cursor loop keeps the metadata sync fast and simple.

Practical guidance

  • The cursor is your source of truth for progress, not timestamps. Resist the temptation to re-derive position from dates — always resume from the stored nextCursorId.
  • Make each run idempotent. Because you resume from a stored cursor, re-running after a failure should never duplicate or skip records.
  • Recover with fromDate if needed. If your state is lost or you need to rebuild, request a fresh initial cursor with a fromDate to re-sync from a known point rather than the very beginning.
  • Mind rate limits and batch size. Request a sensible numberOfResults per call and let the loop page through, rather than asking for very large batches.
Docs AssistantAsk anything about our products