Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Sync Reference

Change tracking, cursor management, devices, preferences, and conflicts.

Base path: /api/v1/sync/

Change object#

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "id": "chg_01J3L",
  "tenant_id": "tnt_01J3A",
  "share_id": "shr_01J3K",
  "resource_type": "file",
  "resource_id": "fil_01J3M",
  "change_type": "updated",
  "changed_by": "usr_01J3N",
  "changed_at": "2026-04-23T10:15:02Z",
  "old_path": "/Engineering/spec.pdf",
  "new_path": "/Engineering/spec.pdf",
  "version_id": "ver_01J3P",
  "metadata": {"size": 12345, "mime_type": "application/pdf"}
}

change_type: created, updated, deleted, moved, renamed. resource_type: file, folder, share.

Change tracking#

GET /api/v1/sync/changes#

Fetch changes since a cursor.

Query:

Param Notes
cursor Opaque cursor from prior response; omit to start from current position
share_id Scope to one share
limit 1–5000, default 1000

Response:

json
1
2
3
4
5
6
{
  "changes": [...],
  "cursor": 1045000,
  "has_more": true,
  "share_id": "shr_01J3K"
}

Required permission: READ on the share.

GET /api/v1/sync/status#

Current cursor position and sync state.

Query: share_id (required), device_id (required).

Response:

json
1
2
3
4
5
6
{
  "cursor_position": 1042387,
  "last_sync_at": "2026-04-23T10:15:00Z",
  "pending_changes": 0,
  "has_conflicts": false
}

Creates a cursor at the current head if none exists.

POST /api/v1/sync/cursor#

Advance the cursor.

Query: share_id, device_id, position.

Response: {cursor_position, last_sync_at}.

POST /api/v1/sync/apply#

Apply client-side changes to the server.

Body:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  "device_id": "dev_01J3KX",
  "share_id": "shr_01J3K",
  "conflict_resolution": "LAST_WRITER_WINS",
  "changes": [
    {
      "resource_type": "file",
      "resource_id": "fil_01J3M",
      "change_type": "updated",
      "version_id": "ver_01J4Q",
      "base_version_id": "ver_01J3P",
      "old_path": "/Engineering/spec.pdf",
      "new_path": "/Engineering/spec.pdf",
      "metadata": {}
    }
  ]
}

Resolution values: LAST_WRITER_WINS, KEEP_BOTH, SERVER_WINS, CLIENT_WINS, MANUAL.

Response:

json
1
2
3
4
5
{
  "applied": 8,
  "conflicts": [...],
  "errors": [...]
}

GET /api/v1/sync/download/{file_id}#

Download file content during sync (range-capable).

Query: version, range_start, range_end.

Headers: Supports Range: bytes=start-end.

Required permission: READ on the file.

Conflicts#

GET /api/v1/sync/conflicts#

List conflicts.

Query:

Param Notes
device_id Filter by device
share_id Filter by share
include_resolved Default false

Response: Array of conflict objects:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "id": "cfl_01J4R",
  "resource_type": "file",
  "resource_id": "fil_01J3M",
  "conflict_type": "version_mismatch",
  "client_version_id": "ver_01J4Q",
  "server_version_id": "ver_01J4S",
  "is_resolved": false,
  "resolution": null,
  "created_at": "2026-04-23T10:15:00Z"
}

POST /api/v1/sync/conflicts/{conflict_id}/resolve#

Resolve a conflict.

Query: resolution (one of the strategies).

Response: Updated conflict object.

Devices#

GET /api/v1/sync/devices#

List the caller's registered devices.

Response: Array of device objects:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "id": "dev_row_01J3K",
  "device_id": "dev_01J3KX",
  "device_name": "Alice MacBook",
  "platform": "macos",
  "client_version": "1.2.0",
  "is_active": true,
  "last_seen_at": "2026-04-23T10:14:58Z",
  "created_at": "2026-01-15T08:00:00Z"
}

POST /api/v1/sync/devices#

Register a device. Returns 201.

Body:

Field Notes
device_id Client-generated; opaque string, typically a UUID
device_name Display
platform windows, macos, linux, ios, android, web, cli
client_version Semver string

PATCH /api/v1/sync/devices/{device_id}#

Update device.

Body: device_name, is_active.

DELETE /api/v1/sync/devices/{device_id}#

Remove device. Returns 204. Cursors associated with the device stop advancing.

POST /api/v1/sync/devices/{device_id}/heartbeat#

Record device activity. Returns 204. Updates last_seen_at.

Sync preferences#

GET /api/v1/sync/devices/{device_id}/preferences#

List per-share sync preferences for a device.

Response:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[
  {
    "id": "pref_01J4A",
    "share_id": "shr_01J3K",
    "share_name": "Engineering",
    "sync_enabled": true,
    "selected_folders": ["fld_01J4A"],
    "excluded_folders": null
  }
]

PUT /api/v1/sync/devices/{device_id}/preferences/{share_id}#

Update a single preference.

Body: sync_enabled, selected_folders, excluded_folders.

PUT /api/v1/sync/devices/{device_id}/preferences#

Bulk update.

Body:

json
1
2
3
4
5
{
  "preferences": [
    {"share_id": "shr_01J3K", "sync_enabled": true, "selected_folders": null, "excluded_folders": null}
  ]
}

Real-time#

WebSocket /api/v1/sync/ws/{device_id}#

Sync nudges for a device.

Auth: ?token=<JWT> query parameter, or Authorization: Bearer header if the client library supports it.

Server frames: change, sync_preference_updated, force_sync, pong. Client frames: {"type": "ping"}, {"type": "subscribe"}.

See Real-time WebSocket.

Error codes#

Code HTTP When
SYNC_CONFLICT 409 Version mismatch on apply
SYNC_VERSION_STALE 409 base_version_id outdated
SYNC_CURSOR_INVALID 400 Malformed or unknown cursor
NOT_FOUND 404 Device, conflict, or share not found
Updated 2026-05-18 15:04:16 View source (.md) rev 2