Guides
TL;DR — Your server sweeps every page of
GET /videoswith the app's key, keeps the whole result, and serves links from it untilrefresh_before. Stop only onnext_cursor === null, never on a short page. Retry429/503; if a sweep fails, keep serving the previous list.
Before you start
- One key per app, with
videos:read, bound to that app (Open video). - Which fields to sync on:
id, nevertitle.
The sweep
- Call
GET /videos?limit=200, then again withcursor=<next_cursor>(URL-encoded, unchanged) untilnext_cursorisnull. A short or empty page does not mean the end: videos not ready to play are skipped. - Key rows by
id. Store theorder_code↔idpair once if you join against an order file; never join ontitle(titles repeat). - Take the earliest
refresh_beforeof all pages: earlier pages expire first. - Replace the list your apps see only when the sweep completed. If it failed, keep serving the previous list (its links stay valid until their own expiry) and try again in a minute.
- Sweep again before the earliest
refresh_before.
Code
/** Every page or an exception (429/503/network are retried inside `call`). Never replace your list with a partial sweep. */
export async function sweep(o: CallOptions = {}): Promise<{ rows: OpenVideo[]; refreshAt: Date }> {
const rows = new Map<string, OpenVideo>();
let cursor: string | null = null;
let refreshAt: Date | null = null;
for (let page = 0; page < 10_000; page++) {
const qs: string = `limit=200${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`;
const { data }: { data: Page } = await call<Page>('GET', `/api/delivery/v1/videos?${qs}`, { policy: BACKGROUND, ...o });
for (const r of data.rows) rows.set(r.id, r); // key by `id`, never by title
const rb = new Date(data.refresh_before); // earlier pages expire first: keep the EARLIEST deadline
if (!refreshAt || rb < refreshAt) refreshAt = rb;
cursor = data.next_cursor;
if (cursor === null) return { rows: [...rows.values()], refreshAt }; // a short or empty page is NOT the end
}
throw new Error('open video sweep did not end');
}let current: { rows: OpenVideo[]; refreshAt: Date } | null = null;
/** Keeps `current` fresh. On failure keeps serving the previous list and tries again in a minute. */
export async function refreshLoop(o: CallOptions = {}): Promise<void> {
let delay = 60_000;
try {
current = await sweep(o);
delay = Math.max(60_000, current.refreshAt.getTime() - Date.now());
} catch {
// previous links stay valid until their own expiry
}
setTimeout(() => void refreshLoop(o), delay).unref();
}
/** What your app receives: links from your server's cache. Apps never store them. */
export const currentRows = (): OpenVideo[] => current?.rows ?? [];Retries happen inside call, under the canonical retry policy. The docs CI runs this code against a fake API whose first page answers 429, to prove the sweep does not end empty.
Fields
Every field of a row: List open videos. The ones that trip people:
mp4.p720can benull: there is no 720p version. Do not substitutep480.order_codeis always present and may benull.difficultyandkcal_estare currently alwaysnull.
Done when
- A sweep whose first page answers
429still ends with the full list. - Apps get links from your server, never from storage.
- The list is refreshed before the earliest
refresh_before.
Related
Updated 5 Oct 2026 · Change history (1)