Skip to content

API Reference

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.


MethodPathDescription
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",
"platform": "ios",
"app_version": "1.3.0",
"schema_version": 0,
"schema_hash": ""
}
FieldTypeRequiredDescription
client_idstringYesStable device identifier (UUID or vendor ID)
client_namestringNoHuman-readable device name
platformstringYesios, android, web, etc.
app_versionstringYesSemver app version for compatibility checks
schema_versionint64YesSchema contract version (0 before first schema fetch)
schema_hashstringYesSchema 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",
"checkpoint": 42,
"schema_version": 7,
"schema_hash": "d16b7d9d..."
}
FieldTypeDescription
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",
"checkpoint": 42,
"tables": ["tasks", "comments"],
"limit": 100,
"known_buckets": ["user:123", "global"],
"schema_version": 7,
"schema_hash": "d16b7d9d..."
}
FieldTypeRequiredDefaultDescription
client_idstringYesRegistered client ID
checkpointint64YesLast processed changelog seq
tablesstring[]Noall tablesOptional table filter
limitintNo100Max records per response (capped at 1000)
known_bucketsstring[]NoBuckets the client currently knows about
schema_versionint64YesClient schema version
schema_hashstringYesClient schema hash
{
"changes": [
{
"id": "a1b2c3d4-...",
"table_name": "tasks",
"data": {
"id": "a1b2c3d4-...",
"title": "Write report",
"user_id": "user-123",
"created_at": "2026-03-05T10:00:00Z",
"updated_at": "2026-03-05T11:30:00Z"
},
"updated_at": "2026-03-05T11:30:00Z"
}
],
"deletes": [
{
"id": "e5f6g7h8-...",
"table_name": "comments"
}
],
"checkpoint": 58,
"has_more": true,
"bucket_updates": {
"added": ["share:xyz"],
"removed": []
},
"schema_version": 7,
"schema_hash": "d16b7d9d..."
}
FieldTypeDescription
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/snapshot
snapshot_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.

ValueMeaning
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
FieldTypeDescription
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
FieldTypeDescription
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_version": 7,
"schema_hash": "d16b7d9d...",
"changes": [
{
"id": "new-uuid-here",
"table_name": "tasks",
"operation": "create",
"data": {
"title": "Review proposal",
"status": "open"
},
"client_updated_at": "2026-03-05T11:00:00Z"
},
{
"id": "existing-uuid",
"table_name": "tasks",
"operation": "update",
"data": {
"title": "Updated report draft"
},
"client_updated_at": "2026-03-05T11:05:00Z",
"base_updated_at": "2026-03-05T10:00:00Z"
},
{
"id": "deleted-uuid",
"table_name": "comments",
"operation": "delete",
"client_updated_at": "2026-03-05T11:10:00Z"
}
]
}
FieldTypeRequiredDescription
idstringYesRecord primary key (client-generated UUID for creates)
table_namestringYesTarget table
operationstringYescreate, update, or delete
dataobjectFor create/updateRecord fields to write
client_updated_atstringYesClient-side timestamp for conflict resolution
base_updated_atstringNoServer timestamp client last saw (optimistic concurrency)
{
"accepted": [
{
"id": "new-uuid-here",
"table_name": "tasks",
"operation": "create",
"status": "applied",
"server_updated_at": "2026-03-05T11:00:01Z"
}
],
"rejected": [
{
"id": "existing-uuid",
"table_name": "tasks",
"operation": "update",
"status": "conflict",
"reason_code": "server_newer",
"message": "server version is newer",
"server_version": {
"id": "existing-uuid",
"table_name": "tasks",
"data": { "title": "Server report draft" },
"updated_at": "2026-03-05T11:03:00Z"
}
}
],
"checkpoint": 0,
"server_time": "2026-03-05T12:00:00Z",
"schema_version": 7,
"schema_hash": "d16b7d9d..."
}
FieldTypeDescription
idstringRecord primary key
table_namestringTarget table
operationstringcreate, update, or delete
statusstringProcessing 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
StatusMeaning
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",
"cursor": null,
"limit": 100,
"schema_version": 7,
"schema_hash": "d16b7d9d..."
}
FieldTypeRequiredDefaultDescription
client_idstringYesRegistered client ID
cursorSnapshotCursor?NonullCursor from the previous snapshot page
limitintNo100Max records per response (capped at 1000)
schema_versionint64YesClient schema version
schema_hashstringYesClient schema hash
FieldTypeDescription
checkpointint64Snapshot checkpoint captured on the first page
table_idxintCurrent table index in registration order
after_idstringLast emitted primary key within the current table
{
"records": [
{
"id": "a1b2c3d4-...",
"table_name": "tasks",
"data": {
"id": "a1b2c3d4-...",
"title": "Write report",
"user_id": "user-123",
"created_at": "2026-03-05T10:00:00Z",
"updated_at": "2026-03-05T11:30:00Z"
},
"updated_at": "2026-03-05T11:30:00Z"
}
],
"cursor": {
"checkpoint": 58,
"table_idx": 1,
"after_id": "a1b2c3d4-..."
},
"checkpoint": 58,
"has_more": true,
"schema_version": 7,
"schema_hash": "d16b7d9d..."
}
FieldTypeDescription
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 remain
schema_versionint64Current server schema version
schema_hashstringCurrent server schema hash
1. POST /sync/register
2. GET /sync/schema
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.

{
"tables": [
{
"table_name": "tasks",
"push_policy": "owner_only",
"dependencies": [],
"parent_table": ""
},
{
"table_name": "comments",
"push_policy": "owner_only",
"dependencies": ["tasks"],
"parent_table": "tasks"
},
{
"table_name": "categories",
"push_policy": "disabled",
"dependencies": []
}
],
"server_time": "2026-03-05T12:00:00Z",
"schema_version": 7,
"schema_hash": "d16b7d9d..."
}

Returns the server-authoritative schema contract for local table creation and migration.

{
"schema_version": 7,
"schema_hash": "d16b7d9d...",
"server_time": "2026-03-05T12:00:00Z",
"tables": [
{
"table_name": "tasks",
"push_policy": "owner_only",
"updated_at_column": "updated_at",
"deleted_at_column": "deleted_at",
"primary_key": ["id"],
"columns": [
{
"name": "id",
"db_type": "uuid",
"logical_type": "string",
"nullable": false,
"default_kind": "none",
"is_primary_key": true
},
{
"name": "created_at",
"db_type": "timestamp with time zone",
"logical_type": "datetime",
"nullable": false,
"default_sql": "CURRENT_TIMESTAMP",
"default_kind": "portable",
"sqlite_default_sql": "CURRENT_TIMESTAMP",
"is_primary_key": false
}
]
}
]
}
FieldTypeDescription
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_only
sqlite_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.

StatusCondition
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",
"retry_after": 5
}
{
"code": "schema_mismatch",
"message": "client schema does not match server schema",
"server_schema_version": 7,
"server_schema_hash": "d16b7d9d..."
}