Incremental sync
Incremental sync lets you fetch only what's changed since the last run, reducing API calls and keeping your data current.
Basic usage
get "/items" {
since: lastSync
}
This automatically:
- Looks up when the last successful sync occurred.
- Adds a
sincequery parameter to the request. - Records a new checkpoint after the action completes.
How it works
First run
On the first run there's no checkpoint yet, so Reqon syncs from the Unix epoch:
GET /items?since=1970-01-01T00:00:00.000Z
Subsequent runs
On later runs, the last sync timestamp is used:
GET /items?since=2024-01-20T10:30:00.000Z
The parameter name defaults to since and the format defaults to ISO 8601. Both are configurable (see below).
Checkpoint storage
By default, checkpoints are stored in a per-mission file under .reqon-data/sync/:
.reqon-data/
└── sync/
└── MyMission.json
When you run with a durable execution log, sync is a view over that log instead: lastSync is resolved from the recorded checkpoint.advanced events, and there's no separate sync file.
Configuration
The since: lastSync form accepts an optional checkpoint key and an optional config block. There are no separate sinceParam, sinceFormat, or syncKey fetch options.
Custom parameter name
Tell Reqon which query parameter the API expects:
get "/items" {
since: lastSync { param: "modified_since" }
}
Generates: ?modified_since=2024-01-20T10:30:00.000Z
Send as a header instead
Use a request header rather than a query parameter (mutually exclusive with param):
get "/items" {
since: lastSync { header: "If-Modified-Since" }
}
Date format
Customise the format of the timestamp. The format is an unquoted identifier:
get "/items" {
since: lastSync { param: "updatedAfter", format: unix }
}
Supported formats:
iso— ISO 8601 (default):2024-01-20T10:30:00.000Zunix— Unix timestamp in seconds:1705748400unix-ms— Unix timestamp in milliseconds:1705748400000date-only— date portion only:2024-01-20
Custom checkpoint key
Override the automatic checkpoint key (which defaults to source:endpoint):
get "/items" {
since: lastSync("items-main-sync")
}
Advancing from a response field
By default the checkpoint advances to the sync time. To instead take the new watermark from a field in the response, use updateFrom:
get "/items" {
since: lastSync { param: "modified_since", updateFrom: "meta.lastModified" }
}
Combining with pagination
get "/items" {
paginate: offset(offset, 100),
until: length(response.items) == 0,
since: lastSync
}
The since parameter is added to each paginated request.
Combining with filters
There's no params option, so put any extra query parameters directly in the path. The since value is appended to whatever you provide:
get "/items?status=active&type=order" {
since: lastSync
}
Handling updates
Use upsert mode so re-synced records overwrite their previous version:
action IncrementalSync {
get "/items" {
paginate: offset(offset, 100),
until: length(response.items) == 0,
since: lastSync
}
for item in response.items {
store item -> items { key: .id, upsert: true }
}
}
Per-source checkpoints
Different sources maintain separate checkpoints, because the checkpoint key includes the source name:
mission MultiSourceSync {
source Xero { auth: oauth2, base: "https://api.xero.com" }
source QuickBooks { auth: oauth2, base: "https://quickbooks.api.com" }
action SyncXero {
get "/invoices" { source: Xero, since: lastSync }
// Uses an Xero-specific checkpoint
}
action SyncQuickBooks {
get "/invoices" { source: QuickBooks, since: lastSync }
// Uses a QuickBooks-specific checkpoint
}
}
Per-endpoint checkpoints
Each endpoint maintains its own checkpoint:
action SyncAll {
get "/customers" { since: lastSync }
// Checkpoint key: source:/customers
get "/orders" { since: lastSync }
// Checkpoint key: source:/orders
get "/products" { since: lastSync }
// Checkpoint key: source:/products
}
Resetting checkpoints
Via the file system
Checkpoints live in .reqon-data/sync/{mission}.json. Delete the file to force a full resync:
# Reset all checkpoints for a mission
rm .reqon-data/sync/MyMission.json
# Then run a full sync
reqon sync.reqon
Programmatically
Use a FileSyncStore, which exposes clear(key) and clearAll():
import { FileSyncStore } from 'reqon-dsl';
const sync = new FileSyncStore('MyMission');
// Clear every checkpoint for the mission
await sync.clearAll();
// Or clear a single checkpoint by key
await sync.clear('API:/items');
Full sync vs incremental
Force a full sync
Omit since to fetch everything:
action FullSync {
get "/items" // No since option = full sync
store response -> items { key: .id, upsert: true }
}
action IncrementalSync {
get "/items" { since: lastSync }
store response -> items { key: .id, upsert: true }
}
Handling deletions
Incremental sync only sees records the API returns, so it won't notice items deleted upstream. How you reconcile deletions depends on your API. Two common shapes:
- Soft deletes. If the API includes deleted records (often behind a flag) and returns them in the changed set, sync them like any other record and let a
deletedfield on the stored record mark their state. - A separate deletions feed. If the API exposes a deletions endpoint, sync it alongside the main feed in a parallel stage:
action SyncItems {
get "/items" { since: lastSync }
store response -> items { key: .id, upsert: true }
}
action SyncDeletions {
get "/items/deleted" { since: lastSync }
store response.deletions -> deletions { key: .id, upsert: true }
}
run [SyncItems, SyncDeletions]
Best practices
Always use upsert
// Good: handles both new and updated items
store item -> items { key: .id, upsert: true }
// Risky: may fail on records already present
store item -> items { key: .id }
Schedule regular syncs
mission RegularSync {
schedule: every 15 minutes
action Sync {
get "/items" { since: lastSync }
store response -> items { key: .id, upsert: true }
}
run Sync
}
Troubleshooting
Checkpoint not updating
Checkpoints only advance once an action completes. If it aborts partway through, the checkpoint stays where it was, so the next run picks up from the same point.
Wrong date format
Match your API's expected format with the format option:
// For APIs expecting ISO 8601 (default)
get "/items" { since: lastSync }
// For APIs expecting a Unix timestamp
get "/items" { since: lastSync { format: unix } }
// For APIs expecting a date only
get "/items" { since: lastSync { format: date-only } }
Missing updates
Make sure the parameter name matches what the API filters on:
// If the API expects an "updatedAfter" parameter
get "/items" { since: lastSync { param: "updatedAfter" } }
// If the API expects a "modifiedSince" parameter
get "/items" { since: lastSync { param: "modifiedSince" } }