/api/sync (push / pull pipeline)
The component, pattern, and page sync pipeline between a workspace and its registry.
The low-level protocol behind handoff-app push/pull/sync-status, distinct from
/api/registry's one-endpoint-per-data-type model. All three
routes require a sync bearer (verifySyncAuth(), see Auth & scopes);
upload additionally requires sync:write.
| Endpoint | Purpose |
|---|---|
GET /api/sync/status | Connection health + current registry statistics — what sync-status prints. |
GET /api/sync/changes?since=<id> | Returns every sync event recorded after since (a sync event ID, not a timestamp). since=0 (the default) returns full history. This is what pull diffs against .handoff/sync-state.json to decide what to write back into the workspace. |
POST /api/sync/upload | Applies a batch of create/update/delete sync events — components, patterns, pages, source files, build artifacts, screenshots — from a push. |
Upload semantics
POST /api/sync/upload takes { changes: SyncChange[] } and applies them in order. The batch is
fail-fast, not partial: the first bad or failing entry ends the request, and entries after it
are never attempted.
200:{ ok: true, appliedCount, applied: [...] }, everything applied.400: a malformed entry (missingentityType/entityId/action). Entries before it in the array have already been written.500: an entry threw while being applied,{ error, applied: [...] }lists what did make it through before the failure.
openapi.yaml still documents a 207 for this route, but the handler never returns one; there is
no partial-success status code anywhere in the app. Treat a non-200 as "the batch stopped
part-way" and re-push.
A component/pattern data change triggers an async validation snapshot on the registry side.
Images ride along with component payloads
Component artifacts pushed through upload may reference local images (quoted strings, CSS
url()). The CLI resolves these to local files, assigns content-addressed IDs
(img_<sha256[:12]>), rewrites the references in the pushed artifact text to
/api/handoff/assets/{assetId}/raw, and pushes the images themselves separately via the
per-image ingest endpoint (POST /api/registry/assets/ingest), so the registry always serves
images from its own asset store, regardless of where they lived in the workspace.
Relationship to push:all
push:all covers config/theme/navigation/tokens/dtcg/icons/logos/fonts (all through
/api/registry/*), components and pages go through this cluster
instead, as one of push:all's steps (or standalone via plain push). See
Workspace → CLI reference for every flag on both commands.