Incremental sync | FieldCamp API
Pull only what changed — build a dashboard or a mirror by polling the FieldCamp v1 API with the `since` cursor, `sort=updatedAt:asc`, and pagination.
If you're building a dashboard, a data warehouse, or a mirror of your FieldCamp data, you don't want to re-download everything on every run. Incremental sync lets you pull only the records that changed since your last run, using three query parameters that every v1 list endpoint supports: since, sort, and page/limit.
This pattern works identically on jobs, clients, invoices, and visits.
Use /api/v1/…, not /api/…
The since filter only exists on the versioned path. GET /api/v1/jobs filters by since; the legacy GET /api/jobs (no v1) is an internal route that ignores since entirely and returns its normal unfiltered list. If your row count looks like your whole account, you're almost certainly missing the /v1/ in the URL.
The three parameters
since— an ISO-8601 timestamp. Returns only rows whoseupdatedAtis at or after it. Send it with a timezone — UTC (2026-08-26T13:04:20Z) or an offset (2026-08-26T07:04:20-06:00) — or add thetimezoneparameter for a zone-less value. A value with no timezone is read as UTC, so a local wall-clock time lands the cutoff in the wrong place.sort=updatedAt:asc— order byupdatedAt, oldest first. This is the important one. The default sort iscreatedAt desc, so without it you get rows in creation order and can't track a clean cursor — which is why combiningsincewithpage/limitreturns what looks like stale data.pageandlimit— offset pagination.pageis 1-based (default1);limitdefaults to30and is capped at 100. A larger value likelimit=1000is not rejected — it is silently clamped to 100, so you still get200with only 100 rows. Never treat one response as "all of it": readmeta.totalfor the real count and page through the rest withpage.
The loop
Backfill once
Start with no since and walk every page, sorted by updatedAt:
GET /api/v1/jobs?sort=updatedAt:asc&page=1&limit=100Keep incrementing page until you've read meta.total rows. The updatedAt of the last row on the last page is your starting cursor — save it.
Sync incrementally
On every later run, pass your saved cursor as since and keep the same sort:
GET /api/v1/jobs?since={last_updated_at}&sort=updatedAt:asc&page=1&limit=100Walk the pages, upsert each row into your store, and save the last row's updatedAt as the next run's since.
Response shape
Every v1 list returns the rows in data and pagination in meta:
{
"success": true,
"data": [ /* … up to `limit` rows … */ ],
"meta": { "page": 1, "limit": 100, "total": 42 }
}There is no next cursor — increment page until page * limit >= meta.total.
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
| Row count is your whole account (e.g. 15,762) | Calling /api/jobs instead of /api/v1/jobs | Add /v1/ — the legacy route ignores since |
| Rows look stale / paging returns old records | No sort=updatedAt:asc (default is createdAt) | Add sort=updatedAt:asc |
| Cutoff is a few hours off | since has no timezone, read as UTC | Send …Z / an offset, or add timezone= |
Only 100 rows even with limit=1000 | limit is capped at 100 | Paginate with page |
FAQ
What counts as "changed"? Any write bumps updatedAt — create, edit, status change, payment, etc. Filtering on updatedAt (via since) catches all of them; there is no separate "created since" filter, and you don't need one — new rows also have an updatedAt.
Do I filter server-side or client-side? Server-side. since is applied in the database, so you only download the rows that changed — not the whole table.
Idempotency | FieldCamp API
Make FieldCamp API retries safe with idempotent create patterns using server-side filters on the v1 endpoints.
FieldCamp API Rate Limits and Throttling Headers
Understand FieldCamp API rate limits, the 60 RPM sliding window, throttling headers, and how to handle 429 errors in production integrations.