Skip to main content

GET /sdk/v1/campaigns/:campaignId/media/sync

Use this endpoint when you keep a local copy of campaign media. The first run downloads the current records in pages. Later runs return records affected by changes and IDs to remove. Each data item is a complete replacement record with the same fields as the campaign media list. Requests require X-Application-Id, X-Api-Key, and the campaigns:read scope. The existing /media endpoint continues to return its complete list.

One cursor for pages and future polls

  1. Start without a cursor and with an empty local collection for this campaign.
  2. Upsert every data item by campaignCreatorMediaId; delete every removedIds entry.
  3. Save nextCursor in the same local transaction as those changes.
  4. If hasMore is true, request the next page using cursor=nextCursor.
  5. Otherwise, keep the cursor and use it the next time you poll.
An empty response is normal. Save its cursor too. Continue while hasMore is true even if data is empty: a page can contain only removals.
Cursors have no time-based expiry. You can pause polling, restart your service, rotate an API key, or change the page size and continue with the saved cursor. There is no routine full reload after a retention window.

Query parameters

Filters are saved in the cursor, so you can omit them on subsequent calls. Use a separate local collection and a new cursor when starting a different filtered view. A record that leaves a filtered view appears in removedIds. Pages also target 1 MiB of record data. A single larger record is returned intact. The number of returned records can therefore be lower than limit.

Example request

Omit the cursor argument on the first request.

Response

removedIds covers deletion, tracking retirement, movement to another campaign, creator removal, and records that no longer match your filters. Deleting a local ID you do not have is harmless. Restored records return as complete upserts. The sync also captures metric changes, creator names and email addresses, agency display names, metadata, and account totals affected by child posts. It sends the latest available state, so multiple changes to one record can become one upsert. It is not an audit history of every intermediate edit.

Changes during paging and retries

Pages read current records. Changes during a run can appear in a later page or on the next poll. The cursor preserves changes committed after the run began, including transactions that were already in progress. A run finishes after its bounded set of IDs; ongoing writes do not require a frozen database transaction. Delivery is at least once. A record may repeat during retries, concurrent edits, or catch-up. Apply complete replacements and removals idempotently. Serialize requests and page application for each local copy so an older response cannot overwrite a newer one. Independent integrations may maintain independent cursors. If a request or local transaction fails, retain your data and retry the last saved cursor. Never clear your local collection on an API error. Only start without a cursor when intentionally creating a new local copy. Campaign-wide metrics and asOfTimestamp are not included. For totals from your local copy, avoid counting both an account’s aggregate and its child posts.

Client example

The repository demo exports mediaSync from sdk-demo/backend/src/sdkClient.js. It follows pages sequentially and stops on an API or storage error. Supply your own transaction implementation:
The store methods above represent your database adapter. The API client and page loop are provided; your adapter must make the page and cursor atomic.

Errors

  • 400: malformed parameters, a damaged cursor, a cursor for another campaign or agency, or filters different from the cursor’s saved filters.
  • 401 or 403: invalid credentials or insufficient scope. Fix authorization and retain your saved cursor.
  • 404: the campaign is unavailable to the authenticated agency.
  • 429 or 5xx: follow Retry-After when present; otherwise retry with backoff using the same cursor.
Cursor age is not an error condition. Cursors are continuation state, not credentials; every request still checks your current authorization.