go-oversync

HTTP API

All /sync/* endpoints are expected to be mounted behind authentication. The handlers require an authenticated oversync.Actor{UserID, SourceID} in request context. Auxiliary GET /syncx/health and GET /syncx/status do not require an authenticated actor.

All visible sync keys are structured JSON objects whose values are strings on the wire. UUID-valued keys use canonical dashed lowercase UUID strings. Hidden server ownership columns such as _sync_scope_id never appear in client-visible payloads, conflicts, committed bundle rows, or snapshot rows.

Wire-facing row_version and base_row_version fields are bundle sequence values. The server stores the current version internally as sync.row_state.bundle_seq and returns it through the existing wire field names.

Oversync uses RFC 8785 JSON Canonicalization Scheme (JCS) for canonical bytes. Exact BIGINT and arbitrary-precision decimal columns are schema-typed JSON strings; approximate floating-point columns remain finite binary64 JSON numbers. bundle_hash authenticates the committed logical row stream, while canonical_request_hash associates that commit with the immutable original upload. Hash-only protocol counters are decimal strings inside the logical hash model. byte_count is the length of those canonical logical bytes. This is standard JCS plus a typed wire schema, not an arbitrary-precision extension to JCS.

Oversync does not migrate incompatible stored hashes, server history, staged sessions, or durable client state. To adopt this contract, deploy compatible server and client versions together and recreate the affected server and client databases. Mixed-version operation is unsupported.

All authenticated /sync/* requests must send:

POST /sync/connect

Resolve the first-connect lifecycle for the authenticated (user_id, source_id).

Request:

{
  "has_local_pending_rows": true
}

Response:

{
  "resolution": "initialize_local",
  "initialization_id": "6df0d8dd-a84b-43b6-bbca-8de70432922a",
  "lease_expires_at": "2026-03-22T12:00:00Z"
}

Possible resolution values:

Failure contract:

Notes:

POST /sync/push-sessions

Create one staging push session for a logical dirty-set bundle.

Request:

{
  "source_bundle_id": 7,
  "planned_row_count": 1,
  "canonical_request_hash": "f9952b89b7b7ce1f4a2b9a3ea31ee7e8ca05d19bb6cb2cd400c8d18a1043fc83"
}

Response:

{
  "push_id": "6df0d8dd-a84b-43b6-bbca-8de70432922a",
  "status": "staging",
  "planned_row_count": 1,
  "next_expected_row_ordinal": 0,
  "canonical_request_hash": "f9952b89b7b7ce1f4a2b9a3ea31ee7e8ca05d19bb6cb2cd400c8d18a1043fc83"
}

Notes:

Create failure contract:

POST /sync/push-sessions/{push_id}/chunks

Upload one contiguous chunk into a staging push session.

Request:

{
  "start_row_ordinal": 0,
  "rows": [
    {
      "schema": "business",
      "table": "users",
      "key": {"id": "550e8400-e29b-41d4-a716-446655440000"},
      "op": "INSERT",
      "base_row_version": 0,
      "payload": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "John Doe",
        "email": "john@example.com"
      }
    }
  ]
}

Response:

{
  "push_id": "6df0d8dd-a84b-43b6-bbca-8de70432922a",
  "next_expected_row_ordinal": 1
}

Failure contract:

POST /sync/push-sessions/{push_id}/commit

Commit the fully staged push session atomically.

Response:

{
  "bundle_seq": 143,
  "source_id": "source-1",
  "source_bundle_id": 7,
  "row_count": 1,
  "bundle_hash": "4c8d2d5f5d2c5a41d9aa6f4d2f3ac5d0d1d5d8bbf1d7a8c39f3b3a970f6af21a",
  "canonical_request_hash": "f9952b89b7b7ce1f4a2b9a3ea31ee7e8ca05d19bb6cb2cd400c8d18a1043fc83"
}

Failure contract:

Push conflict response:

{
  "error": "push_conflict",
  "message": "update conflict on business.users 550e8400-e29b-41d4-a716-446655440000: expected version 7, got 3",
  "conflict": {
    "schema": "business",
    "table": "users",
    "key": {"id": "550e8400-e29b-41d4-a716-446655440000"},
    "op": "UPDATE",
    "base_row_version": 3,
    "server_row_version": 7,
    "server_row_deleted": false,
    "server_row": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Jane Doe",
      "email": "jane@example.com"
    }
  }
}

GET /sync/committed-bundles/{bundle_seq}/rows

Fetch one deterministic page of authoritative committed rows for accepted-push replay.

Query:

Response:

{
  "bundle_seq": 143,
  "source_id": "source-1",
  "source_bundle_id": 7,
  "row_count": 1,
  "bundle_hash": "4c8d2d5f5d2c5a41d9aa6f4d2f3ac5d0d1d5d8bbf1d7a8c39f3b3a970f6af21a",
  "canonical_request_hash": "f9952b89b7b7ce1f4a2b9a3ea31ee7e8ca05d19bb6cb2cd400c8d18a1043fc83",
  "rows": [
    {
      "schema": "business",
      "table": "users",
      "key": {"id": "550e8400-e29b-41d4-a716-446655440000"},
      "op": "INSERT",
      "row_version": 143,
      "payload": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "John Doe",
        "email": "john@example.com"
      }
    }
  ],
  "next_row_ordinal": 0,
  "has_more": false
}

DELETE /sync/push-sessions/{push_id}

Best-effort explicit cleanup for an abandoned uncommitted push session.

Response:

GET /sync/pull

Pull complete committed bundles after the client checkpoint.

Query:

Response:

{
  "stable_bundle_seq": 156,
  "bundles": [
    {
      "bundle_seq": 143,
      "source_id": "source-2",
      "source_bundle_id": 18,
      "rows": [
        {
          "schema": "business",
          "table": "users",
          "key": {"id": "550e8400-e29b-41d4-a716-446655440001"},
          "op": "INSERT",
          "row_version": 143,
          "payload": {
            "id": "550e8400-e29b-41d4-a716-446655440001",
            "name": "Jane Doe",
            "email": "jane@example.com"
          }
        }
      ]
    }
  ],
  "has_more": true
}

Notes:

Failure contract:

GET /sync/watch

Open an optional Server-Sent Events stream for metadata-only bundle wakeups.

Query:

Event:

event: bundle
data: {"bundle_seq":157,"source_id":"device-a","source_bundle_id":9}

Notes:

Failure contract:

POST /sync/snapshot-sessions

Create one frozen snapshot session for hydration or destructive recovery.

The request body is optional.

Keep-source rebuild request:

{}

Rotated rebuild request:

{
  "source_replacement": {
    "previous_source_id": "device-old",
    "new_source_id": "device-new",
    "reason": "history_pruned"
  }
}

Notes:

Response:

{
  "snapshot_id": "2f2d3f7a-cd1f-42b9-8d08-c4d2b45d9f88",
  "snapshot_bundle_seq": 156,
  "row_count": 124,
  "byte_count": 18240,
  "expires_at": "2026-03-22T12:00:00Z"
}

Failure contract:

Structured source_retired response:

{
  "error": "source_retired",
  "message": "source device-old was retired for user user-123 and replaced by device-new",
  "source_id": "device-old",
  "replaced_by_source_id": "device-new"
}

GET /sync/snapshot-sessions/{snapshot_id}

Fetch one deterministic chunk from a frozen snapshot session.

Query:

Response:

{
  "snapshot_id": "2f2d3f7a-cd1f-42b9-8d08-c4d2b45d9f88",
  "snapshot_bundle_seq": 156,
  "rows": [
    {
      "schema": "business",
      "table": "users",
      "key": {"id": "550e8400-e29b-41d4-a716-446655440001"},
      "row_version": 143,
      "payload": {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "name": "Jane Doe",
        "email": "jane@example.com"
      }
    }
  ],
  "next_row_ordinal": 1,
  "has_more": true
}

DELETE /sync/snapshot-sessions/{snapshot_id}

Best-effort explicit cleanup for a completed or abandoned snapshot session.

Response:

GET /sync/capabilities

Returns the protocol version, schema version, app name, registered tables, registered table specs, feature flags, and bundle limits.

Important feature flags:

Important bundle limit fields:

GET /syncx/health

Readiness-oriented health response. Returns HTTP 200 when the service is healthy, and HTTP 503 when the service is unhealthy. A newly constructed runtime remains unhealthy and reports accepting_operations=false until Bootstrap() has committed schema validation, trigger installation, and any populated-table adoption.

GET /syncx/status

Returns a lifecycle and operability snapshot including lifecycle, registered tables, feature flags, bundle visibility, retained-floor visibility, and error counters. Unlike sync operations, status is available while bootstrap is incomplete so operators can observe the readiness gate.