All endpoints accept and return application/json. Authentication is handled by the consuming application. Synchro expects a user ID in request context for register, pull, push, and snapshot.
Method Path Description POST/sync/registerRegister or re-register a client device POST/sync/pullPull incremental changes after a checkpoint POST/sync/pushPush local changes to the server POST/sync/snapshotRead a full snapshot for bootstrap or rebuild GET/sync/tablesGet sync metadata for all tables GET/sync/schemaGet the canonical schema contract for SDK table creation
Registers a client device for sync. Upserts on (user_id, client_id). On first registration, the client is subscribed to ["user:<user_id>", "global"] buckets.
"client_id" : " device-abc-123 " ,
"client_name" : " Matt's iPhone " ,
Field Type Required Description client_idstringYes Stable device identifier (UUID or vendor ID) client_namestringNo Human-readable device name platformstringYes ios, android, web, etc.app_versionstringYes Semver app version for compatibility checks schema_versionint64Yes Schema contract version (0 before first schema fetch) schema_hashstringYes Schema contract hash ("" before first schema fetch)
"id" : " 550e8400-e29b-41d4-a716-446655440000 " ,
"server_time" : " 2026-03-05T12:00:00Z " ,
"last_sync_at" : " 2026-03-05T11:55:00Z " ,
"schema_hash" : " d16b7d9d... "
Field Type Description idstringServer-assigned client row ID server_timestringCurrent server time (ISO 8601) last_sync_atstring?Last sync timestamp, null on first registration checkpointint64Last known pull checkpoint (0 on first registration) schema_versionint64Current server schema version schema_hashstringCurrent server schema hash
Returned when app_version is below the server’s MinClientVersion.
"error" : " client upgrade required "
Retrieves incremental changes for the client after its checkpoint. pull is not the bootstrap endpoint. Fresh clients and stale clients rebuild through POST /sync/snapshot.
"client_id" : " device-abc-123 " ,
"tables" : [ " tasks " , " comments " ],
"known_buckets" : [ " user:123 " , " global " ],
"schema_hash" : " d16b7d9d... "
Field Type Required Default Description client_idstringYes — Registered client ID checkpointint64Yes — Last processed changelog seq tablesstring[]No all tables Optional table filter limitintNo 100 Max records per response (capped at 1000) known_bucketsstring[]No — Buckets the client currently knows about schema_versionint64Yes — Client schema version schema_hashstringYes — Client schema hash
"created_at" : " 2026-03-05T10:00:00Z " ,
"updated_at" : " 2026-03-05T11:30:00Z "
"updated_at" : " 2026-03-05T11:30:00Z "
"schema_hash" : " d16b7d9d... "
Field Type Description changesRecord[]Inserted or updated records with full data deletesDeleteEntry[]Records that were deleted (soft-deleted) checkpointint64New checkpoint to send on next pull has_morebooltrue if more records are available (pull again)snapshot_requiredbooltrue if the client must rebuild from /sync/snapshotsnapshot_reasonstringWhy incremental pull is invalid bucket_updatesBucketUpdate?Changes to bucket subscriptions schema_versionint64Current server schema version schema_hashstringCurrent server schema hash
When snapshot_required is true, incremental replay is invalid. The client must call POST /sync/snapshot and rebuild local state before returning to normal incremental pull.
Value Meaning initial_sync_requiredThe client has not completed initial bootstrap yet checkpoint_before_retentionThe client’s checkpoint is behind the compaction boundary history_unavailableIncremental history cannot satisfy the request
Field Type Description idstringRecord primary key table_namestringSource table dataobjectFull record as JSON (respects SyncColumns if configured) updated_atstringServer-side updated_at timestamp deleted_atstring?Present when the record is soft-deleted
Field Type Description idstringDeleted record primary key table_namestringSource table
When has_more is true, the client should immediately call pull again using the new checkpoint. Repeat until has_more is false.
Pull(checkpoint=42) -> changes[...], checkpoint=100, has_more=true
Pull(checkpoint=100) -> changes[...], checkpoint=185, has_more=true
Pull(checkpoint=185) -> changes[...], checkpoint=192, has_more=false
Pushes local changes from the client to the server. All changes are applied inside a single database transaction under RLS context.
"client_id" : " device-abc-123 " ,
"schema_hash" : " d16b7d9d... " ,
"title" : " Review proposal " ,
"client_updated_at" : " 2026-03-05T11:00:00Z "
"title" : " Updated report draft "
"client_updated_at" : " 2026-03-05T11:05:00Z " ,
"base_updated_at" : " 2026-03-05T10:00:00Z "
"table_name" : " comments " ,
"client_updated_at" : " 2026-03-05T11:10:00Z "
Field Type Required Description idstringYes Record primary key (client-generated UUID for creates) table_namestringYes Target table operationstringYes create, update, or deletedataobjectFor create/update Record fields to write client_updated_atstringYes Client-side timestamp for conflict resolution base_updated_atstringNo Server timestamp client last saw (optimistic concurrency)
"server_updated_at" : " 2026-03-05T11:00:01Z "
"reason_code" : " server_newer " ,
"message" : " server version is newer " ,
"data" : { "title" : " Server report draft " },
"updated_at" : " 2026-03-05T11:03:00Z "
"server_time" : " 2026-03-05T12:00:00Z " ,
"schema_hash" : " d16b7d9d... "
Field Type Description idstringRecord primary key table_namestringTarget table operationstringcreate, update, or deletestatusstringProcessing outcome reason_codestring?Machine-readable rejection or conflict reason messagestring?Human-readable message server_versionRecord?Current server record for conflicts server_updated_atstring?Server timestamp for successfully applied writes server_deleted_atstring?Server delete timestamp when relevant
Status Meaning appliedThe write was accepted and committed conflictThe server version won; server_version is returned rejected_terminalThe write is invalid and should be removed from the local queue rejected_retryableThe write should remain queued and be retried later
Reads a full snapshot for bootstrap or local rebuild. Snapshot pages are stateless except for the cursor the client sends back.
The server captures a snapshot checkpoint once at the start of the flow. That checkpoint is carried in the cursor and returned on every page. After the final page, the client uses that checkpoint for normal incremental pull.
"client_id" : " device-abc-123 " ,
"schema_hash" : " d16b7d9d... "
Field Type Required Default Description client_idstringYes — Registered client ID cursorSnapshotCursor?No nullCursor from the previous snapshot page limitintNo 100 Max records per response (capped at 1000) schema_versionint64Yes — Client schema version schema_hashstringYes — Client schema hash
Field Type Description checkpointint64Snapshot checkpoint captured on the first page table_idxintCurrent table index in registration order after_idstringLast emitted primary key within the current table
"created_at" : " 2026-03-05T10:00:00Z " ,
"updated_at" : " 2026-03-05T11:30:00Z "
"updated_at" : " 2026-03-05T11:30:00Z "
"after_id" : " a1b2c3d4-... "
"schema_hash" : " d16b7d9d... "
Field Type Description recordsRecord[]Full non-deleted rows visible to the client cursorSnapshotCursor?Cursor for the next page, omitted on the final page checkpointint64Snapshot checkpoint captured at snapshot start has_morebooltrue if more snapshot pages remainschema_versionint64Current server schema version schema_hashstringCurrent server schema hash
3. POST /sync/snapshot {client_id, cursor: null}
4. POST /sync/snapshot {client_id, cursor: {...}}
5. Repeat until has_more=false
6. Persist the returned checkpoint
7. Resume normal POST /sync/pull from that checkpoint
Returns sync metadata for all registered tables. No authentication required.
"push_policy" : " owner_only " ,
"table_name" : " comments " ,
"push_policy" : " owner_only " ,
"dependencies" : [ " tasks " ],
"table_name" : " categories " ,
"push_policy" : " disabled " ,
"server_time" : " 2026-03-05T12:00:00Z " ,
"schema_hash" : " d16b7d9d... "
Returns the server-authoritative schema contract for local table creation and migration.
"schema_hash" : " d16b7d9d... " ,
"server_time" : " 2026-03-05T12:00:00Z " ,
"push_policy" : " owner_only " ,
"updated_at_column" : " updated_at " ,
"deleted_at_column" : " deleted_at " ,
"logical_type" : " string " ,
"db_type" : " timestamp with time zone " ,
"logical_type" : " datetime " ,
"default_sql" : " CURRENT_TIMESTAMP " ,
"default_kind" : " portable " ,
"sqlite_default_sql" : " CURRENT_TIMESTAMP " ,
Field Type Description namestringColumn name db_typestringSource PostgreSQL type logical_typestringClient SDK logical type nullableboolWhether the column is nullable default_sqlstring?Raw PostgreSQL default expression default_kindstringnone, portable, or server_onlysqlite_default_sqlstring?SQLite-safe default expression when default_kind=portable is_primary_keyboolWhether the column is part of the primary key
Most errors return a JSON body with an error field.
Status Condition 400Malformed request body or missing required fields 401Missing user identity in context 404Snapshot requested for an unregistered client 409Schema mismatch (code = schema_mismatch) 426Client version below MinClientVersion 429Rate limited (via RetryAfterMiddleware) 500Internal server error 503Transient error (DB down, connection lost, timeout)
"error" : " description of the error "
429 and 503 responses include a Retry-After header and a retry_after field in the body:
"error" : " service temporarily unavailable " ,
"code" : " schema_mismatch " ,
"message" : " client schema does not match server schema " ,
"server_schema_version" : 7 ,
"server_schema_hash" : " d16b7d9d... "
Schema recovery
When a client receives a 409, it should re-fetch GET /sync/schema, apply any local migrations, and retry the original request with the updated schema_version and schema_hash.