Bản lưu ngày / Snapshot of 2026-10-18 · Xem bản mới nhất / See the latest version
WanduraDocsBeta
BetaThese docs are in beta and change often. Something wrong, unclear or missing? Tell us.
Guides

Creatives sync

Markdown
On this page

TL;DR — Pull approved ad creatives incrementally: pass your last checkpoint as updated_since, follow next_cursor to the end, overwrite records by creative_id, and move the checkpoint only after the whole run succeeded. Re-check a stored creative with GET /creatives/{id}; stop using it after the UTC day of rights.ends_at.

Before you start

  • A key with creatives:export for the job, in WANDURA_API_KEY. The marketing job never needs playback.
  • The record and the envelope: Creatives.

The procedure

  1. First run: GET /creatives?limit=100 without updated_since; while data.next_cursor is not null, call again with cursor=<next_cursor>.
  2. Upsert every record by creative_id. A record can come twice (updated_at is rounded down to the second, and updated_since is strict): overwrite it.
  3. Checkpoint: after the run completed, save the largest updated_at you received.
  4. Later runs: the same, with updated_since=<checkpoint> on every page of the run.
  5. On failure (including 422 4222, a cursor no longer valid): keep the old checkpoint; the next run starts again from it. Nothing is skipped.
  6. Withdrawn creatives are not announced. Before you use a stored creative, or once a day, call GET /creatives/{id}: 404 4040 means stop using it.
  7. Rights: usable until the end of the UTC day rights.ends_at points at (ends_at + 86400).

A few runs an hour is well within the creatives limit.

Code

export async function syncCreatives(store: CreativeStore, o: CallOptions = {}): Promise<boolean> {
  const since = await store.checkpoint();
  let cursor: string | null = null;
  let max = since ?? 0;
  try {
    do {
      const qs = new URLSearchParams({ limit: '100' });
      if (since !== null) qs.set('updated_since', String(since)); // the same value on every page of a run
      if (cursor) qs.set('cursor', cursor);
      const { data }: { data: Envelope } = await call<Envelope>('GET', `/api/delivery/v1/creatives?${qs}`, { policy: BACKGROUND, ...o });
      for (const c of data.data.items) { await store.upsert(c); max = Math.max(max, c.updated_at); }
      cursor = data.data.next_cursor;
    } while (cursor);
  } catch (e) {
    // 422 / code 4222 (cursor no longer valid) or anything else: the next run restarts from the old checkpoint.
    log('creatives_sync_failed', { status: (e as { status?: number }).status, code: (e as { errorCode?: string }).errorCode });
    return false;
  }
  await store.saveCheckpoint(max); // only after the whole run: nothing is ever skipped
  return true;
}

Done when

  • A second run with no changes on Wandura's side returns nothing new.
  • A run interrupted half-way does not move the checkpoint.
  • A creative past its rights day is no longer used.

Updated 2 Oct 2026 · Change history (1)

↑↓ selectEnter openEsc close

Send feedback

Your message goes straight to the Wandura team. It includes the address of this page.

0 / 4000