Skip to main content

Overview

The sync status API lets you observe the entire lifecycle of every sync that runs for a user — across pulls, webhooks, SDK uploads, Apple Health XML imports, and historical Garmin backfills — in real time. Three endpoints are exposed:
  • GET /api/v1/users/{user_id}/sync/stream — long-lived Server-Sent Events connection that streams every event as it happens.
  • GET /api/v1/users/{user_id}/sync/recent — the last N events buffered in Redis (default 50, max 200, retained for 24 hours).
  • GET /api/v1/users/{user_id}/sync/runs — the latest event per run (one row per run_id), so you can quickly inspect which syncs are in progress and how recent ones ended.
All three accept the same ApiKeyDep authentication as the rest of the public API: pass your API key via the X-API-Key header.
HTTP status codes (all three endpoints) Error response shape
The SSE endpoint also supports webhooks — if you have outgoing webhook endpoints registered, terminal sync events (sync.completed, sync.failed, sync.cancelled) are also dispatched as Svix webhooks. See the Webhooks guide.

Event Schema

Every event is a JSON object emitted as the data field of an SSE message with event: sync.status:

Fields


Streaming endpoint

Path parameters Query parameters The replay parameter causes the most recent events from the last 24 hours to be replayed before the live stream begins, so a freshly connected client can see in-progress syncs immediately. The stream emits:
  • A connect comment (: connected) when the connection is established.
  • One SSE message per event (event: sync.status, data: <json>).
  • A : heartbeat comment every 15 seconds to keep the connection alive.
Browsers can consume the stream using EventSource (without auth headers) or fetch + ReadableStream (with a Bearer JWT token in the Authorization header). Both authentication methods are accepted by the same endpoint — no separate dashboard variant is needed. Example SSE stream output
Error responses

Example: Node.js


Recent events

Path parameters Query parameters Events are retained in Redis for 24 hours. Useful when reconnecting or rendering a “history” tab to seed the UI before opening the SSE stream. Success response — array of SyncStatusEvent objects, newest first.

Sync run summaries

Path parameters Query parameters Returns the latest event for each unique run_id from the past 24 hours (one row per run), ordered newest first. Each run_id is stable for the entire lifetime of a single sync invocation — multiple progress events share the same run_id and only the most recent one is surfaced here. For Garmin backfill, the run_id is unique per execution and incorporates an execution-scoped trace identifier. Success response — array of run summary objects:

Rate limiting

The sync status endpoints share the global API rate limit. The SSE stream endpoint (/sync/stream) holds a single long-lived connection per client — there is no per-user connection cap, but you should maintain at most one open stream per user and reuse it rather than opening a new one on each page load. For polling-based fallbacks using /sync/recent or /sync/runs, a reasonable poll interval is 5–15 seconds. Polling faster than once per second provides no meaningful benefit since event latency is already sub-second via the SSE stream. Retry guidance If the SSE stream disconnects (network error, server restart, or a 5xx response), implement exponential back-off before reconnecting:
  1. On first disconnect, reconnect after 1 second.
  2. Double the interval on each subsequent failure (2 s, 4 s, 8 s…) up to a maximum of 30 seconds.
  3. Pass replay=<N> on reconnect so you don’t miss events that arrived while offline.

Retention

All sync status data is held in Redis with a 24-hour TTL. For long-term audit, subscribe to the corresponding sync.started / sync.completed / sync.failed outgoing webhooks, which are persisted by Svix.