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 perrun_id), so you can quickly inspect which syncs are in progress and how recent ones ended.
ApiKeyDep authentication as the rest of the public API: pass your API key via the X-API-Key header.
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 thedata field of an SSE message with event: sync.status:
Fields
Streaming endpoint
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
: heartbeatcomment every 15 seconds to keep the connection alive.
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
Example: Node.js
Recent events
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
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:
- On first disconnect, reconnect after 1 second.
- Double the interval on each subsequent failure (2 s, 4 s, 8 s…) up to a maximum of 30 seconds.
- 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 correspondingsync.started / sync.completed / sync.failed outgoing webhooks, which are persisted by Svix.
