# Incremental sync

> updatedSince on the six lists — inclusive, ordered by updatedAt ascending, with an overlap-and-dedupe recipe keyed by (id, revision) on runs and (id, updatedAt) elsewhere.

Source: https://developers.processcart.com/guides/incremental-sync

Six lists accept `updatedSince=<ISO 8601 instant>`: `GET /v1/runs`, `GET /v1/workflows`, `GET /v1/views`, `GET /v1/webhooks`, `GET /v1/schedules` and `GET /v1/automation-rules`. It is the one rule for keeping a copy of a collection current without reading every record.

## The rule

- `updatedSince` keeps the items whose `updatedAt` is **at or after** the instant (inclusive, `>=`).
- The order becomes **`updatedAt` ascending**, with the id as the tiebreaker. An explicit `sort` or `order` that conflicts with it is a 422 (`errors[].code = "conflicts_with_updated_since"`).
- Pagination works as usual: follow `nextCursor` with the same `updatedSince`. A cursor sent without it, or with a different filter, is refused (422, `errors[].code` `cursor_mismatch`); a cursor the API did not hand out is `invalid_cursor`.
- `updatedAt` is the server's write time of the record's last change. On runs it moves on every persisted change (a field, a task, the header, a file); comments do not move it.

```bash
curl "https://app.processcart.com/api/v1/runs?updatedSince=2026-10-07T18:00:00Z&limit=100&includeTrashed=true" \
  -H "Authorization: Bearer pc_live_…"
```

## The recipe

1. Remember the **start instant** of your previous sync (your clock, taken before the first request).
2. Request with `updatedSince = that instant minus one minute` — the overlap covers clock skew and in-flight writes.
3. Page to the end.
4. **Dedupe.** Runs: by `(id, revision)` — the same number you send back as `Expected-Revision` ([Concurrency](https://developers.processcart.com/guides/concurrency)); the same `revision` is unchanged, a higher one replaces yours. Workflows, views, webhooks, schedules and automation rules carry no revision: compare `updatedAt` — a later value replaces yours, an equal one is unchanged.
5. Store the start instant of this sync for the next one.

Two writes can share an `updatedAt` instant, and the revision can jump by more than one between two reads (the engine may bump it twice for one logical change). Neither matters to the recipe: on runs, compare revisions, not timestamps.

## Trash and deletions

`GET /v1/runs` hides trashed runs unless you send `includeTrashed=true`. A sync that must notice deletions sends it: a trashed run then arrives with `deletedAt` set, and a restored one with `deletedAt: null`. (A webhook for a run trashed or restored with its workflow carries only a lite header — `{ id, name, revision, workflow, deletedAt }` with `data.withWorkflow: true` — whose `deletedAt` and `revision` are enough to mark the row; fetch `GET /v1/runs/{id}` when you need the body.) Runs are purged from Trash after 30 days; a run you never saw trashed and never see again was purged — treat an id that stops appearing as gone only after that window.

## Webhooks instead

If you would rather be told than ask, a webhook on `run.updated`, `run.status_changed`, `task.completed` and friends carries the full Run in every delivery, except the lite header on a workflow-trash cascade ([Webhooks](https://developers.processcart.com/webhooks)). Deliveries are unordered, so apply the same `(id, revision)` rule there; a sync with `updatedSince` is the way to catch up after an outage.
