> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.usegrade.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Syncing

> Keep local copies current by downloading changed records and removals with durable cursors.

Sync endpoints let you maintain a local copy of Grade data. Start with a paginated
copy, then poll with the saved cursor to receive changed records and removal IDs.
An unchanged feed returns an empty page. You choose when to poll.

| Feed | Scope | Record identity |
| - | - | - |
| [Campaign media](https://api-docs.usegrade.com/campaign-media/sync.md) | `campaigns:read`, one campaign | `campaignCreatorMediaId` |
| [Campaign creators](https://api-docs.usegrade.com/syncing/creators.md) | `campaigns:read`, one campaign | `id` (campaign creator record) |
| [Contract versions](https://api-docs.usegrade.com/syncing/contracts.md) | `campaigns:read`, one campaign | `id` (contract history version) |
| [Media eligibility scores](https://api-docs.usegrade.com/syncing/eligibility.md) | `campaigns:read`, one campaign | `id` (campaign media record) |
| [Payouts](https://api-docs.usegrade.com/syncing/payouts.md) | `payouts:read`, your agency | `id` (stable payout sync identity) |
| [Transactions](https://api-docs.usegrade.com/syncing/transactions.md) | `payouts:read`, your agency | `id` (transaction) |
| [Tracked media](https://api-docs.usegrade.com/syncing/tracked-media.md) | `analytics:read`, your agency | `id` (tracked media record) |

Every request requires `X-Application-Id` and `X-Api-Key`. Keep a separate local
collection and cursor for each agency, endpoint, campaign, and supported filter
set. Existing list endpoints keep their behavior. Each new feed has an explicit
record format documented on its page; it is not a drop-in replacement for list
responses containing previews, aggregates, or display conversions.

## Apply a page and save its cursor together

1. Start without `cursor`, using an empty local collection for this sync scope.
2. Replace or insert each `data` record using its identity from the table above.
3. Delete each `removedIds` entry. Unknown IDs are harmless.
4. Commit `nextCursor` in the **same local transaction** as those changes.
5. When `hasMore` is `true`, immediately request another page with that cursor.
6. When `hasMore` is `false`, keep the cursor for your next poll.

Save the cursor from empty pages too. Continue through pages containing only
removals. Use one worker at a time for each local copy; applying an older response
after a newer one could overwrite newer state.

> [!NOTE]
>
> Cursors do not expire by age. Pausing an integration, restarting it, changing the
> page size, or rotating its API key does not require a full reload. A cursor stays
> bound to its feed and agency/campaign scope, and every request still checks access.

## Shared request and response

The six new feeds accept only `cursor` and `limit`. Campaign media additionally
supports its documented initial filters. Unknown parameters are rejected so a
misspelled filter cannot silently broaden a sync.

| Parameter | Behavior |
| - | - |
| `cursor` | Opaque `nextCursor` returned by this feed. Omit only to create a new local copy. |
| `limit` | Maximum IDs processed per page, including removals. 1–100, default 100. You can change it while resuming. |

Pages target 1 MiB of record data. A single larger record is returned intact;
page length can be smaller than `limit`.

```json theme={null}
{
  "success": true,
  "data": [{ "id": "record_123", "otherFields": "complete record" }],
  "removedIds": ["record_456"],
  "hasMore": false,
  "nextCursor": "opaque-cursor"
}
```

`data` contains **complete replacements**, not partial patches. `removedIds`
means the record no longer belongs in this local view: for example, it was
deleted, moved to another scope, or became unavailable. Restored records return
as upserts. Campaign media also removes records that leave its saved filters.

## Concurrent changes and retries

Each page reads a consistent current state. A multi-page run is not a frozen
export: edits during paging appear in a later page or the next poll. Transactions
that started before the checkpoint and committed later are included too.

Delivery is at least once. Repeated records and removals are safe when applied
idempotently. Multiple edits can become one replacement containing the latest
state; these feeds are not audit logs of intermediate edits.

On an API error or failed local transaction, retry the last committed cursor.
Keep your local data. Do not automatically restart the sync on an error.

* `400`: fix malformed parameters or a damaged/wrong-scope cursor. There is no routine cursor-expiry error.
* `401` / `403`: restore valid credentials or the required scope, then resume.
* `404`: the requested campaign is unavailable to your agency. Do not reuse its cursor for a different campaign.
* `429` / `5xx`: back off and retry the same saved cursor; honor `Retry-After` when supplied.

## Client example

The repository demo exports `sync` from `sdk-demo/backend/src/sdkClient.js`.
Supply your own database adapter and transaction implementation:

```javascript theme={null}
const { sync } = require('./src/sdkClient');
const cursor = await store.readCursor(syncScope);

await sync.creators.run({
  campaignId,
  cursor,
  limit: 100,
  applyPage: async ({ data, removedIds, nextCursor }) => {
    await store.transaction(async tx => {
      for (const record of data) await tx.replace(syncScope, record.id, record);
      for (const id of removedIds) await tx.remove(syncScope, id);
      await tx.saveCursor(syncScope, nextCursor);
    });
  }
});
```

Use `sync.contracts`, `sync.eligibility`, `sync.payouts`, `sync.transactions`, or
`sync.trackedMedia` for the other new feeds. Agency feeds omit `campaignId`.
The deployed campaign media feed retains its existing `mediaSync` client and
`campaignCreatorMediaId` identity; see its [client example](https://api-docs.usegrade.com/campaign-media/sync.md#client-example).

## What syncing improves

Later polls transfer and hydrate only records affected by changes. The initial
copy still reads the full scope, in bounded pages. If every record changes,
catching up still requires every record. Page size bounds individual requests;
it does not guarantee less total work when downloading everything.

For date-range charts, campaign totals, and payable previews, use the existing
analytics and payment endpoints. Sync records provide durable facts for a local
copy; they do not continually recompute financial or analytical aggregates.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.