Guides
TL;DR — Pull approved ad creatives incrementally: pass your last checkpoint as
updated_since, follownext_cursorto the end, overwrite records bycreative_id, and move the checkpoint only after the whole run succeeded. Re-check a stored creative withGET /creatives/{id}; stop using it after the UTC day ofrights.ends_at.
Before you start
- A key with
creatives:exportfor the job, inWANDURA_API_KEY. The marketing job never needsplayback. - The record and the envelope: Creatives.
The procedure
- First run:
GET /creatives?limit=100withoutupdated_since; whiledata.next_cursoris notnull, call again withcursor=<next_cursor>. - Upsert every record by
creative_id. A record can come twice (updated_atis rounded down to the second, andupdated_sinceis strict): overwrite it. - Checkpoint: after the run completed, save the largest
updated_atyou received. - Later runs: the same, with
updated_since=<checkpoint>on every page of the run. - On failure (including
4224222, a cursor no longer valid): keep the old checkpoint; the next run starts again from it. Nothing is skipped. - Withdrawn creatives are not announced. Before you use a stored creative, or once a day, call
GET /creatives/{id}:4044040means stop using it. - Rights: usable until the end of the UTC day
rights.ends_atpoints 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.
Related
Updated 5 Oct 2026 · Change history (1)