User Guide

Everything FAST World SSAI does, and how to use it.

FAST World SSAI turns the linear channels ChannelForge produces into monetized streams: it finds each ad break, decides what fills it, stitches the ad media straight into the viewer's HLS manifest, and records every decision and playback event. This guide walks through the whole system — the concepts, the console, the creative pipeline, VAST, playback, telemetry, reconciliation and analytics — plus the full API.

1Overview

Server-side ad insertion (SSAI) for ChannelForge-generated linear FAST channels — no client SDK, no VPAID, no second player. The stream the viewer receives already contains the ads.

FAST World SSAI is the ad-insertion tier of a three-part ecosystem. Each part owns one job:

  • ChannelForge (forge.ventunotech.com) builds the linear channels and emits SCTE-35 ad-break markers in the HLS origin.
  • FAST World SSAI (this product, ssai.ventunotech.com) detects those breaks, decides the ads, and stitches them into a per-viewer manifest.
  • FAST World TV (fastworldtv.ventunotech.com) plays the stitched manifest in Shaka Player and fires ad-quartile beacons back.

SSAI is not a ChannelForge component — ChannelForge integrates it across a versioned adapter seam, so the two systems evolve independently.

Control plane vs. data plane

The server is split into two planes with very different jobs, so heavy work never touches playback:

Control plane
Campaigns, creatives, channels, sessions, ad decisioning, analytics, the admin console and every debug API. It can be DB-heavy — it never sits on the playback path.
Data plane
The manifest hot path only: GET /v1/hls/{session}/{channel}/index.m3u8. Minimal dependencies, predictable latency. It reads an already-decided, cached ad pod and rewrites segment references — no DB analytics, transcoding, or third-party calls.
Why it matters: the ad decision for a break is made once per viewer and cached in Redis. Every playlist refresh after that is a pure read, so the stream stays fast no matter how rich the decisioning gets.

2The pipeline, end to end

One flow, six stages, from the channel origin to analytics.

01 · Origin
ChannelForge HLS
02 · Detect
SCTE ad break
03 · Decide
Ad pod
04 · Stitch
Into the manifest
05 · Play
One HLS stream
06 · Measure
Analytics
  1. Detect the avail. SSAI reads the ChannelForge origin, finds each CUE-OUT…CUE-IN break from its SCTE markers, and reads the declared duration — producing a stable, per-channel opportunity.
  2. Decide the pod. Eligible creatives are selected by priority and weight, looped to fill a longer break, with any remainder padded by slate so the break is always complete. The decision is deterministic: same seed and inventory → the same pod every time.
  3. Stitch & serve. The pod's real HLS segments replace the break inside a per-viewer manifest, wrapped in the correct discontinuities and held a little behind the live edge so they reliably reach the player.
  4. Play. The viewer receives a normal continuous HLS stream — program → ad → ad → program — with no separate ad player and no interruption.
  5. Track & measure. Impression and quartile beacons flow back, third-party VAST beacons fire server-side, and everything reconciles per break into the analytics.

3Core concepts

The objects you work with, top to bottom.

Channel
A linear FAST channel from ChannelForge that SSAI inserts ads into. Each channel points at a ChannelForge origin and can carry its own VAST tag and creatives.
Campaign
A group of creatives with scheduling and priority. Campaigns are what compete to fill a break.
Creative
A single ad asset. Sourced by uploading an MP4 or by ingesting a VAST tag, then conditioned to HLS offline before it can air.
Slate
House filler used when a break has unsold time. Prepared the same way as a creative so it stitches seamlessly.
Opportunity
A single detected ad break (avail) on a channel — the "we could show ads here" event, keyed to the SCTE marker.
Decision & Pod
The result of filling an opportunity: an ordered pod of creatives (plus any slate), persisted and cached so playback just reads it.
Session
One viewer's playback. Gets a signed, expiring token the data plane verifies before serving a manifest.
Break (reconciliation)
The unit analytics reconcile on, keyed to ChannelForge's external_break_id so deliveries attach to the right break.

4The admin console

Everything below is available in the browser at /admin — no API calls required.

Sign in with your console credentials. The console gives you:

  • Channels, Campaigns, Creatives, Slates — full create, edit and archive, plus a source-upload form for creatives.
  • VAST config — attach a third-party VAST tag to a channel and let the worker turn it into airable creatives.
  • Analytics — fill, slate and completion rates in real time.
  • Audit — a log of every configuration change.
  • Debug / observability trace — reconstruct any opportunity or session end-to-end: eligible and rejected creatives with reasons, the chosen pod, and the full impression → quartile → complete timeline.
Tip: the debug trace is the fastest way to answer "why did this break show what it did?" — it replays the exact decision, including the creatives that were rejected and why.

5Getting started

From an empty channel to ads on air, in order.

  1. Add the channel. Create a channel pointing at its ChannelForge origin. ChannelForge must be emitting SCTE-35 avails for that channel (it does this natively for managed FAST channels).
  2. Add creatives. Either upload an MP4 on the creative (it's stored and marked uploaded), or ingest a VAST tag on the channel (SSAI fetches it, follows wrapper redirects, and creates per-channel creatives). Either way the offline worker conditions the media to HLS.
  3. Group creatives into a campaign. Set priority and weight — these drive selection when a break opens.
  4. Add a slate. Prepare at least one slate so any unsold time in a break is always filled and the break completes cleanly.
  5. Wait for READY. A creative can only air once conditioning finishes (see the state list in §6). The console shows its status.
  6. Verify. Open the channel in FAST World TV, hit a break, then use the console's debug trace to confirm the decision, the pod and the beacons.

6Creatives & VAST

Two ways in, one offline conditioning pipeline — media is never transcoded on the playback path.

Getting media in

  • Source upload — POST /v1/creatives/{id}/source stores an MP4 and marks the creative uploaded. The worker transcodes it offline.
  • VAST-tag ingestion — POST /v1/channels/{id}/vast-tag fetches a third-party VAST tag, follows wrapper redirects, and creates per-channel creatives the worker conditions the same way.

Offline conditioning

The creative worker prepares every asset the same way, driven by a state machine over the creative's status. It validates → transcodes → segments → stores HLS → marks READY, and prepares slates identically. ffprobe/ffmpeg run here, in the worker, never on the manifest hot path.

A
Validate
B
Transcode
C
Segment (HLS)
D
Store
E
READY
Server-side beacons: for VAST creatives, third-party impression / quartile / complete beacons are fired out-of-band by the server and recorded — the viewer's player never has to. See §9.

7Ad decisioning

Deterministic, always-complete break filling.

When a break opens, /v1/ad-decision reads eligible inventory from Postgres, builds the pod, persists the opportunity/decision/pod, and caches the pod in Redis for the data plane to serve. Selection is:

  • Eligibility-filtered — only creatives that qualify for this channel/break are considered; the debug trace shows the ones that were rejected and why.
  • Priority- and weight-based — higher priority wins; weight distributes within a tier.
  • Seeded / deterministic — the same seed and inventory always produce the same pod, which makes runs reproducible and debuggable.
  • Loop-to-fill — ads loop to fill a longer break (on by default), so a 90s break isn't left with dead air after a 30s ad.

Failure is always safe

Breaks never go dark: unsold time falls back to slate, and a decisioning failure falls back to the origin. Playback survives any advertising failure.

8Playback & stitching

The data-plane hot path — minimal, cached, signed.

A viewer starts a session and receives a signed, expiring playback token. The data plane verifies that token, then serves the per-viewer manifest at GET /v1/hls/{session}/{channel}/index.m3u8. For each break it reads the already-cached pod and rewrites segment references so the ad segments sit inline, wrapped in the right discontinuities. The stitched break is held a little behind the live edge so the ad segments reliably reach the player before playback reaches them.

No forgeable sessions: the token is signed and time-limited — the data plane rejects an unsigned or expired session before serving anything.

9Telemetry & beacons

Two beacon paths: the player's own quartiles, and server-side third-party VAST beacons.

Player telemetry

The player posts playback events to POST /v1/events — quartiles (start / first / mid / third / complete) are mapped to a canonical set and de-duplicated. The endpoint accepts text/plain bodies specifically so navigator.sendBeacon() calls aren't dropped by the browser at unload.

Server-side VAST beacon relay

For creatives that carry third-party tracking, SSAI fires the vendor's impression / quartile / complete beacons server-side, out of band, and records each attempt in beacon_deliveries. Delivery health is visible at GET /v1/beacons/stats. This keeps third-party tracking off the viewer's device and makes it auditable.

10Reconciliation

Every delivery attaches to the right break.

GET /v1/reconciliation/breaks reconciles playback deliveries per break, keyed on ChannelForge's external_break_id. Because the break id is shared across the two systems, SSAI can line up "what we decided" against "what actually played" for each individual break — the foundation for trustworthy fill and completion numbers.

11Analytics

Fill, slate and completion — from real playback, not projections.

GET /v1/analytics (and the console's Analytics screen) report the metrics that matter for a FAST break: fill rate (how much of each break was sold ad vs slate), slate rate, and completion rate (how far into each ad viewers actually watched, from the quartile beacons). Everything is derived from persisted opportunities, decisions and events, reconciled per break.

12Audit log

Who changed what.

Every configuration mutation — create/edit/archive on channels, campaigns, creatives and slates, and VAST config — is written to an audit log, readable at GET /v1/audit and on the console's Audit page. Use it to trace a change back to its source when a break behaves unexpectedly.

13API reference

The live endpoints. Control-plane APIs are under /v1; the data plane serves the manifest hot path.

MethodEndpointWhat it does
POST/v1/ad-decisionDecide and persist the pod for an opportunity; cache it for the data plane.
GET/v1/hls/{session}/{channel}/index.m3u8Data plane. The per-viewer stitched manifest (token-verified).
POST/v1/sessions · GET /v1/sessions/:idStart a playback session / read one.
GETPOST/v1/channels · /v1/channels/:idChannel CRUD.
POST/v1/channels/:id/vast-tagIngest a VAST tag → per-channel creatives (wrappers followed).
GETPOST/v1/campaignsCampaign CRUD.
GETPOST/v1/creatives · /v1/creatives/:idCreative CRUD.
POST/v1/creatives/:id/sourceMultipart MP4 upload → creative marked uploaded.
GETPOST/v1/slatesSlate CRUD.
POST/v1/eventsPlayer telemetry (quartiles; accepts text/plain for sendBeacon).
GET/v1/beacons/statsServer-side VAST beacon-relay health.
GET/v1/reconciliation/breaksPer-break reconciliation on external_break_id.
GET/v1/analyticsFill / slate / completion metrics.
GET/v1/auditConfiguration-change audit log.
GET/v1/debug/opportunity/:id · /v1/debug/session/:idReconstruct a decision / a session end-to-end.
GET/healthLiveness.
This is the current live surface. Request and response shapes are best explored from the console's debug screens, which call these same endpoints.

14ChannelForge integration

A clean, versioned seam — not a shared codebase.

ChannelForge owns the channel and emits the SCTE-35 avail markers in its HLS origin; SSAI reads that origin and does the insertion. The two systems talk across a versioned adapter, and each keeps its own ids — SSAI reconciles back to ChannelForge's external_break_id so the boundary stays clean. Practically, that means you can point SSAI at any ChannelForge channel that emits avails, and you can upgrade either system without breaking the other.

See it live: SSAI is inserting real ads into four live ChannelForge FAST channels right now — open FAST World TV and watch a break, then trace it in the console.