Syncing
Keep a local copy current with delta sync instead of refetching the library.
Refetching a whole library on every launch is wasteful and slow. GET /api/recipes supports a watermark so you can ask only for what changed.
Delta sync
Pass updated_since and you get back only recipes modified at or after that
instant, plus the ids of recipes that are no longer visible:
curl "https://api.flambe.dev/api/recipes?updated_since=2026-09-09T00:00:00.000Z" \
-H "Authorization: Bearer $FLAMBE_TOKEN"{
"recipes": [ /* changed since the watermark */ ],
"revoked_ids": ["3c2b1a09-8f7e-6d5c-4b3a-2918f7e6d5c4"],
"server_timestamp": "2026-09-10T18:22:04.512Z",
"full_sync_required": false,
"next_cursor": null
}Apply it like this:
- Upsert everything in
recipes. - Delete everything in
revoked_ids— these are recipes deleted, or un-shared from a household. - Store
server_timestampand send it as the nextupdated_since.
Use the server's timestamp, not your own clock. Clock skew between your device and the server will otherwise cause you to miss changes in the gap.
The 25-day cliff
Change history is retained for 30 days. A watermark older than 25 days is refused:
{
"recipes": [],
"revoked_ids": [],
"full_sync_required": true,
"server_timestamp": "2026-09-10T18:22:04.512Z"
}The five-day margin exists so you are told to re-sync before the history you would need actually expires.
When you see full_sync_required: true, discard local state and re-sync without
updated_since. Do not ignore it and do not retry with the same watermark —
the response is empty, so a client that ignores the flag silently stops
receiving updates forever.
async function sync(db: LocalStore) {
const since = await db.getWatermark();
const qs = since ? `?updated_since=${encodeURIComponent(since)}` : '';
const res = await authedFetch(`/api/recipes${qs}`);
const page = await res.json();
if (page.full_sync_required) {
await db.clear();
await db.setWatermark(null);
return sync(db); // once — the next call has no watermark, so it cannot recurse again
}
await db.upsertAll(page.recipes);
await db.deleteAll(page.revoked_ids);
await db.setWatermark(page.server_timestamp);
}Pagination and delta sync are independent
limit and cursor work with or without updated_since. A large delta comes
back paginated, so keep following next_cursor until it is null — and only
then commit the new watermark. Committing it mid-page loses everything on the
pages you have not read.
The change log
GET /api/recipes/changes is the lower-level primitive: an append-only event
log, for when you need to know what happened rather than just the current
state.
curl "https://api.flambe.dev/api/recipes/changes?since=0&limit=100" \
-H "Authorization: Bearer $FLAMBE_TOKEN"`since` here is a sequence number
Unlike updated_since on /api/recipes, the since on /changes is a
numeric sequence watermark, not a timestamp. Start at 0 and carry
forward the value the response gives you. Passing an ISO timestamp returns
400 Invalid since parameter.
Live updates
Delta sync covers catching up. For changes arriving while your client is open, subscribe to the event stream — and still delta-sync on reconnect, since events that happened while you were disconnected are not replayed.