Slates & ads

Configure slates and fallbacks, ad breaks and inventory, in-house campaigns, interstitials and rescue playlists.

This guide covers everything that fills the time around programmes on a FAST channel: what plays when something goes wrong (slates and fallbacks), where ad breaks fall, which ads fill them (campaigns), and the promos, bumpers and filler clips that fill junctions (interstitials).

What Ingenee does and doesn't do

  • Ingenee plans, your playout plays. It stores slate and fallback references, plans the breaks and describes the cue points. It emits no SCTE-35 packets and never calls or validates a VAST tag; your playout or SSAI layer does the inserting.
  • Nothing reaches air unreviewed. A required slate with no playout asset ID blocks the channel's publish gate, and campaign spots are placed into a copy of the published schedule that goes through the gate again.
  • Viewers read, programmers and admins write. A viewer key gets 403 FORBIDDEN on slate, ad-inventory, ad-source and campaign changes.

Slates and fallbacks

A channel has four scenarios. For each you choose a slate (a Library title, a playout asset ID, or both) and a fallback rule.

scenarioMeaning
unfilledThe part of an ad break no campaign fills
missingA scheduled programme or ad file is missing or unplayable
failurePlayback of an asset fails while on air
gapTime with no scheduled event
fallbackRuleMeaning
fill_breakFill the rest of the break
until_next_programmePlay until the next programme
switch_to_slateSwitch to the slate
loop_until_next_eventLoop until the next event
skipSkip and carry on
curl https://dev.app.ingenee.ai/api/v1/channels/f27a91c3-4b6d-4e08-a5d2-9c1e3b7f0a64/slates \
  -H "Authorization: Bearer $INGENEE_API_KEY"

GET always returns all four scenarios, saved or not:

200 OK (trimmed)
{
  "rows": [
    { "scenario": "unfilled", "slateAssetId": null, "playoutAssetRef": "SLATE-BE-RIGHT-BACK", "fallbackRule": "fill_break",
      "fallbackAfterSec": null, "required": false, "slateTitle": null, "ref": "SLATE-BE-RIGHT-BACK", "refSource": "playout", "state": "mapped" },
    { "scenario": "gap", "slateAssetId": null, "playoutAssetRef": null, "fallbackRule": "switch_to_slate",
      "fallbackAfterSec": 5, "required": true, "slateTitle": null, "ref": null, "refSource": null, "state": "blocker" }
  ],
  "chip": { "kind": "blocks", "count": 1, "label": "1 blocks export" }
}

PUT saves the scenarios you send (1 to 4, one row per scenario) and returns the same view. Unknown keys are rejected.

curl -X PUT https://dev.app.ingenee.ai/api/v1/channels/f27a91c3-4b6d-4e08-a5d2-9c1e3b7f0a64/slates \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "rows": [
    { "scenario": "unfilled", "slateAssetId": null, "playoutAssetRef": "SLATE-BE-RIGHT-BACK", "fallbackRule": "fill_break", "fallbackAfterSec": null, "required": false },
    { "scenario": "gap", "slateAssetId": "5e7f9a1b-2c3d-4e5f-8a6b-7c8d9e0f1a2b", "playoutAssetRef": "SLATE-OFF-AIR", "fallbackRule": "switch_to_slate", "fallbackAfterSec": 5, "required": true }
  ] }'
  • slateAssetId is a title in your Library (or null); another organization's or a trashed title returns 422.
  • playoutAssetRef is the ID your playout system uses for the slate (up to 200 characters). It is what exports carry.
  • fallbackAfterSec is 0 to 3600, or null.
  • state is mapped (a playout ID resolves), blocker (required but no ID), unmapped (optional, a Library slate chosen but no ID) or unset. A blocker row blocks publishing with a slate_unmapped_required message.

Saved slates appear in the channel's XMLTV as ingenee-slate elements and in schedule.json as slates.

Ads on a channel

A channel's ad rules live in dna.adConfig, set with PATCH /v1/channels/{id}. You can send only the keys you want to change; they are merged over the stored config.

curl -X PATCH https://dev.app.ingenee.ai/api/v1/channels/f27a91c3-4b6d-4e08-a5d2-9c1e3b7f0a64 \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "dna": { "adConfig": {
    "enabled": true,
    "accounting": "included",
    "source": "campaigns",
    "avgViewersPerBreak": 1200,
    "spotSec": 30,
    "cueMethod": "scte35",
    "preRoll": { "on": true, "pods": 1 },
    "midRoll": { "on": true, "pods": 2, "everyMin": 15, "minTailMin": 5, "pattern": "cadence" },
    "scte": { "spliceCommand": "time_signal", "segmentationType": "provider_po", "cuePreRollMs": 4000, "autoReturn": true }
  } } }'
KeyValues and meaning
enabledfalse means no breaks: the slot is the content
accountingincluded reserves the ad time inside the slot. playout leaves ad time to your playout system, so breaks are exported as zero-length cue points and no time is reserved
sourceWho fills a break. none: exported as an avail. campaigns: your own campaigns' videos. vast: an outside ad server fills it; the break goes out as slate
vastTagUrlFor vast: an https:// URL up to 2,048 characters. Stored and passed on, never called
avgViewersPerBreakWhole number, 0 to 100,000,000. The audience campaign forecasts multiply by. null means no views figure is shown
inventoryModeschedule (default) uses the breaks of the schedule Ingenee builds. estimate uses the manual grid below
fillModefixed (default): each ad plays at most the spot length. full: each plays its whole measured length and breaks are packed by seconds
spotSecSpot length pods are sized from, 5 to 120
preRoll, midRoll, postRoll{ on, pods }, pods being spots per pod (0 to 10). midRoll also takes everyMin, minTailMin, firstBreakAfterMin, maxBreaks, pattern (cadence, fixed, natural) and fixedPositionsMin
podPatternExplicit spot lengths in every pod, such as [30, 30, 15]
cueMethodscte35, scte104, manifest or none
sctespliceCommand (splice_insert, time_signal), segmentationType, cuePreRollMs, autoReturn
interProgramSec, minProgramMinSeconds between programmes; programmes shorter than this get no pods
daypartCapsAd minutes per clock hour, for overnight and daytime: { "on": true, "capMinPerHr": 12 }
rescueWhat playout does when a break can't be filled: onFail is house_ads (the channel's own house ads, optionally houseAdIds), extend or black, with triggerSec and repeatProtectHrs

Refusals come back as 422 VALIDATION with a plain sentence, for example "Ad time on this channel is managed by playout, so it has no break time for campaigns. Set Ad time to "Included in the schedule" first." Changing source, the VAST tag or the fill settings needs a programmer or admin key, and is recorded in the audit history. For auto-publish, a change to the break rules (accounting, pods, cues, caps) counts as a settings change; source, vastTagUrl, avgViewersPerBreak, inventoryMode and the fill settings don't.

Ad breaks

GET /v1/channels/{id}/ad-breaks reads the breaks of the published schedule, the same ones the exports carry. Ask for one channel-local day, or a window of hours from now:

curl "https://dev.app.ingenee.ai/api/v1/channels/f27a91c3-4b6d-4e08-a5d2-9c1e3b7f0a64/ad-breaks?day=2026-11-02" \
  -H "Authorization: Bearer $INGENEE_API_KEY"

day is YYYY-MM-DD on the channel's clock; hours is 1 to 168 (default 24). Sending both returns 422. The response has window, days, tzLabel, counts, breaks and warnings. Each break lists its start and the ads placed in it. Nothing here builds or reorders anything.

Ad inventory (for channels scheduled elsewhere)

When a channel's schedule is built outside Ingenee, give campaigns something to forecast against: a weekly grid of ad slots per hour. It is read only while the channel's inventoryMode is estimate.

curl -X PUT https://dev.app.ingenee.ai/api/v1/channels/f27a91c3-4b6d-4e08-a5d2-9c1e3b7f0a64/ad-inventory \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "windows": [ { "weekday": 5, "startMin": 1200, "endMin": 1380, "slotsPerHour": 4 } ] }'
200 OK
{ "mode": "estimate", "windows": [ { "weekday": 5, "startMin": 1200, "endMin": 1380, "slotsPerHour": 4 } ], "weeklySlots": 12, "spotSec": 30 }

weekday is 0 (Sunday) to 6. startMin and endMin are minutes of the day, and a window can't cross midnight (split it in two). slotsPerHour has at most two decimals and must fit in an hour at your spotSec. A PUT replaces the whole grid (up to 200 windows); overlaps are refused with 422. GET returns the same view.

Campaigns

A campaign is a named group of your own Library videos, in priority order, that plays inside a channel's ad breaks instead of the slate. It needs a channel with ads enabled, accounting of included and source of campaigns; otherwise saving returns 422 naming the channel and the fix.

curl -X POST https://dev.app.ingenee.ai/api/v1/campaigns \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{
    "name": "Autumn house promo",
    "advertiser": "Ingenee",
    "startDate": "2026-11-01",
    "endDate": "2026-11-30",
    "openEnded": false,
    "channelIds": ["f27a91c3-4b6d-4e08-a5d2-9c1e3b7f0a64"],
    "maxPerBreak": 1,
    "items": [
      { "assetId": "5e7f9a1b-2c3d-4e5f-8a6b-7c8d9e0f1a2b", "targetCount": 4, "targetPer": "day", "cpm": 6.5 }
    ]
  }'
201 Created (trimmed)
{ "id": "0f1e2d3c-4b5a-4968-8776-655443322110" }

The response also has an applied field reporting what happened when Ingenee applied the change to the channel schedules.

  • Dates. A start date is required. Pick an endDate, or set openEnded: true (an end date of null without it is refused).
  • Items. Up to 50 videos, each from your Library with a measured length. targetCount (1 to 999) plays per targetPer: day, week, airing (inside each airing of the programme you name in programmeAssetId) or total (needs an end date). Optional per item: daypart, playMode (channel, full, cut with cutSec), positionInBreak (any, first, last), adName, guaranteed.
  • Money. moneyModel is cpm (the default; every item needs a cpm from 0 to 10,000) or contract (then send contractValue). currency defaults to USD. Results are reported as estimated value, not revenue.
  • Daypart. daypart limits weekdays and local-time windows.
  • Status is derived from the dates: running, scheduled or ended.
  • A change replaces the whole campaign: PATCH /v1/campaigns/{id} takes the same body as create (name, dates, channels, items).

GET /v1/campaigns returns { campaigns, channels, counts } (filter with ?status=running|scheduled|ended). DELETE /v1/campaigns/{id} removes one. Saving or deleting a campaign applies the change to the channels' schedules.

Approve, apply and check

  • POST /v1/campaigns/{id}/approve locks the campaign's spots that are already in the schedule on air and still to come. Re-adding to the schedule or changing the allocation rule will not move them. Changing its videos, spot counts, days and times, dates or channels clears the approval. Approving publishes nothing. It returns { id, approvedAt }.
  • POST /v1/channels/{id}/apply-campaigns puts the campaigns' videos into the breaks of the schedule already on air, without changing any programme. Ingenee copies the published version exactly, places the spots in the copy, and publishes it through the gate. It returns 409 with a reason if the channel has no published schedule, no breaks, ads off, or is in estimate mode.
  • GET /v1/campaigns/{id}/airs?channel={channelId}&day=2026-11-02 lists the published breaks on a day that carry the campaign's ads (channel is required; day must fall in the next seven days).
  • GET /v1/campaigns/forecast forecasts delivery for all campaigns. Add ?campaign={id} to focus on one, and ?rule=priority|value|share to see the result under another allocation rule without changing it.

Interstitials

Interstitials are the promos, channel IDs, bumpers, trailers, filler clips, loops and house ads that fill junctions. By default a channel uses only its own interstitials, never another channel's.

curl -X POST https://dev.app.ingenee.ai/api/v1/interstitials \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "title": "Ingenee Classic Cinema ID", "category": "channel_id", "duration": "00:00:10", "rating": "G",
        "channelId": "f27a91c3-4b6d-4e08-a5d2-9c1e3b7f0a64", "houseNumber": "ICC-ID-01", "mediaPath": "ids/icc-id-01.mxf" }'

200 with { "id": "…" }. title and a duration are required.

FieldNotes
duration or durationSecWhole seconds (30) or a timecode (00:00:30 or 00:00:30:00). 01:30 is refused as ambiguous
categorypromo, channel_id, bumper, trailer, filler, loop or house_ad. Blank means unclassified; an unknown label is refused, never guessed
ratingG, PG, PG-13, R, NC-17, TV-Y, TV-Y7, TV-G, TV-PG, TV-14, TV-MA or 18+. Compact forms like pg13 are accepted
houseNumberYour reference. Unique within your organization (409 CONFLICT if taken)
channelIdAssign to a channel; leave out to keep it organization-wide
mediaPath, somThe media file reference, and the start of media as HH:MM:SS:FF. Media can arrive after the record; the list marks it mediaPending
validFrom, validToYYYY-MM-DD availability window
spotType"", ZS or BLK
description, notesFree text

GET /v1/interstitials returns { interstitials: [...] }, each with mediaPending, usedCount and its channel rule. Filter with channel (a channel id, or none), category, rating, q, validOn and media (pending or ready). PATCH /v1/interstitials/{id} edits any field, and "archived": true or false archives or restores one. A malformed field returns 400 VALIDATION_FAILED.

Rescue playlists

A rescue playlist is a named list of Library titles with a total runtime. What a channel does when an ad break can't be filled is set separately, in adConfig.rescue above, and uses the channel's house ads.

curl -X POST https://dev.app.ingenee.ai/api/v1/rescue-playlists \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "name": "Evergreen shorts", "description": "Safe 5-minute fillers", "assetIds": ["5e7f9a1b-2c3d-4e5f-8a6b-7c8d9e0f1a2b"] }'

201 with { "id": "…" }. GET /v1/rescue-playlists returns { playlists: [{ id, name, description, itemCount, runtimeMin, createdAt }] }.

Not yet available

  • Changing the organization-wide campaign allocation rule, and reordering campaigns, through the API.
  • Un-approving a campaign, and editing or deleting a rescue playlist.
  • Creating interstitials in bulk or from CSV (create them one at a time), and setting a channel's per-interstitial usage rules.

Endpoints

OperationMethod + pathScope
Get slates and fallbacksGET /v1/channels/{id}/slateschannels:read
Select slates and fallbacksPUT /v1/channels/{id}/slateschannels:write
Update a channel (ad configuration)PATCH /v1/channels/{id}channels:write
Ad breaks on a channelGET /v1/channels/{id}/ad-breaksads:read
Ad inventoryGET /v1/channels/{id}/ad-inventoryads:read
Set ad inventoryPUT /v1/channels/{id}/ad-inventoryads:write
Place campaign spots into breaksPOST /v1/channels/{id}/apply-campaignsads:write
List campaignsGET /v1/campaignsads:read
Create a campaignPOST /v1/campaignsads:write
Get a campaignGET /v1/campaigns/{id}ads:read
Update a campaignPATCH /v1/campaigns/{id}ads:write
Delete a campaignDELETE /v1/campaigns/{id}ads:write
Approve a campaign's spotsPOST /v1/campaigns/{id}/approveads:write
Where a campaign airedGET /v1/campaigns/{id}/airsads:read
Forecast campaign deliveryGET /v1/campaigns/forecastads:read
List interstitialsGET /v1/interstitialsads:read
Add an interstitialPOST /v1/interstitialsads:write
Update an interstitialPATCH /v1/interstitials/{id}ads:write
List rescue playlistsGET /v1/rescue-playlistschannels:read
Create a rescue playlistPOST /v1/rescue-playlistschannels:write

On this page