FieldCamp

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 whose updatedAt is 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 the timezone parameter 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 by updatedAt, oldest first. This is the important one. The default sort is createdAt desc, so without it you get rows in creation order and can't track a clean cursor — which is why combining since with page/limit returns what looks like stale data.
  • page and limit — offset pagination. page is 1-based (default 1); limit defaults to 30 and is capped at 100. A larger value like limit=1000 is not rejected — it is silently clamped to 100, so you still get 200 with only 100 rows. Never treat one response as "all of it": read meta.total for the real count and page through the rest with page.

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=100

Keep 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=100

Walk 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

SymptomCauseFix
Row count is your whole account (e.g. 15,762)Calling /api/jobs instead of /api/v1/jobsAdd /v1/ — the legacy route ignores since
Rows look stale / paging returns old recordsNo sort=updatedAt:asc (default is createdAt)Add sort=updatedAt:asc
Cutoff is a few hours offsince has no timezone, read as UTCSend …Z / an offset, or add timezone=
Only 100 rows even with limit=1000limit is capped at 100Paginate 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.

On this page