SyncStore Protocol

A language-agnostic protocol for building offline-first applications

SyncStore is an open protocol specification for client-first, offline-capable datastores. It provides a standardized way to synchronize collected key-value pairs holding JSON data between clients and servers, with optional graph-like relations between records.

Why SyncStore?

Offline-First by Design

Build applications that work seamlessly without connectivity. Changes sync automatically when back online.

Platform Agnostic

Implement in any language or framework. The protocol speaks JSON, MsgPack, or Protobuf over HTTP.

Conflict-Aware

The inbox/outbox pattern with explicit outcomes (accepted/rejected) gives you full control over conflict resolution.

Flexible Data Model

Collections, indexes, and optional graph relations let you model complex data without sacrificing sync simplicity.

How It Works

1

Outbox — Clients queue local changes (set, patch, delete) with a unique reference ID.

2

Sync — Changes are pushed to the server via POST /sync/outbox.

3

Inbox — Clients pull confirmed changes via GET /sync/inbox, learning what was accepted or rejected.

4

Checkpoint — Periodic snapshots allow clients to bootstrap without replaying full history.

Concepts

Collections

A collection is a named container for key-value pairs. Keys are strings and are automatically sorted in lexicographical order. Each key maps to a JSON value. Collections provide the primary way to organize and query data in SyncStore.

Indexes

Indexes allow you to filter and re-order a subset of items in a collection using arbitrary string values as ordering keys. An item can belong to multiple indexes, each with its own ordering key. When querying with an index, only items in that index are returned, sorted lexicographically by their ordering key.

Relations

Relations create graph-like connections between records across collections using a triple-based model: subject → predicate → object. The subject is the item the relation belongs to, the predicate (type) describes the relationship, and the object references another item in any collection.

Operations

Data Operations

Operation Description
set Replace the entire value at a key in a collection
patch Apply a JSON Patch (RFC 6902) to an existing value
delete Remove a key from a collection

Endpoints

Sync

Method Endpoint Description
GET /v1/:db/sync/inbox Pull confirmed changes from server
POST /v1/:db/sync/outbox Push local changes to server
[{
  "ref": "client-uuid-123",
  "collection": "tasks",
  "key": "task_1",
  "operation": "set" | "patch" | "delete",
  "data": { ... },
  "metadata": { ... },
  "timestamp": "2024-06-01T12:00:00Z"
}]
GET /v1/:db/sync/checkpoint Retrieve a snapshot for bootstrapping

Data

Method Endpoint Description
GET /v1/:db/data List all collections
GET /v1/:db/data/:collection List items in a collection (with optional index query)
GET /v1/:db/data/:collection/:key Read a single item
PUT /v1/:db/data/:collection/:key Create or replace an item
{
  "name": "My Task",
  "completed": false,
  "dueDate": "2024-06-15"
}
PATCH /v1/:db/data/:collection/:key Apply JSON Patch (RFC 6902) to an item
[
  { "op": "replace", "path": "/completed", "value": true },
  { "op": "add", "path": "/completedAt", "value": "2024-06-10T14:30:00Z" }
]
DELETE /v1/:db/data/:collection/:key Remove an item

Indexes

Indexes allow you to assign an arbitrary ordering key to items in a collection. When querying with an index, results are sorted lexicographically by this key, enabling filtering and custom ordering. Only items with an index entry are returned.

Method Endpoint Description
GET /v1/:db/data/:collection/:key/indexes List all index entries for an item
PUT /v1/:db/data/:collection/:key/indexes/:name Set an item's entry in an index
{
  "value": "2024-06-10T14:30:00Z"
}

Query with: GET /v1/:db/data/tasks?index=completed&dir=asc

DELETE /v1/:db/data/:collection/:key/indexes/:name Remove an item from an index

Relations

Method Endpoint Description
GET /v1/:db/data/:collection/:key/relations List all relations for an item
PUT /v1/:db/data/:collection/:key/relations/:id Create or update a relation
{
  "type": "belongs_to",
  "object": {
    "collection": "users",
    "key": "user_123"
  }
}
DELETE /v1/:db/data/:collection/:key/relations/:id Remove a relation

Batch

Method Endpoint Description
POST /v1/:db/batch Execute multiple data operations atomically
[
  {
    "method": "PUT",
    "path": "/data/tasks/task_1",
    "body": { "name": "Task 1" }
  },
  {
    "method": "PUT",
    "path": "/data/tasks/task_2",
    "body": { "name": "Task 2" }
  },
  {
    "method": "DELETE",
    "path": "/data/tasks/task_3"
  }
]

Supported Formats

SyncStore is transport-format agnostic. Any format that can be encoded to and decoded from JSON without loss is valid. We recommend implementors support JSON and MessagePack at minimum.

Content-Type Status Description
application/json Recommended Standard JSON encoding, universally supported
application/msgpack Recommended Binary format, more compact and faster to parse
application/protobuf+json Optional Protocol Buffers with JSON mapping

Clients specify their preferred format via the Content-Type and Accept headers.

Recommended Limits

These limits are recommendations to keep the protocol lightweight and performant. Implementors may choose to adjust them based on their use case.

Resource Limit
Item keys 128 bytes
Index names 128 bytes
Index ordering keys 128 bytes
Relation IDs 128 bytes
Item values 1 MB (uncompressed JSON)