Sync setup

Bring your own sync server.

This guide is for people comfortable running a web server. Sync is completely optional — if that’s not you, cinefill works fully without it. cinefill stores data on your device first. When you turn sync on, you give the app a server URL and a personal token, and it mirrors your diary, watchlist, and standout episodes to that server.

cinefill sync settings with server URL, token, connection check, and sync controls

What the app calls

Enter the origin of your server in the app, for example https://media.example.com. cinefill appends these paths:

Health check

GET /api/cinefill/v1/health should return any 2xx response when the bearer token is valid. The app uses this to test your settings.

Sync exchange

POST /api/cinefill/v1/sync receives local changes and returns server changes in one request. Use Authorization: Bearer YOUR_TOKEN and Content-Type: application/json.

Request shape

The app sends a schema version, its device ID, the last cursor it received, and dirty local records grouped by collection.

{
  "schemaVersion": 1,
  "deviceId": "device:...",
  "cursor": "opaque-server-cursor-or-null",
  "changes": {
    "diaryEntries": [],
    "watchlistItems": [],
    "episodeStandouts": []
  }
}

Response shape

Return the records you accepted, the next cursor, and any remote changes the device has not seen yet. Cursors are opaque: choose a timestamp, sequence number, or durable change-log token that works for your server.

{
  "schemaVersion": 1,
  "serverTime": "2026-05-28T21:00:00.000Z",
  "nextCursor": "opaque-server-cursor",
  "accepted": {
    "diaryEntries": ["diary:..."],
    "watchlistItems": ["watchlist:movie:123"],
    "episodeStandouts": ["standout:123:1:4"]
  },
  "changes": {
    "diaryEntries": [],
    "watchlistItems": [],
    "episodeStandouts": []
  }
}

Record shapes

Each collection uses the same merge fields: syncId, updatedAt, deletedAt, and lastModifiedDeviceId. These examples show the fields your server should store and return.

DiaryEntry

{
  "syncId": "diary:lwm7c4x1:ab12cd34ef56gh78",
  "tmdbId": 123,
  "mediaType": "movie",
  "seasonNumber": null,
  "seasonName": null,
  "title": "Example Film",
  "year": "2026",
  "posterPath": "/poster.jpg",
  "watchedDate": "2026-05-28",
  "rating": 4.5,
  "note": "A private note.",
  "isPublic": false,
  "createdAt": 1779991200000,
  "updatedAt": 1779991200000,
  "deletedAt": null,
  "lastModifiedDeviceId": "device:..."
}

WatchlistItem

{
  "syncId": "watchlist:movie:123",
  "tmdbId": 123,
  "mediaType": "movie",
  "title": "Example Film",
  "year": "2026",
  "posterPath": "/poster.jpg",
  "isPublic": false,
  "addedAt": 1779991200000,
  "updatedAt": 1779991200000,
  "deletedAt": null,
  "lastModifiedDeviceId": "device:..."
}

EpisodeStandout

{
  "syncId": "standout:456:1:4",
  "tmdbId": 456,
  "seasonNumber": 1,
  "episodeNumber": 4,
  "episodeName": "Example Episode",
  "showTitle": "Example Show",
  "posterPath": "/season-poster.jpg",
  "markedAt": 1779991200000,
  "updatedAt": 1779991200000,
  "deletedAt": null,
  "lastModifiedDeviceId": "device:..."
}

Server checklist

  • Store records by syncId. Each record includes updatedAt, deletedAt, and lastModifiedDeviceId so clients can merge changes.
  • Treat deletedAt records as tombstones. Keep them long enough for other devices to learn that something was removed.
  • Return only records owned by the token holder. cinefill does not send accounts or passwords; your token is the boundary.
  • Keep diary and watchlist records private unless isPublic is true. Public media pages should render only records the user explicitly marked public.
  • Make the operation idempotent. If a device retries the same request, accepting the same syncId again should be harmless.

In the app

Open the gear in the You tab, choose Sync, enter your server URL and token, then tap Check connection. Once the health check passes, enable sync and tap Sync now.

For privacy behavior and common questions, read the FAQ.