Skip to content
Guide

Schedule a campaign from your own system

Create program entries for a campaign day by day from your marketing calendar, and remove them again by their label.

3 min readUpdated Oct 30, 2026As Markdown
For
Developers who connect a marketing calendar, a shop system, or a campaign tool
Prerequisite
a key ak_… of the class Playout control, a program that your displays play, and a playlist or scene with the campaign
Where
command line
On this page

By the end, one command puts a campaign into a program for a date range, during the hours you choose, and a second command takes it out again. The dates can come straight from your marketing calendar or shop system.

Why one entry per day

A program entry of the type once runs from startsAt to endsAt without a break. A campaign from 2 to 14 November as a single entry would therefore run through every night. It would also push aside the nightly broadcast pause, because a one-off entry beats every recurring one. The script instead creates one one-off entry per day, from opening to closing, and gives them all the same label so that it can find and remove them later.

Rule Effect on the campaign
A one-off entry beats a recurring one The campaign wins over the weekly plan during its hours.
The shorter window beats the longer one A short promotion inside the campaign hours still wins over it.
If both are equal, the entry created last wins Two campaigns with the same hours: the newer one plays.
Outside every entry, the base program runs The displays return to normal after closing time.

Steps

  1. Find the IDs. Open the program and the playlist in the portal: the addresses end with their IDs sch_… and pll_….

  2. Save the script:

    TypeScript
    // campaign.ts// node campaign.ts add <sch_…> <label> <pll_…> <from> <to> <opens> <closes>// node campaign.ts remove <sch_…> <label>const API = process.env.SZENERA_API;const KEY = process.env.SZENERA_API_KEY; // class Playout controlconst TIME_ZONE = 'Europe/Berlin'; async function call(method: string, path: string, body?: unknown, idempotencyKey?: string) {  const headers: Record<string, string> = {    Authorization: `Bearer ${KEY}`,    'Content-Type': 'application/json',  };  if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;  const res = await fetch(`${API}${path}`, {    method,    headers,    body: body === undefined ? undefined : JSON.stringify(body),  });  if (!res.ok) throw new Error(`${method} ${path}: ${res.status} ${await res.text()}`);  return res.json();} // A local time on that day with the offset of the time zone, +01:00 in winter and +02:00 in summerfunction at(day: string, time: string): string {  const zone = new Intl.DateTimeFormat('en', { timeZone: TIME_ZONE, timeZoneName: 'longOffset' })    .formatToParts(new Date(`${day}T12:00:00Z`))    .find((part) => part.type === 'timeZoneName')?.value ?? 'GMT';  return `${day}T${time}:00${zone === 'GMT' ? '+00:00' : zone.slice(3)}`;} function nextDay(day: string): string {  const date = new Date(`${day}T00:00:00Z`);  date.setUTCDate(date.getUTCDate() + 1);  return date.toISOString().slice(0, 10);} const [action, scheduleId, label, playlistId, from, to, opens, closes] = process.argv.slice(2); if (action === 'add') {  for (let day = from; day <= to; day = nextDay(day)) {    await call(      'POST',      `/v1/schedules/${scheduleId}/items`,      {        label,        content: { kind: 'playlist', playlistId },        recurrence: { kind: 'once', startsAt: at(day, opens), endsAt: at(day, closes) },      },      `campaign-${encodeURIComponent(label)}-${day}`,    );    console.log(`added ${day} ${opens} to ${closes}`);  }} if (action === 'remove') {  const { schedule } = await call('GET', `/v1/schedules/${scheduleId}`);  for (const item of schedule.items.filter((entry) => entry.label === label)) {    await call('DELETE', `/v1/schedules/${scheduleId}/items/${item.id}`);    console.log(`removed ${item.recurrence.startsAt}`);  }}
  3. Add the campaign:

    Terminal
    node campaign.ts add sch_01KWVAFRM0ZETPAN0000000001 "Herbstaktion" \  pll_01KWV91ZS0PRGRAMM000000001 2026-11-02 2026-11-14 08:00 20:00
    added 2026-11-02 08:00 to 20:00added 2026-11-03 08:00 to 20:00…

    The idempotency key is built from label and day. If the script stops halfway, run it again within 24 hours: days that are already there aren't created twice.

  4. Remove it when the campaign is canceled or over:

    Terminal
    node campaign.ts remove sch_01KWVAFRM0ZETPAN0000000001 "Herbstaktion"

Entries that lie in the past don't play, but they stay in the program until someone removes them. A program holds at most 200 entries, so remove a finished campaign by its label.

Check the result

  • The weekly plan of the program in the portal shows one block per day with the label of the campaign, see Read the weekly plan.
  • During the hours of the campaign, the displays with this program play the playlist.
  • After remove, no entry with this label is left in GET /v1/schedules/{id}.

If something goes wrong

Symptom Meaning Solution
422 Schedule.InvalidRecurrence The times don't fit, for example the end lies before the start. Give opening and closing time as 08:00 and 20:00.
422 Schedule.ItemLimitReached The program already holds 200 entries. Remove finished campaigns by their label.
422 Schedule.ContentNotFound The playlist ID doesn't exist. Check the ID in the portal address.
422 Validation.IdempotencyKeyReused The same label and day were sent with other hours within 24 hours. Remove the campaign, then add it again under a new label.
The campaign doesn't appear on a display Another program runs there, an override is active, or a shorter entry wins. See How does a display decide what to play?.
3 min readUpdated Oct 30, 2026As Markdown

Was this helpful?