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.
| Code | Meaning |
|---|---|
200 | Success |
401 | Missing or invalid API key |
404 | User not found |
422 | Validation error (invalid parameter value) |
500 | Internal server error |
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
| Field | Type | Description |
|---|---|---|
run_id | string | Unique identifier per sync run. Multiple events share a run_id as a sync progresses. |
provider | string | Lower-case provider slug (e.g. garmin, oura, apple). |
source | SyncSource | One of pull, webhook, sdk, backfill, xml_import. |
stage | SyncStage | One of queued, started, fetching, processing, saving, completed, failed, cancelled. |
status | SyncStatus | One of in_progress, success, partial, failed, cancelled. Terminal statuses end a run. |
progress | number | null | Optional 0..1 fraction. Not all syncs report this. |
items_processed | number | null | Optional count of items processed so far. |
items_total | number | null | Optional total when known. |
started_at | string | null | ISO-8601 timestamp when the run started. |
ended_at | string | null | ISO-8601 timestamp when the run reached a terminal status. |
timestamp | string | ISO-8601 timestamp of this individual event. |
Streaming endpoint
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | string (UUID) | Required | The user whose sync events to stream. |
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
replay | integer | 20 | 1..200 | Number of recent events to replay before the live stream begins. Allows a freshly connected client to catch up on in-progress syncs. |
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
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | string (UUID) | Required | The user whose events to return. |
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
limit | integer | 50 | 1..200 | Maximum number of events to return, ordered newest first. |
SyncStatusEvent objects, newest first.
Sync run summaries
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | string (UUID) | Required | The user whose run summaries to return. |
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
limit | integer | 20 | 1..50 | Maximum number of run summaries to return. |
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.
