Dokumentasjon for innholdsleverandører · versjon 1.1

Enkel-TV
API for
innholdsleverandører

Hvordan en innholdsleverandør publiserer spillelister med YouTube, NRK og lysbilder til kanalene på TV-ene, hvordan TV-en spiller dem av som vanlig lineær TV, og hvilken historikk og profilinformasjon leverandøren kan hente tilbake for å gjøre innholdet bedre. Dokumentet beskriver API-et slik det er bygget.

Versjon
1.1 (formatene er versjon 1)
Dato
5. oktober 2026
API
https://<server>/api/publish med API-nøkkel (Authorization: Bearer etv_…)
Formater
enkeltv-lineup/1 · enkeltv-channel/1 · enkeltv-playlist/1 · enkeltv-slideshow/1 (JSON eller zip-pakke med manifest.json) · enkeltv-event/1 · enkeltv-profile/1
På nett
https://<server>/docs/ og som PDF på https://<server>/docs/enkeltv-api.pdf
Leses av
Innholdsleverandører med API-nøkkel
Status
Bygget og testet. Det som bevisst er utsatt, står i kapittel 19.

Innhold

Del A · Kom i gang
1Prinsipper og beslutninger
2Hurtigstart: publiser dagens spilleliste hjemmefra
3Tilgang, rettigheter og feil
Del B · Innhold
4Datamodellen
5Spillelister og de tre innslagstypene
6Lysbildeserier
7Lysbildepakker (zip)
8Mediefiler
Del C · Kanaler og avspilling
9Kanalbiblioteket
10Kanaloppsett per profil
 
11Lineær TV: tidslinjen
12Slik spiller TV-en av
Del D · Innsikt og personvern
13Hendelser og historikk fra TV-en
14Profilen: bio, notater og bilder
15Personvern
Del E · Referanse
16Endepunkter for leverandører
17Eksempel: en personlig kanal for Ingrid
18Stabilitet og versjonering
19Senere

Finn fram

Jeg vil …KallLes
publisere dagens spilleliste til en kanalPUT /playlists/{id}, så POST /channels/{id}/playlistskap. 2, 5 og 9
legge en kanal på en bestemt plass på én TVPATCH /profiles/{ref}/lineupkap. 2 og 10
vite hvordan YouTube-, NRK- og lysbildeinnslag ser ut–kap. 5 (oversikten over de tre typene), 6 og 7
se hva som går og hva som skal gåGET /profiles/{ref}/now, GET /channels/{id}/schedulekap. 11 og 16
hente historikken: kanalbytter og programmerGET /profiles/{ref}/history, GET /eventskap. 13 og 16
hente bio og informasjon om brukerenGET /profiles/{ref}/bio, /notes, /photoskap. 14
forstå en feilmelding–kap. 3 (alle feilkoder i én tabell)

Alle adresser i tabellen er relative til https://<server>/api/publish. Denne dokumentasjonen ligger også på serveren: https://<server>/docs/ og som PDF på https://<server>/docs/enkeltv-api.pdf.

Del A · Kom i gang

1Prinsipper og beslutninger

Enkel-TV-serveren er en frittstående applikasjon som skal endres sjelden, og som ikke vet noe om hva som er godt innhold. Den har fire ansvar:

  1. Samtaler – videosamtaler mellom pårørende og TV-en.
  2. Kanaler – hvilke kanaler hver profil har, og hva som går på dem akkurat nå.
  3. Innhold – ta imot, sjekke og lagre kanaler, spillelister og lysbildeserier fra leverandører.
  4. Innsikt – ta imot hendelser fra TV-ene og bio, notater og bilder fra personalet og familien, og gi dem videre til leverandøren som har lov.

Alt som handler om å velge innhold – intervjuer med de pårørende, kuratering, personalisering, KI og rettighetsavtaler – skjer hos innholdsleverandørene, utenfor serveren.

Innholdsleverandør intervjuer pårørende, velger innhold, lager kanaler Enkel-TV-server samtaler · kanaloppsett bibliotek · mediefiler hendelser · profiler TV-en (profilen) kanal 0–99: YouTube, lysbilder og NRK inn: kanaler, spillelister, lysbildeserier, kanaloppsett ut (leverandøren spør): hendelser, historikk, bio, notater, bilder kanaler og dagens spillelister hendelser Familien mobilappen: notater, bilder, samtykke Personalet i Enkel-TV admin: bio, nøkler, kanaloppsett

Profilen, ikke boksen. Alt i dette dokumentet hører til profilen – TV-brukeren som logger inn med brukernavn og boks-ID. Kanaloppsett, bio, notater, bilder, samtykke og hendelser følger personen, ikke den fysiske boksen. Boks-ID-en er bare passordet: går en boks i stykker eller må byttes, lager personalet en ny boks-ID i admin, og den nye boksen får de samme kanalene, den samme bioen og den samme historikken. Leverandører ser profilen bare som en pseudonym referanse, ref (f.eks. p_7Kq2mPx9Qa), aldri navn, brukernavn eller boks-ID.

Beslutninger

Disse valgene ligger til grunn for det som er bygget, og for resten av dokumentet.

OmrådeBeslutning
Profilen eier altTV-brukeren (profilen) eier kanaloppsett, bio, notater, bilder, samtykke og hendelser – ikke den fysiske boksen. Boks-ID-en er passordet, og personalet kan lage en ny; en ny boks beholder alt. Leverandører ser bare pseudonymet ref (p_…).
Én leverandør per profilHver profil har høyst én innholdsleverandør, valgt av toppledelsen i admin. Bare den leverandøren kan endre profilens kanaloppsett, lage personlige kanaler, spillelister og lysbildeserier for den, og lese dens hendelser og profildata. Unntaket er Enkel-TVs egne nøkler med events:all: de kan lese hendelser, historikk, kanaloppsett og «nå» for alle profiler, men ikke endre noe eller lese bio, notater og bilder.
Felles eller personligKanaler, spillelister og lysbildeserier er enten felles (alle leverandører kan lese og bruke dem) eller personlige for én profil – f.eks. en lysbildeserie med private familiebilder.
Kanaler per profilHver profil har plass 1–99, standard 10. Pil opp/ned går 0 → 1 → … → siste → 0, og 0 er alltid klokkeskjermen. Plasser uten innhold viser standardoppsettet, så reserveprogrammet, og til sist «Kommer snart».
Blandede spillelisterEn spilleliste kan veksle mellom YouTube, lysbilder og NRK. Innslagstypen (kind) bestemmer avspilleren.
NRKLeverandøren oppgir bare NRK sin program-ID. TV-en henter avspillingsadressen hos NRK rett før avspilling, fordi adressene er kortlivede. NRK1–3 direkte er en egen kanaltype ("type": "live"), ikke innslag i en spilleliste.
SendedøgnetStarter kl. 06:00 norsk tid. Dagens spilleliste begynner med første innslag kl. 06:00 og går i løkke til kl. 06:00 neste morgen.
SamtalerEn samtale pauser all avspilling – lysbilder, YouTube og NRK. Etterpå fortsetter TV-en der den stoppet. Bytter man kanal, er man tilbake på direkte.
LysbilderFritt oppsett med lag – bilde, video, tekst og form – i prosent av skjermen, med tider for tekst og lyd, og rolig zoom. Hele serien kan sendes som én zip-pakke. Høyst 200 MB per serie, ingen lagringskvote per leverandør.
API-nøklerLages av toppledelsen i Enkel-TV. Hver nøkkel hører til én leverandør og har rettigheter som krysses av.
Ingen spørsmålsbankLeverandøren intervjuer de pårørende utenfor systemet. Personalet skriver bioen i admin. De pårørende sender notater og bilder fra mobilappen når noe har skjedd.
Ingen lokal KIFor hvert notat og bilde velger den som sender: leverandøren (også deres KI) eller bare personalet.
Alle hendelser lagresServeren lagrer hver hendelse og sletter eller summerer ingenting.
Ingen webhooksServeren varsler ingen. Leverandører spør selv.
Ingen mellomlagringBoksen lagrer ikke innhold og må være på nett.
Stabilt APIAPI-et for leverandører er lite og endres sjelden. Feltene og feilkodene som står her, endres ikke; nye, valgfrie felt kan komme (kap. 18).

2Hurtigstart: publiser dagens spilleliste hjemmefra

Alt en leverandør gjør, går med vanlige HTTPS-kall til https://<server>/api/publish med API-nøkkelen. Her er hele gangen med curl: sjekk nøkkelen, finn profilene, lagre en spilleliste med alle tre innslagstypene, publiser den til en kanal for i dag eller i morgen, legg kanalen på en plass hos én profil, sjekk resultatet, og hent historikk og bio. Bytt ut <server>, etv_… og p_7Kq2mPx9Qa med deres egne verdier.

Hver dag: steg 4 og 6 – lagre dagens spilleliste og publiser den til kanalen – og gjerne steg 8 for å se at den ligger der. Én gang: steg 1–3, 5 og 7. Kommer det ingen ny spilleliste, går den forrige videre.

Eksemplene er skrevet for bash (Mac, Linux, eller Git Bash/WSL på Windows). Lange JSON-dokumenter er lettest å lagre i en fil og sende med --data-binary @fil.json i stedet for -d '…'.

1–2. Sjekk nøkkelen og finn profilene

ETV=https://<server>/api/publish
AUTH="Authorization: Bearer etv_…"
JSON="Content-Type: application/json"

# 1. Nøkkelen: leverandør, rettigheter, formater og sendedøgn (kap. 16)
curl -s -H "$AUTH" $ETV

# 2. Profilene (TV-ene) nøkkelen har tilgang til – noter ref-en (p_…)
curl -s -H "$AUTH" $ETV/profiles
# → [ { "ref": "p_7Kq2mPx9Qa", "online": true, "channelCount": 15,
#       "consent": { "bio": true, "notes": true, "photos": false }, … } ]

Gir steg 1 401, er nøkkelen feil eller trukket tilbake. Er lista i steg 2 tom, er ingen TV-er koblet til leverandøren ennå (kap. 3).

3. En lysbildeserie (én gang)

Spillelisten under har et lysbildeinnslag, og det peker på en lagret serie. Denne serien sendes som ren JSON, fordi bildene ligger på leverandørens egen https-server. Med egne bilde- og lydfiler sendes serien som zip-pakke (kap. 7). Feltene står i kap. 6.

curl -s -X PUT -H "$AUTH" -H "$JSON" $ETV/slideshows/enkeltv.ingrid-hagen -d '{
  "format": "enkeltv-slideshow/1",
  "title": "Hagen på Florø",
  "visibility": "personal",
  "profile": "p_7Kq2mPx9Qa",
  "defaults": { "text": { "style": { "size": 6, "background": "#00000099", "padding": 1.2 } } },
  "slides": [
    { "id": "hagen", "duration": 15,
      "layers": [
        { "type": "image", "src": "https://bilder.example.no/ingrid/hagen-1965.jpg",
          "motion": { "from": { "scale": 1 }, "to": { "scale": 1.1 } } },
        { "type": "text", "text": "Hagen på Florø, 1965", "x": 5, "y": 80, "w": 90,
          "align": "center", "start": 1, "fadeIn": 1 } ] },
    { "id": "epletre", "duration": 15,
      "layers": [
        { "type": "image", "src": "https://bilder.example.no/ingrid/epletreet.jpg" },
        { "type": "text", "text": "Epletreet i blomst", "x": 5, "y": 80, "w": 90, "align": "center" } ] }
  ]
}'
# → { "id": "enkeltv.ingrid-hagen", "version": 1, "changed": true, "duration": 30, "slides": 2, … }

4. Lagre dagens spilleliste – med YouTube, NRK og lysbilder

Én spilleliste per dag og kanal er enklest. Gi den en ID med datoen. Innslagene spilles i rekkefølge fra kl. 06:00 og går i løkke til kl. 06:00 neste morgen (kap. 11). Feltene for de tre typene står samlet i kap. 5.

curl -s -X PUT -H "$AUTH" -H "$JSON" $ETV/playlists/enkeltv.ingrid-2026-10-06 -d '{
  "format": "enkeltv-playlist/1",
  "title": "Ingrid – tirsdag 6. oktober",
  "visibility": "personal",
  "profile": "p_7Kq2mPx9Qa",
  "items": [
    { "id": "yt.ZlGyCfigmFg", "kind": "youtube", "title": "Allsang: Liten fuggel",
      "duration": 208, "youtube": { "videoId": "ZlGyCfigmFg" } },
    { "id": "nrk.FUHA01005272", "kind": "nrk", "title": "Fleksnes", "duration": 2196,
      "nrk": { "nrkId": "FUHA01005272", "type": "episode", "seriesTitle": "Fleksnes",
               "season": 1, "episode": 1 } },
    { "id": "lysbilder.ingrid-hagen", "kind": "slideshow", "title": "Hagen på Florø",
      "slideshow": { "ref": "enkeltv.ingrid-hagen" } }
  ]
}'
# → { "id": "enkeltv.ingrid-2026-10-06", "version": 1, "changed": true, "items": 3 }

5–6. Kanalen, og publiser spillelisten til den

Kanalen er det beboeren ser som «en TV-kanal», med et fast navn. Den lages én gang (steg 5). Etterpå publiseres en ny spilleliste til kanalen hver dag med POST /channels/{id}/playlists (steg 6), uten å sende kanalen på nytt.

# 5. Bare første gang: PUT erstatter hele kanalen, også spillelistene den har
curl -s -X PUT -H "$AUTH" -H "$JSON" $ETV/channels/enkeltv.ingrids-kanal -d '{
  "format": "enkeltv-channel/1",
  "name": "Ingrids kanal",
  "description": "Tog, Bergen og gamle favoritter",
  "color": "#e8a87c",
  "visibility": "personal",
  "profile": "p_7Kq2mPx9Qa",
  "programming": { "type": "loop", "playlists": [] }
}'

# 6. Publiser spillelisten fra sendedøgnet tirsdag 6. oktober (fra kl. 06:00 norsk tid).
#    Uten "from" gjelder den fra dagens sendedøgn – altså med en gang.
curl -s -X POST -H "$AUTH" -H "$JSON" $ETV/channels/enkeltv.ingrids-kanal/playlists \
  -d '{ "playlistId": "enkeltv.ingrid-2026-10-06", "from": "2026-10-06" }'
# → { "id": "enkeltv.ingrids-kanal", "version": 2, "changed": true }

Spillelisten gjelder til en nyere publiseres. Publiseres det to ganger for samme dag, gjelder den siste. Oppføringer som aldri kan gjelde igjen, ryddes bort fra kanalen hver gang noe publiseres (kap. 9). Selve spillelistene blir liggende til de slettes med DELETE /playlists/{id}.

7. Legg kanalen på en bestemt plass hos én profil (én gang)

# Plass 6 hos Ingrid. PATCH endrer bare plassene som er nevnt; "6": null fjerner plassen igjen.
curl -s -X PATCH -H "$AUTH" -H "$JSON" $ETV/profiles/p_7Kq2mPx9Qa/lineup \
  -d '{ "slots": { "6": "enkeltv.ingrids-kanal" } }'
# → { "format": "enkeltv-lineup/1", "profile": "p_7Kq2mPx9Qa", "channelCount": 15,
#     "slots": { "6": "enkeltv.ingrids-kanal", … }, "resolved": [ … ], … }

TV-en får endringen innen et par sekunder (kap. 11). Plassen viser kanalen fra kl. 06:00 den dagen kanalen har en spilleliste; før det viser plassen standardoppsettet (kap. 10).

8. Sjekk sendeplanen og hva som går nå

curl -s -H "$AUTH" "$ETV/channels/enkeltv.ingrids-kanal/schedule?date=2026-10-06"
# → { "date": "2026-10-06", "playlistId": "enkeltv.ingrid-2026-10-06",
#     "entries": [ { "itemId": "yt.ZlGyCfigmFg", "startsAt": "2026-10-06T04:00:00.000Z", … }, … ] }

curl -s -H "$AUTH" $ETV/profiles/p_7Kq2mPx9Qa/now
# → { "showing": { "slot": 6, … }, "slots": [ { "slot": 6, "kind": "nrk", "title": "Fleksnes", … }, … ] }

9. Hent historikk og bio

# Hva ble sett i sendedøgnet 6. oktober (06:00–06:00 norsk tid = 04:00–04:00 UTC om sommeren)
curl -s -H "$AUTH" "$ETV/profiles/p_7Kq2mPx9Qa/history?from=2026-10-06T04:00:00Z&to=2026-10-07T04:00:00Z"

# Bioen og nye notater fra familien – krever samtykke fra eieren (403 no-consent ellers)
curl -s -H "$AUTH" $ETV/profiles/p_7Kq2mPx9Qa/bio
curl -s -H "$AUTH" "$ETV/profiles/p_7Kq2mPx9Qa/notes?after=0"

Historikken er forklart i kap. 13, bio og notater i kap. 14. Alle feilkodene står i kap. 3.

3Tilgang, rettigheter og feil

Leverandøren får en API-nøkkel av Enkel-TV. Hver nøkkel tilhører en leverandør og har et sett rettigheter som krysses av når nøkkelen lages. En leverandør kan ha flere nøkler, f.eks. én per ansatt eller per system; alt innhold tilhører leverandøren, ikke nøkkelen. Serveren kjenner igjen nøkkelen på en SHA-256-sjekksum, og nøkkelen vises som regel bare én gang, når den lages – ta vare på den. Er den borte eller kommet på avveie, lager Enkel-TV en ny og trekker tilbake den gamle, som da slutter å virke med en gang (401). Innholdet leverandøren har laget, blir liggende.

Authorization: Bearer etv_VUFK…        # nøkkelen begynner alltid med etv_
Content-Type: application/json         # eller application/zip for lysbildepakker

Hvilken leverandør en profil (TV) har, velger Enkel-TV. GET /api/publish/profiles viser profilene nøkkelen har tilgang til. En tom liste betyr at ingen profiler er koblet til leverandøren ennå – be Enkel-TV velge leverandøren på TV-ene det gjelder.

Rettighetene

«Egne profiler» er aktive profiler der nøkkelens leverandør er valgt som leverandør. En profil som ikke er leverandørens, finnes ikke for nøkkelen (404 no-profile), bortsett fra lesing med events:all.

RettighetGir tilgang til
alle gyldige nøklerGET /api/publish, lista over egne profiler (GET /profiles), standardoppsettet (GET /lineups/default), lesing av felles og egne kanaler, spillelister og lysbildeserier, og sendeplanen for en synlig kanal (GET /channels/{id}/schedule)
library:writeLagre og slette egne kanaler, spillelister og lysbildeserier (JSON og zip), publisere spillelister til egne kanaler (POST /channels/{id}/playlists) og POST /media/check
lineups:writeLese og endre kanaloppsettet til egne profiler (GET · PUT · PATCH /profiles/{ref}/lineup) og se hva som går nå (GET /profiles/{ref}/now)
events:readHendelser (GET /events) og historikk (GET /profiles/{ref}/history) for egne profiler. Fyller også feltet showing i GET /profiles/{ref}/now når TV-en er på (ellers er det null)
profiles:readBio og notater for egne profiler, når eieren har samtykket (403 no-consent ellers)
photos:readFamiliebilder for egne profiler, når eieren har samtykket til bilder
lineups:defaultEndre standardoppsettet og reserveprogrammet som alle profiler arver (PUT /lineups/default). Bare for Enkel-TVs egen innholdstjeneste
events:allHendelser og historikk for alle profiler. Lista GET /profiles viser da alle profiler, og sammen med lineups:write kan nøkkelen lese – men ikke endre – kanaloppsettet og «nå» for andre leverandørers profiler. Bare til Enkel-TVs egen analyse, ikke til eksterne leverandører

Bio, notater og bilder kan bare leses for egne profiler – også med events:all. En vanlig nøkkel for en ekstern leverandør har library:write, lineups:write og events:read, og profiles:read og photos:read når leverandøren skal bruke bio, notater og bilder.

Svar og feil

Alle svar er JSON med status 200, også når noe lagres. Feil har en HTTP-status og en kropp med error – en melding på norsk – og som regel en fast code. Meldingen er til mennesker og kan bli bedre formulert; programmer skal se på status og code. Valideringsfeil begynner med stien til feltet som er feil:

HTTP/1.1 400 Bad Request
{ "error": "items[3].youtube.videoId må være en YouTube-ID på 11 tegn", "code": "invalid-playlist" }

Dette er alle feilene /api/publish kan gi:

StatuscodeNår
400invalid-channel
invalid-playlist
invalid-slideshow
invalid-lineup
Dokumentet følger ikke formatet (kanal, spilleliste, lysbildeserie eller kanaloppsett). Meldingen sier hvor, f.eks. slides[1].audio[0] slutter etter lysbildet: start + duration = 13.4 s, men lysbildet varer 12 s. Gjelder også en ugyldig ID i adressen, en referanse til noe som ikke finnes, manifest.json som ikke er gyldig JSON, en fil i pakken med ukjent filtype (media/bilde.heic: filtypen er ukjent – bruk JPEG, PNG, WebP, MP3, AAC, Ogg eller MP4) og en kanal som får mer enn 100 spillelister
400invalid-zipZip-pakken er ødelagt eller bruker noe som ikke støttes, eller manifest.json mangler øverst i pakken (kap. 7)
400invalid-jsonKroppen er ikke gyldig JSON, eller ikke et JSON-objekt
400invalid-requestFeil i kroppen til POST /media/check, ugyldig date i /schedule (må være YYYY-MM-DD, år 1900 eller senere), eller en adresse med ødelagt prosentkoding (f.eks. %E0)
400invalid-queryUgyldig parameter: since, after, types eller limit i /events, from/to i /history, eller after i /notes
400abortedOpplastingen ble avbrutt før hele kroppen var sendt
401–Nøkkelen mangler, er ukjent eller er trukket tilbake
403wrong-roleKallet har en innlogging fra en av appene i stedet for en API-nøkkel
403missing-scopeNøkkelen mangler rettigheten: Nøkkelen mangler rettigheten «library:write»
403not-ownerObjektet tilhører en annen leverandør (slette, eller publisere til en annen leverandørs kanal)
403no-consentEieren av TV-en har ikke samtykket til bio, notater eller bilder
403no-providerNøkkelen er ikke knyttet til en leverandør
404not-foundKanalen, spillelisten eller lysbildeserien finnes ikke, eller er en annen leverandørs personlige
404no-profileProfilen finnes ikke, er ikke aktiv eller har ikke nøkkelens leverandør
404–Ukjent adresse under /api/
405–Metoden finnes ikke for adressen
409id-takenID-en brukes av en annen leverandør
409visibility-lockedvisibility eller profile kan ikke endres på et objekt som finnes
409in-useObjektet er i bruk og kan ikke slettes. Svaret har lista usedBy (kap. 4)
409live-channelKanalen sender NRK direkte og har ingen spillelister
413too-largeFor stort: JSON-kropp over grensen (1 MB for kanaler, spillelister og lysbildeserier, ellers 512 KB), zip-pakke over 200 MB, manifest.json over 1 MB, eller en serie som bruker mer enn 200 MB filer
415unsupported-media-typeLysbildeserier må sendes som application/json eller zip (application/zip, application/x-zip-compressed eller application/x-zip)
500–Feil på serveren (Intern feil på serveren). Prøv igjen litt senere

Feil uten code (401, 404 for ukjent adresse, 405 og 500) har bare error. Innholds-API-et har ingen grense for antall kall (kap. 19). Bak en proxy kan en omstart av serveren gi 502 eller 503 i noen sekunder – prøv igjen.

Del B · Innhold

4Datamodellen

Profilen får kanalene sine gjennom et kanaloppsett: plasser (kanalnumre) som hver peker på en kanal i biblioteket. Kanalen bestemmer hvilken spilleliste som går hvert sendedøgn, og spillelisten består av innslag av de tre typene. Lysbildeserier er egne objekter med sine mediefiler, så de kan brukes i mange spillelister.

Profilref p_… · leverandør Kanaloppsettplass 1–99 → kanal Kanalnavn · programmering Spillelisteversjonert Innslagyoutube · slideshow · nrk Bio · notater· bildermed samtykke Standardoppsett+ reserveprogram LysbildeserieJSON eller zip · versjonert Hendelserfra TV-en og serveren TV-boksboks-ID = passordet Mediumbilde · lyd · video (sha256) 1 : 1 plass dato 1–1000 ref (kind:slideshow) 1 → mange 1 : 1 plasser alle arver logger inn som publiseres av leverandører (eies av leverandøren) hører til profilen felles for alle profiler (rettigheten lineups:default)
ObjektHvaEies av
ProfilTV-brukeren: én beboer med brukernavn og boks-ID. Leverandører ser den bare som ref: p_ og 10 tilfeldige bokstaver og tall. Hver profil har høyst én leverandør.Enkel-TV
KanaloppsettAntall kanaler og hvilken kanal som ligger på hver plass (kap. 10).profilen
StandardoppsettKanalene alle profiler får på plasser de ikke har satt selv, og reserveprogrammet for tomme plasser.felles
KanalNavn, beskrivelse, farge, emneord og programmering: hvilken spilleliste som går hvilke sendedøgn, eller en NRK-kanal direkte (kap. 9).leverandøren
SpillelisteInnslag i rekkefølge, av blandet type (kap. 5).leverandøren
InnslagEtt program: en YouTube-video, en lysbildeserie eller et NRK-program. Del av en spilleliste.–
LysbildeserieLysbilder med lag, lyd og musikk, som JSON eller zip-pakke (kap. 6–7).leverandøren
MediumEn bilde-, lyd- eller videofil. Lagres én gang uansett hvor mange serier som bruker den (kap. 8).serveren
Bio, notater, bilderBio skrevet av personalet, notater og bilder fra familien, og eierens samtykke (kap. 14).profilen
HendelseNoe som skjedde på TV-en, i en samtale eller med kanaloppsettet – sendt av TV-en eller laget av serveren (kap. 13).profilen

Regler som gjelder kanaler, spillelister og lysbildeserier

5Spillelister og de tre innslagstypene

En spilleliste er innslag i rekkefølge, og de tre typene – YouTube NRK Lysbilder – kan blandes fritt. Spillelister lagres med PUT /api/publish/playlists/{id}; svaret er { "id", "version", "changed", "items" }, der items er antall innslag. En spilleliste vises først på TV-en når den er publisert til en kanal (kap. 9).

Format enkeltv-playlist/1

FeltTypeBeskrivelse
format *tekst"enkeltv-playlist/1"
title *tekst ≤ 120Navn for leverandøren og Enkel-TV. Vises ikke for beboeren
visibility *tekstshared eller personal
profilep_…Påkrevd for personal, ikke lov for shared
items *liste1–1000 innslag, se oversikten under

Oversikt: de tre innslagstypene

Hvert innslag har noen felles felt og ett objekt med typens egne felt. Objektet har samme navn som kind: et YouTube-innslag har "kind": "youtube" og objektet "youtube": { … }. Et objekt for en annen type gir feil. * = påkrevd; alt annet er valgfritt.

FeltType og grenserBetyr
Felles for alle innslag
id *1–80 tegn: bokstaver, tall, . _ : -, først bokstav eller tallLeverandørens ID for programmet, unik i spillelisten. Bruk samme ID for samme innhold i alle spillelister – da kan hendelsene sammenlignes på tvers
kind *youtube · nrk · slideshowTypen – bestemmer avspilleren
title *tekst ≤ 120Vises i kanalbanneret («Nå: …», «Neste kl. 19:30: …»)
duration *heltall 1–86 400 sSekunder på tidslinjen. Påkrevd for youtube og nrk. For slideshow regnes lengden ut av serien; en oppgitt verdi ignoreres
tags≤ 20 emneord à ≤ 40 tegnF.eks. ["tog", "vestlandet"]. Like emneord slås sammen. Følger med i item.started
availableFrom
availableTo
YYYY-MM-DD, availableTo ≥ availableFromFørste og siste sendedøgn innslaget er med (til og med). Utenfor vinduet hoppes innslaget over, og resten av lista rykker sammen
YouTube "kind": "youtube" – objektet youtube
videoId *11 tegn: bokstaver, tall, _ og -YouTube-ID-en, f.eks. "ZlGyCfigmFg" fra youtube.com/watch?v=ZlGyCfigmFg. Videoen må tillate innbygging
startheltall ≥ 0 s, standard 0Hvor langt inn i videoen innslaget begynner. Med start og duration kan man vise en bit av en lang video – f.eks. 30 minutter fire timer inn i Bergensbanen
NRK "kind": "nrk" – objektet nrk
nrkId *4–40 tegn: bokstaver, tall, _ og -, først bokstav eller tallNRK sin program-ID, f.eks. "FUHA01005272" – ID-en i adressen på tv.nrk.no. Det er alt som trengs for å spille av
typeprogram · episode · filmlive gir 400: NRK1–3 direkte er en egen kanaltype (kap. 9), ikke et innslag
seriesTitletekst ≤ 120Serienavn for episoder
season, episodeheltall 0–10 000 og 0–100 000Sesong og episode
yearheltall 1800–2200Produksjonsår
descriptiontekst ≤ 500Kort omtale, vises i plassholderen
imageUrlhttps, ≤ 2000 tegnProgrambilde – vises mens videoen laster, og i plassholderen
subtitlesUrlhttps, ≤ 2000 tegnUndertekster (WebVTT), slås på som standard. Filen hentes med CORS, så den må sendes med Access-Control-Allow-Origin, ellers vises ikke undertekstene
startheltall ≥ 0 s, standard 0Hvor langt inn i programmet innslaget begynner
Lysbilder "kind": "slideshow" – objektet slideshow
ref *ID-en til en lagret lysbildeserieSerien lagres først (kap. 6–7) og må følge reglene for referanser (kap. 4): en felles spilleliste kan bare bruke felles serier. Innslagets lengde er seriens lengde

GET /api/publish/playlists/{id} viser alltid seriens nåværende lengde som duration på lysbildeinnslag. playUrl og format i nrk-objektet fra eldre dokumenter brukes ikke: de ignoreres og fjernes når spillelisten lagres.

Eksempel: en spilleliste med alle tre typene

PUT /api/publish/playlists/enkeltv.ingrid-kveld-uke41
{
  "format": "enkeltv-playlist/1",
  "title": "Ingrid – kveld, uke 41",
  "visibility": "personal",
  "profile": "p_7Kq2mPx9Qa",
  "items": [
    { "id": "yt.bergensbanen", "kind": "youtube", "title": "Bergensbanen",
      "duration": 1800, "tags": ["tog", "vinter"],
      "youtube": { "videoId": "d_S_13TWn1c", "start": 14400 } },

    { "id": "lysbilder.bergen-1950", "kind": "slideshow", "title": "Bergen på 1950-tallet",
      "tags": ["bergen", "hjemsted"],
      "slideshow": { "ref": "enkeltv.ingrid-bergen-1950" } },

    { "id": "yt.ZlGyCfigmFg", "kind": "youtube", "title": "Allsang: Liten fuggel",
      "duration": 208, "tags": ["allsang"],
      "youtube": { "videoId": "ZlGyCfigmFg" } },

    { "id": "nrk.fleksnes.s1e1", "kind": "nrk", "title": "Fleksnes",
      "duration": 2196, "tags": ["humor", "70-tallet"],
      "availableFrom": "2026-10-01", "availableTo": "2026-12-31",
      "nrk": { "nrkId": "FUHA01005272", "type": "episode",
               "seriesTitle": "Fleksnes", "season": 1, "episode": 1, "year": 1972 } }
  ]
}

YouTube Avspilling og feil

TV-en spiller videoen i YouTubes innebygde spiller i fullskjerm, fra start pluss hvor langt sendingen har kommet. Kan videoen ikke spilles, melder TV-en playback.error med kind media-missing (fjernet eller privat), not-embeddable (eieren tillater ikke innbygging), unsupported (annen feil) eller network (YouTube kunne ikke lastes), avslutter innslaget med reason: error og viser et rolig pausebilde. Innslaget blir stående i tidslinjen, og TV-en prøver igjen senere (kap. 12). Leverandøren bør bytte det ut.

NRK Slik virker avspillingen

Status for NRK: Avspillingen bruker NRK sitt uoffisielle API (PSAPI), og vi har ikke avtale med NRK ennå (kap. 19). NRK sitt API svarer i dag bare nettsider på nrk.no og på maskinen selv (localhost). Til TV-boksene er satt opp for det, kan NRK-innslag og direktekanaler vise plassholderen i stedet for programmet; i hendelsene ses det som playback.error med kind: network og details «Får ikke kontakt med NRK (nett eller CORS) …».

{ "id": "nrk.fleksnes.1972.fysiske-fordeler", "kind": "nrk", "title": "Fleksnes: Fysiske fordeler",
  "duration": 2196, "tags": ["humor", "70-tallet"],
  "nrk": { "nrkId": "FUHA01005272", "type": "episode", "seriesTitle": "Fleksnes",
           "season": 1, "episode": 1, "year": 1972,
           "imageUrl": "https://gfx.nrk.no/W-sVDcYdu6rzfyUZ5GAk4ABRYRfA_2J6J_fjWiAYLNfw" } }

{ "id": "nrk.hurtigruten.2011.bergen", "kind": "nrk",
  "title": "Hurtigruten minutt for minutt: Ved kai i Bergen", "duration": 1173,
  "nrk": { "nrkId": "DVFJ67000111" } }

Det andre innslaget viser det minste som trengs: bare nrkId.

Lysbilder Innslag som viser en serie

Et lysbildeinnslag er bare "slideshow": { "ref": "<seriens id>" }. Selve serien – lysbilder med lag, lyd og musikk – lagres for seg, som JSON (kap. 6) eller zip-pakke med filene (kap. 7), og kan brukes i så mange spillelister man vil. En ny versjon av serien gjelder i alle spillelistene som peker på den, fra neste gang innslaget starter.

6Lysbildeserier

En lysbildeserie beskriver skjermbilder med lag – bilde, video, tekst og form – plassert fritt på et 16:9-lerret, med lyd og musikk på faste tider. TV-en tegner nøyaktig det JSON-en beskriver. Leverandøren kan dermed bygge rike skjermbilder uten å sende kode. Serien lagres med PUT /api/publish/slideshows/{id}, enten som zip-pakke med filene (kap. 7) eller som ren JSON når alle filene er https://- eller sha256:-adresser.

Lerretet

Fiskebodene Torget i Bergen, 1954 Mittet & Co. / Nasjonalbiblioteket, 1954 0x = 50 %100 % 0y = 50 %100 % 1 2 3 4 1 image x 0 · y 0 · w 100 · h 100, fit cover rolig zoom 1,0 → 1,15 gjennom lysbildet 2 shape (ellipse) x 22 · y 48 · w 30 · h 18, fra 5 til 10 s 3 text «Fiskebodene» x 24 · y 37 · w 26, size 3,5, fra 5 til 10 s 4 text «Torget i Bergen, 1954» x 5 · y 80 · w 90, size 6, bold, bakgrunn fra 0,5 s, fadeIn 0,6 s – – anbefalt: hold tekst innenfor 5–95 % credit: alltid nede til høyre x, y, w, h i prosent av lerretet tekststørrelse i prosent av høyden
Alle mål er prosent av lerretet, så det samme lysbildet ser likt ut på alle TV-er. Er skjermen ikke 16:9, får lerretet svarte kanter.

Format enkeltv-slideshow/1

Kolonnen «Standard» er verdien TV-en bruker når feltet mangler. Det lagrede dokumentet beholder defaults; TV-en får dem flettet inn.

FeltTypeBeskrivelse
format *tekst"enkeltv-slideshow/1"
title *tekst ≤ 120Seriens tittel
languagetekst ≤ 10nb, nn, se …
visibility *tekstshared eller personal (med profile). Serier med familiebilder må være personlige
profilep_…Påkrevd for personal
tagslisteHøyst 20 emneord à 40 tegn
musicobjektBakgrunnsmusikk for hele serien, se under
defaultsobjekt{ "text": { "style": {…} }, "transition": {…} } – standardverdier så de ikke må gjentas
slides *liste1–500 lysbilder. Seriens lengde er summen av lysbildenes duration

Lysbilde

FeltTypeStandardBeskrivelse
id *tekst ≤ 80–Unik i serien (samme tegn som innslags-ID), brukes i slide.shown
duration *1–600 s–Hvor lenge lysbildet står, desimaler er lov
background#rrggbb#000000Bakgrunn bak lagene
transitionobjektfade 1 s{ "type": "fade", "duration": 0–5 } (det nye lysbildet toner inn over det forrige) eller { "type": "cut" }. Overgangen er en del av lysbildets egen tid. Mangler feltet, brukes defaults.transition, ellers fade på 1 s. En fade uten duration varer alltid 1 s, også når defaults.transition har en annen lengde
layers *liste–1–12 lag, se under
audiolisteingen0–4 lydklipp, f.eks. opplesning og en lydeffekt
credittekst ≤ 200ingenKreditering, nede til høyre. Ta den med når lisensen krever navngivelse

Felles for alle lag

FeltTypeStandardBeskrivelse
type *tekst–image · video · text · shape
x, y, w, h0–1000, 0, 100, 100Plassering og størrelse. For tekst følger høyden teksten når h mangler
start, endsekunder0, lysbildets sluttNår laget vises. start må være mindre enn lysbildets lengde; end må være etter start og ikke etter lysbildets slutt
fadeIn, fadeOut≥ 0 s0Myk inn- og uttoning
opacity0–11Gjennomsiktighet

Lagtyper

TypeEgne felt (standard i parentes)
imagesrc * (et bilde) · fit: cover (fyller, kan beskjæres) eller contain (hele bildet) (cover) · alt ≤ 300 tegn · motion: rolig zoom og panorering gjennom lagets tid, { "from": { "scale": 1, "x": 50, "y": 50 }, "to": { "scale": 1.15, "x": 40, "y": 55 } }, der x/y (0–100, standard 50) er punktet i bildet det zoomes mot og scale er 1–2 (standard 1). Både from og to må være med
videosrc * (MP4) · duration *: klippets lengde, 0,1–86 400 s · fit (cover) · trimStart: sekunder inn i klippet, mindre enn duration (0) · muted (true når lysbildet har audio, ellers false) · volume 0–1 (1) · loop (false). Uten loop blir siste bilde stående når klippet er ferdig
texttext *: ren tekst ≤ 500 tegn, \n gir linjeskift (ingen HTML) · align: left · center · right (left) · valign: top · middle · bottom (top), og det virker bare når laget har h · style, se under
shapeshape *: rect eller ellipse · fill og stroke: #rrggbb eller #rrggbbaa (ingen) · strokeWidth: 0–5 prosent av høyden (0) · radius: hjørner på rect, 0–50 prosent av høyden (0). Brukes til bakgrunner og til å peke ut noe i bildet

style for tekst

Verdiene flettes slik: TV-ens standard → defaults.text.style → lagets egen style.

FeltTypeStandardBeskrivelse
fonttekstsanssans eller serif – TV-ens egne skrifter, ingen nedlasting
size1,5–205Prosent av lerretets høyde
weight, italicregular, falseregular eller bold; true/false
color, backgroundfargehvit, ingen#rrggbb eller #rrggbbaa (med gjennomsiktighet). Bakgrunnen dekker bare selve teksten (pluss padding), ikke hele laget. Vil du ha en flate over hele bredden, legger du et shape-lag under teksten
padding, radius0–100, 0Luft rundt teksten og runde hjørner på bakgrunnen, i prosent av høyden
lineHeight0,8–31,25Linjeavstand
shadowsant/usantuten bakgrunnSkygge bak teksten for lesbarhet. Standard er true når teksten ikke har bakgrunn

Lyd og musikk

FeltTypeStandardBeskrivelse
audio[] på lysbildet
src *fil–Lydfil: MP3, AAC, Ogg eller M4A (en MP4-fil godtas også)
start≥ 0 s0Når lyden begynner
duration *0,1–600 s–Lydfilens lengde. start + duration må være innenfor lysbildet (0,05 s slingring)
volume0–11Volum
ducksant/usanttrueSenk musikken til duckTo mens klippet spiller
transcript≤ 2000 tegningenDet som blir sagt. Vises ikke, men lagres og leveres med serien
music for serien
src *, duration *fil, 0,1–86 400 s–Lydfil og lengde. Musikken går over alle lysbildene og begynner forfra når den er ferdig
volume, duckTo0–10,25, 0,08Volum, og volum mens et lydklipp med duck spiller. Lyd fra et video-lag demper ikke musikken

Filer (src, høyst 2000 tegn) oppgis på én av tre måter: en sti i zip-pakken som begynner med media/ (media/torget-1954.jpg), en fil serveren allerede har (sha256: fulgt av 64 heksadesimale tegn, kap. 8), eller en https://-adresse hos leverandøren. Serveren sjekker at filen passer til laget: et image-lag må peke på et bilde, et video-lag på en video (MP4, MOV eller 3GP – kap. 7), og lyd og musikk på en lydfil eller MP4. https-adresser hentes ikke av serveren og sjekkes ikke: TV-en henter dem direkte fra leverandørens server, så de må være åpne og i et format TV-en kan spille (kap. 7).

Serveren sjekker tidene, ikke lengden på selve filene: lag og lydklipp må ligge innenfor lysbildets duration, og lengdene som er oppgitt for lyd og video, brukes til det. Lyd som likevel er lengre, kuttes når lysbildet er over.

Ett lysbilde over tid

Lysbildet «torget» i eksempelet under: duration: 12, kryssfade inn på 1,2 s, opplesning fra 1 s (9,4 s lang), pekeren og «Fiskebodene» fra 5 til 10 s.

Overgang (fade 1,2 s)
fade
1 Bilde, zoom 1,0 → 1,15
torget-1954.jpg
4 Tekst (fra 0,5 s)
«Torget i Bergen, 1954»
2 Form (5–10 s)
ellipse
3 Tekst (5–10 s)
«Fiskebodene»
Opplesning (1–10,4 s)
tale/torget.mp3
Musikk (hele serien)
0,25
dempet til 0,08 under opplesningen
0 s151012 s

Eksempel: manifest.json i en pakke

{
  "format": "enkeltv-slideshow/1",
  "title": "Bergen på 1950-tallet",
  "language": "nb",
  "visibility": "shared",
  "tags": ["bergen", "1950-tallet", "hjemsted"],
  "music": { "src": "media/musikk/morgenstemning.mp3", "duration": 245.0,
             "volume": 0.25, "duckTo": 0.08 },
  "defaults": {
    "text": { "style": { "font": "sans", "size": 5.5, "color": "#ffffff",
                         "background": "#00000099", "padding": 1.2, "radius": 1 } },
    "transition": { "type": "fade", "duration": 1.2 }
  },
  "slides": [
    {
      "id": "floyen", "duration": 10,
      "layers": [
        { "type": "image", "src": "media/floyen-1961.jpg", "fit": "cover",
          "alt": "Utsikt fra Fløyen over Vågen",
          "motion": { "from": { "scale": 1.0, "x": 50, "y": 50 },
                      "to": { "scale": 1.1, "x": 55, "y": 60 } } },
        { "type": "text", "text": "Utsikten fra Fløyen", "x": 5, "y": 80, "w": 90,
          "align": "center", "start": 0.5, "fadeIn": 0.6 }
      ],
      "audio": [
        { "src": "media/tale/floyen.mp3", "start": 1.0, "duration": 7.8,
          "transcript": "Vi begynner oppe på Fløyen, med utsikt over hele byen." }
      ],
      "credit": "Jac Brun / Mittet & Co. / Nasjonalbiblioteket, 1961"
    },
    {
      "id": "torget", "duration": 12,
      "layers": [
        { "type": "image", "src": "media/torget-1954.jpg", "fit": "cover",
          "motion": { "from": { "scale": 1.0, "x": 50, "y": 50 },
                      "to": { "scale": 1.15, "x": 40, "y": 55 } } },
        { "type": "shape", "shape": "ellipse", "x": 22, "y": 48, "w": 30, "h": 18,
          "stroke": "#f2c48d", "strokeWidth": 0.6,
          "start": 5, "end": 10, "fadeIn": 0.8, "fadeOut": 0.8 },
        { "type": "text", "text": "Fiskebodene", "x": 24, "y": 37, "w": 26,
          "start": 5, "end": 10, "style": { "size": 3.5 } },
        { "type": "text", "text": "Torget i Bergen, 1954", "x": 5, "y": 80, "w": 90,
          "align": "center", "start": 0.5, "fadeIn": 0.6,
          "style": { "weight": "bold", "size": 6 } }
      ],
      "audio": [
        { "src": "media/tale/torget.mp3", "start": 1.0, "duration": 9.4,
          "transcript": "Dette er Torget i 1954. Fiskehandlerne står bak diskene." }
      ],
      "credit": "Mittet & Co. / Nasjonalbiblioteket, 1954"
    },
    {
      "id": "bryggen-film", "duration": 14, "transition": { "type": "cut" },
      "layers": [
        { "type": "video", "src": "media/bryggen-1958.mp4", "duration": 14.0, "fit": "cover" },
        { "type": "text", "text": "Bryggen en vårdag", "x": 5, "y": 6, "w": 60,
          "start": 2, "end": 9, "fadeIn": 0.6, "fadeOut": 0.6 }
      ]
    }
  ]
}

7Lysbildepakker (zip)

En hel lysbildeserie kan sendes som én zip-fil: manifest.json med serien, og mediefilene den peker på. Serveren sjekker pakken, lagrer filene og gir serien en versjon. Etterpå kan serien brukes i så mange spillelister man vil, bare ved å peke på ID-en – uten ny opplasting.

enkeltv.bergen-1950.zip
├── manifest.json                 ← lysbildeserien (enkeltv-slideshow/1)
└── media/
    ├── floyen-1961.jpg
    ├── torget-1954.jpg
    ├── bryggen-1958.mp4
    ├── tale/floyen.mp3
    ├── tale/torget.mp3
    ├── musikk/morgenstemning.mp3
    └── gammel-utgave.jpg         ← ikke brukt: gir en advarsel

Krav til pakken

TypeFormater serveren kjenner igjenAnbefaling
BildeJPEG, PNG, WebP1920–3840 px bredt (større når bildet zoomes)
LydMP3, AAC (ADTS), Ogg, M4ATale og musikk med jevnt lydnivå
VideoMP4 og andre filer i samme beholderformat (ISO-medier med ftyp), også MOV (QuickTime), M4V og 3GP. Alle leveres til TV-en som video/mp4MP4 med H.264 og AAC, 1080p

HEIC-, HEIF- og AVIF-bilder (vanlig fra iPhone) er ukjent filtype og må gjøres om til JPEG før de legges i pakken. MOV og 3GP godtas, men serveren sjekker ikke kodeken i klippet: H.264 virker, mens HEVC (H.265), som iPhone tar opp med som standard, kan TV-en ikke spille sikkert. Gjør derfor helst videoen om til MP4 med H.264 og AAC.

Grenser

Laste opp og gjenbruke

PUT /api/publish/slideshows/enkeltv.bergen-1950
Content-Type: application/zip
<zip-filen>

HTTP/1.1 200 OK
{ "id": "enkeltv.bergen-1950", "version": 1, "changed": true, "duration": 36, "slides": 3,
  "bytes": 48213377, "media": { "new": 6, "reused": 0 },
  "warnings": [ "media/gammel-utgave.jpg er ikke brukt i manifestet" ] }
POST /api/publish/media/check
{ "sha256": [ "e1e5628ba30deef1d0a0e5c38e6d6b74aa32a21da631f7159a9b7ca78f02dbe6",
              "23193579fb505275fb6ddd64b1e62e36d26db474958cda9c1c9659d6cb62be26" ] }

{ "have": [ "e1e5628ba30deef1d0a0e5c38e6d6b74aa32a21da631f7159a9b7ca78f02dbe6" ],
  "missing": [ "23193579fb505275fb6ddd64b1e62e36d26db474958cda9c1c9659d6cb62be26" ] }

8Mediefiler

Bilder, lyd og video fra lysbildepakkene – og familiebildene fra mobilappen – ligger i serverens mediearkiv.

Del C · Kanaler og avspilling

9Kanalbiblioteket

En kanal er det beboeren opplever som «en TV-kanal»: et navn, et tema og en sending som går hele tiden. Kanaler lagres med PUT /api/publish/channels/{id}. Samme kanal kan ligge på mange profiler (felles), eller lages for én beboer (personlig).

Kanalen er fast, spillelistene skiftes. Kanalen har et fast navn – det beboeren ser når hen bytter kanal. Spillelisten er det som spilles den dagen, og leverandøren publiserer en ny så ofte den vil, helst én gang om dagen. Kommer det ingen ny, går den forrige videre til en ny kommer. En kanal kan lages før den har noen spilleliste; da er den tom, og plassen viser standardoppsettet, reserveprogrammet eller «Kommer snart».

Format enkeltv-channel/1

FeltTypeBeskrivelse
format *tekst"enkeltv-channel/1"
name *tekst ≤ 40Kanalnavnet, vises stort når man bytter kanal
descriptiontekst ≤ 120Én linje under navnet i kanalbanneret
color#rrggbbAksentfarge i kanalbanneret og på pausebildet
visibility *tekstshared eller personal
profilep_…Profilens ref. Påkrevd for personal, ikke lov for shared
tagslisteHøyst 20 emneord à høyst 40 tegn. Like emneord slås sammen
programming *objektHva som går på kanalen: "type": "loop" (spillelister, se under) eller "type": "live" (NRK direkte)

Programmering: én spilleliste per sendedøgn, i løkke

"type": "loop" med en liste playlists (0–100 oppføringer). Hver oppføring er { playlistId, from?, to? }, der from og to er sendedøgn (YYYY-MM-DD, til og med; to kan ikke være før from). Sendedøgn D varer fra D kl. 06:00 til D+1 kl. 06:00 norsk tid. Slik velger serveren dagens spilleliste:

  1. Oppføringer der from er etter D, eller to er før D, gjelder ikke.
  2. Blant resten vinner den med senest from. En oppføring uten from regnes som eldst.
  3. Har flere samme from, vinner den som står sist i lista.
  4. Gjelder ingen, er kanalen tom denne dagen, og plassen faller videre (kap. 10).

Slik kan neste ukes spilleliste legges inn på forhånd. Den tar over kl. 06:00 den dagen den gjelder fra (kap. 11). "type": "schedule" (faste klokkeslett) avvises foreløpig med Daglige sendeplaner kommer senere – bruk type "loop".

PUT /api/publish/channels/sporet
{
  "format": "enkeltv-channel/1",
  "name": "Sporet",
  "description": "Togreiser gjennom Norge",
  "color": "#f2c48d",
  "visibility": "shared",
  "tags": ["tog", "slow-tv"],
  "programming": { "type": "loop", "playlists": [ { "playlistId": "enkeltv.sporet" } ] }
}
PUT /api/publish/channels/enkeltv.ingrids-kanal
{
  "format": "enkeltv-channel/1",
  "name": "Ingrids kanal",
  "description": "Tog, Bergen og gamle favoritter",
  "color": "#e8a87c",
  "visibility": "personal",
  "profile": "p_7Kq2mPx9Qa",
  "tags": ["personlig"],
  "programming": {
    "type": "loop",
    "playlists": [
      { "playlistId": "enkeltv.ingrid-kveld" },
      { "playlistId": "enkeltv.ingrid-kveld-uke41", "from": "2026-10-05", "to": "2026-10-11" }
    ]
  }
}

Her går enkeltv.ingrid-kveld-uke41 fra mandag 5. oktober kl. 06:00 til mandag 12. oktober kl. 06:00. Før og etter gjelder enkeltv.ingrid-kveld, som ikke har datoer. Svaret er { "id", "version", "changed" }.

Dagens spilleliste til en kanal

Den vanlige arbeidsflyten er å lagre dagens spilleliste og så publisere den til kanalen – uten å sende hele kanalen på nytt. from er dagens sendedøgn hvis den utelates, og oppføringen har ingen sluttdato, så den gjelder til en nyere publiseres. Publiseres det to ganger samme dag, erstatter den siste den første. Oppføringer som aldri kan gjelde igjen (passert to, eller eldre enn dagens oppføring uten sluttdato), ryddes bort, så lista ikke vokser. Bare leverandøren som eier kanalen, kan publisere til den (ellers 403 not-owner). Spillelisten må finnes og følge reglene for referanser (kap. 4): en felles kanal kan bare få felles spillelister.

PUT  /api/publish/playlists/enkeltv.sporet-2026-10-06     # dagens spilleliste
POST /api/publish/channels/sporet/playlists
{ "playlistId": "enkeltv.sporet-2026-10-06" }            # gjelder fra i dag kl. 06:00

POST /api/publish/channels/sporet/playlists
{ "playlistId": "enkeltv.sporet-jul", "from": "2026-12-24", "to": "2026-12-26" }

Kroppen er { playlistId, from?, to? }, med from og to som sendedøgn (YYYY-MM-DD). Svaret er det samme som for PUT /channels/{id}: { "id", "version", "changed" }. Feilene viser til feltene i forespørselen, f.eks. playlistId viser til spillelisten «x», som ikke finnes eller to kan ikke være før from. En kanal kan ha høyst 100 spillelister som gjelder i dag eller senere; flere gir 400 invalid-channel. Vil du fjerne eller flytte oppføringer, hent kanalen med GET, endre programming.playlists og lagre den med PUT.

Direktekanaler: NRK1, NRK2 og NRK3

En direktekanal viser en NRK-kanal direkte hele døgnet. Programmeringen er "type": "live" med nrkChannel *: nrk1, nrk2 eller nrk3. Kanalen har ingen spillelister og ingen tidslinje: POST /channels/{id}/playlists gir 409 med code: "live-channel". TV-en henter strømmen hos NRK (https://psapi.nrk.no/playback/manifest/channel/nrk1) når kanalen stilles inn, og spiller direktesendingen (kap. 5). Faller strømmen ut, viser TV-en «Sendingen kommer straks tilbake» og prøver igjen av seg selv, først etter 15 sekunder og så sjeldnere, høyst hvert 5. minutt. Finnes det allerede felles direktekanaler for NRK1–3 (se GET /channels), bruk dem i kanaloppsettet i stedet for å lage nye.

PUT /api/publish/channels/nrk1
{
  "format": "enkeltv-channel/1",
  "name": "NRK1",
  "description": "NRK1 direkte",
  "color": "#e05a4f",
  "visibility": "shared",
  "tags": ["nrk", "direkte"],
  "programming": { "type": "live", "nrkChannel": "nrk1" }
}

Direkte NRK er ikke et innslag: et NRK-innslag med "type": "live" gir 400 (kap. 5). I GET /channels har hver kanal feltet type (loop eller live).

10Kanaloppsett per profil

Kanaloppsettet bestemmer hva beboeren finner når hen blar med pil opp/ned. Det har et antall kanaler (channelCount, 1–99) og en kanal for plassene profilen har valgt selv. Fjernkontrollen går rundt: 0 → 1 → … → channelCount → 0, og 0 er alltid klokkeskjermen. Har profilen ikke et eget antall, gjelder standardoppsettets (10 hvis standardoppsettet ikke har et antall).

Hva vises på en plass?

For hver plass fra 1 til channelCount velger serveren den første kilden som har innhold i dagens sendedøgn:

#sourceForklaring
1profileProfilens egen plass – en felles kanal, eller en personlig kanal for denne profilen. Kanalen kan få et eget navn på denne profilen.
2defaultPlassen i standardoppsettet (bare felles kanaler).
3fallbackReserveprogrammet: én felles kanal som fyller tomme plasser.
4builtinIngenting: TV-en viser et rolig, innebygd «Kommer snart»-bilde (channelId: null).

«Har innhold» betyr at kanalen finnes, at programmeringen har en spilleliste for dagens sendedøgn (kap. 9), og at spillelisten har minst ett innslag som er tilgjengelig denne datoen. Lysbildeinnslag der serien er slettet, regnes ikke med. En direktekanal (kap. 9) har alltid innhold. En kanal uten innhold i dag gjør at plassen faller videre til neste kilde.

Format enkeltv-lineup/1

FeltTypeBeskrivelse
formattekst"enkeltv-lineup/1". Påkrevd i PUT; valgfritt i PATCH, men må da være riktig
channelCount1–99Antall kanaler på profilen. null (eller utelatt i PUT) betyr «følg standardoppsettet». Utelatt i PATCH: uendret
slotsobjektPlassnummer "1"–"99" → kanal-ID, eller { "channel": "allsang", "name": "Sangstund" } for et eget navn (≤ 40 tegn) på denne profilen. I PATCH fjerner null plassen. Påkrevd i PUT. Plasser over channelCount er lov: de lagres, men vises ikke

Kanalen må finnes og være felles eller personlig for denne profilen – ellers 400 invalid-lineup, f.eks. slots.3 viser til kanalen «x», som ikke finnes. En felles kanal kan være laget av hvilken som helst leverandør.

Svaret

FeltBeskrivelse
format"enkeltv-lineup/1"
profileProfilens ref
channelCount / ownChannelCountAntallet TV-en viser, og profilens eget tall (null = følger standardoppsettet)
slotsPlassene profilen har valgt selv, sortert
resolvedHva som faktisk vises på hver plass i dag: { slot, channelId, name, source }
updatedAt, updatedBySiste endring, og hvem: { "type": "provider" | "staff", "name": … }
PATCH /api/publish/profiles/p_7Kq2mPx9Qa/lineup        (endrer bare plassene som er nevnt)
{ "slots": { "7": "kysten", "8": { "channel": "allsang", "name": "Sangstund" }, "15": null } }

GET /api/publish/profiles/p_7Kq2mPx9Qa/lineup
{
  "format": "enkeltv-lineup/1",
  "profile": "p_7Kq2mPx9Qa",
  "channelCount": 15,
  "ownChannelCount": 15,
  "slots": {
    "6": "enkeltv.ingrids-kanal",
    "7": "kysten",
    "8": { "channel": "allsang", "name": "Sangstund" },
    "14": "gammeldans"
  },
  "resolved": [
    {"slot": 1, "channelId": "nrk1", "name": "NRK1", "source": "default"},
    {"slot": 2, "channelId": "nrk2", "name": "NRK2", "source": "default"},
    {"slot": 3, "channelId": "nrk3", "name": "NRK3", "source": "default"},
    {"slot": 4, "channelId": "sporet", "name": "Sporet", "source": "default"},
    {"slot": 5, "channelId": "fjell-og-fjord", "name": "Fjell og fjord", "source": "default"},
    {"slot": 6, "channelId": "enkeltv.ingrids-kanal", "name": "Ingrids kanal", "source": "profile"},
    {"slot": 7, "channelId": "kysten", "name": "Kysten", "source": "profile"},
    {"slot": 8, "channelId": "allsang", "name": "Sangstund", "source": "profile"},
    {"slot": 9, "channelId": "norge-for-i-tiden", "name": "Norge før i tiden", "source": "default"},
    {"slot": 10, "channelId": "klassisk", "name": "Klassisk", "source": "default"},
    {"slot": 11, "channelId": "dyr-og-fugler", "name": "Dyr og fugler", "source": "default"},
    {"slot": 12, "channelId": "bilder-fra-bergen", "name": "Bilder fra Bergen", "source": "default"},
    {"slot": 13, "channelId": "nrk", "name": "NRK-arkivet", "source": "default"},
    {"slot": 14, "channelId": "gammeldans", "name": "Gammeldans", "source": "profile"},
    {"slot": 15, "channelId": "sporet", "name": "Sporet", "source": "fallback"}
  ],
  "updatedAt": "2026-10-05T08:12:00.000Z",
  "updatedBy": { "type": "provider", "name": "Enkel-TV" }
}

Hvem endrer oppsettet?

Den siste lagringen gjelder. Hver endring lagrer hvem som gjorde den, og gir hendelsen lineup.changed med plassene som ble endret (kap. 13). En lagring som ikke endrer noe, gir ingen hendelse.

Standardoppsett og reserveprogram

Standardoppsettet har samme format, pluss fallback (reserveprogrammet: en felles kanal-ID eller null). Det kan bare inneholde felles kanaler, leses med alle nøkler og endres med PUT og rettigheten lineups:default. channelCount gjelder profiler som ikke har satt sitt eget antall; utelates det i PUT, blir det 10. Plasser med null hoppes over, og en plass kan ha eget navn ({ "channel", "name" }) som i profilens oppsett – men fallback må være en ren kanal-ID.

Svaret på GET og PUT har feltene format, channelCount, slots, fallback, updatedAt og updatedBy. Er standardoppsettet aldri lagret, er det { "format": "enkeltv-lineup/1", "channelCount": 10, "slots": {}, "fallback": null, "updatedAt": null, "updatedBy": null }.

PUT /api/publish/lineups/default
{
  "format": "enkeltv-lineup/1",
  "channelCount": 13,
  "slots": { "1": "nrk1", "2": "nrk2", "3": "nrk3", "4": "sporet", "5": "fjell-og-fjord",
             "6": "kysten", "7": "gamle-slagere", "8": "gammeldans", "9": "norge-for-i-tiden",
             "10": "klassisk", "11": "dyr-og-fugler", "12": "bilder-fra-bergen", "13": "nrk" },
  "fallback": "sporet"
}

Med dette standardoppsettet får alle profiler direktekanalene NRK1–3 (kap. 9) på plass 1–3, med mindre profilen har lagt egne kanaler på de plassene. Standardoppsettet endres bare av Enkel-TVs egen innholdstjeneste (lineups:default).

11Lineær TV: tidslinjen

Hver kanal har én tidslinje som går hele tiden, også når ingen ser på. Tidslinjen er ren regning uten lagret tilstand: serveren og TV-en bruker den samme regnemåten og kommer alltid fram til det samme. Alle profiler som har samme felles kanal, ser derfor det samme samtidig. En personlig kanal har bare én seer. Direktekanaler (kap. 9) har ingen tidslinje: de viser NRK-sendingen slik den går.

Kanal «Ingrids kanal», sendedøgnet søndag 4. oktober (enkeltv.ingrid-kveld) 06:00 – første innslag runde 2 runde 3 06:0007:0008:0009:0010:00 Bergensbanen Fleksnes Gammeldans Blomster Bergensbanen Fleksnes Gammeldans Blomster Bergensbanen 09:12 ny versjon: «Gammeldans» spilles ferdig, så gjelder den nye lista Ingrid ser på: direkte fra 07:10 samtale 12 min etter direkte 07:40 samtalen pauser alt; 07:52 fortsetter TV-en der den stoppet 08:30 kanalbytte: tilbake på direkte YouTube lysbilder NRK Spillelisten er 106 min og 44 s lang og går i løkke til kl. 06:00 neste morgen.
Sendedøgnet starter kl. 06:00 med første innslag. En samtale fryser bildet; etterpå ligger kanalen like mye etter direkte som samtalen varte, til man bytter kanal. En ny versjon av spillelisten tar over når innslaget som går, er ferdig.

Sendedøgnet og regnestykket

Sendedøgnet med dato D begynner D kl. 06:00 og slutter D+1 kl. 06:00, norsk tid (Europe/Oslo). Mellom midnatt og kl. 06:00 hører natten til gårsdagens sendedøgn. Starttidspunktet regnes ut med tidssonen, så døgnet er 25 timer når klokka stilles tilbake om høsten (sendedøgnet lørdag 24. oktober 2026 varer til søndag kl. 06:00 vintertid) og 23 timer når den stilles fram om våren (sendedøgnet lørdag 27. mars 2027).

dagStart = D kl. 06:00 norsk tid (som UTC-tidspunkt)          dagSlutt = D+1 kl. 06:00
L        = summen av duration for dagens innslag (sekunder)
runde    = gulv((t − dagStart) / L)                          t = tidspunktet vi spør om
pos      = t − dagStart − runde · L                          hvor langt inn i løkkerunden
innslag  = det første innslaget som slutter etter pos        offset = pos − innslagets start

Leverandøren ser resultatet med GET /api/publish/channels/{id}/schedule?date=YYYY-MM-DD – alle innslag i sendedøgnet, runde for runde (kap. 16).

Når endringer når TV-en

TV-en spør serveren hvert 2. sekund om noe er endret. Endres noe i biblioteket, standardoppsettet eller profilens kanaloppsett, henter TV-en kanalene på nytt – en endring når altså TV-en innen omtrent 2 sekunder. Det samme skjer når et nytt sendedøgn begynner, og ved midnatt UTC (da fornyes adressene til mediefilene, kap. 8). Er TV-en på en kanal, gjør den så dette:

SituasjonTV-en …
Et nytt sendedøgn har begynt… stiller inn kanalen på nytt med en gang, på direkte, med det nye døgnets spilleliste. Finnes plassen ikke lenger, går den til plass 0.
Plassen finnes ikke lenger (færre kanaler)… går til klokkeskjermen (plass 0).
Plassen har fått en annen kanal, eller kanalen har fått eller mistet innhold… stiller inn plassen på nytt med en gang, på direkte.
Innslaget som spiller, finnes ikke lenger (ingen innslag med samme id)… avslutter det (reason: replaced) og starter det som går nå etter den nye lista.
Alt annet – f.eks. ny versjon av spillelisten eller serien… spiller innslaget ferdig. Ved slutten regnes neste innslag ut av den nye lista ved det klokkeslettet – det kan være midt i et innslag, fordi hele tidslinjen regnes fra kl. 06:00 med de nye lengdene. Navn, farge og beskrivelse oppdateres med en gang.

Samtaler

Når en pårørende ringer, fryser TV-en avspillingen der den står – YouTube, NRK og lysbilder, med lyd, video og animasjoner – fra det begynner å ringe til TV-en er tilbake fra samtaleskjermen. Etterpå fortsetter innslaget akkurat der det stoppet. Kanalen ligger da like mye etter direkte som pausen varte, og neste innslag starter tilsvarende senere; klokkeslettene i banneret er forskjøvet like mye. Bytter man kanal, er man tilbake på direkte og forsinkelsen er borte. Fjernkontrollen bytter ikke kanal mens samtalen pågår. Pausen gir hendelsene playback.paused og playback.resumed (med pausedSeconds).

Når man kommer inn på en kanal

TV-en starter innslaget som går nå, så langt inn som sendingen har kommet (offset). Innhopp mindre enn 2 sekunder etter starten regnes som fra start (joined: start), ellers joined: middle.

InnslagKommer man inn midt i et innslag …
YouTube… starter videoen på start + offset sekunder.
NRK opptak… henter TV-en avspillingsadressen hos NRK og starter på start + offset, pluss tiden hentingen tok.
NRK direktekanal… spilles direktesendingen som den er, uten å hoppe.
Lysbilder… vises lysbildet som går nå, med lag, toning og zoom slik de er på det tidspunktet. Lydklipp som begynte mer enn 0,25 s tidligere, hoppes over, så en stemme aldri starter midt i en setning; senere klipp kommer på sin tid. Musikken starter på offset modulo musikkens lengde. Videolag hopper til riktig sted.

12Slik spiller TV-en av

TV-appen logger inn med brukernavn og boks-ID og starter på klokkeskjermen (plass 0). Den henter profilens kanaler og dagens spillelister fra serveren, og kanalvelgeren eier tiden: den finner innslaget som går, starter riktig avspiller midt i det, planlegger slutten og går videre. Avspillerne bytter aldri innslag selv.

AvspillerHvordan
YouTubeYouTubes innebygde spiller i fullskjerm, uten knapper, forslag eller annen YouTube-visning. Miniatyrbildet vises bak spilleren mens videoen laster.
LysbilderTV-appens egen tegner. Den bygger skjermbildet av lagene i JSON-en på et 16:9-lerret med svarte kanter (kap. 6), og laster bildene til neste lysbilde i forkant.
NRKHenter avspillingsadressen hos NRK rett før avspilling (kap. 5), og for direktekanaler strømmen til kanalen (kap. 9). Spiller HLS-strømmen i et <video>-element med hls.js, med undertekster fra subtitlesUrl. Går det ikke å spille, vises plassholderen (se under).

TV-en og appene bruker egne, interne endepunkter som ikke er en del av leverandør-API-et (kap. 18).

Når noe ikke kan spilles

Serveren tar aldri innslag ut av tidslinjen av seg selv. TV-en viser noe rolig – aldri en svart skjerm eller en feilmelding – melder feilen som playback.error (kap. 13), og tidslinjen går videre som før. Feil som kan gå over av seg selv, prøver TV-en igjen, på riktig sted i tidslinjen:

FeilTV-en …
YouTube nettverksfeil (YouTube kan ikke lastes, eller videoen står stille i 60 s)… viser pausebildet med kanalnavn, klokke og «Neste kl. …» og avslutter innslaget med reason: error. Prøver igjen etter omtrent 30 sekunder, så sjeldnere, høyst hvert 5. minutt – og med en gang nettet er tilbake eller en samtale er over.
YouTube andre feil (fjernet, privat, ikke tillatt innbygging)… som over, men prøver igjen etter 5 minutter, så sjeldnere, høyst hvert 30. minutt. Hvert nytt forsøk gir en ny item.started (joined: middle), og en ny item.ended med reason: error hvis det feiler igjen.
NRK program… viser plassholderen med tittel, serie og episode, beskrivelse og programbilde («Programmet kan ikke vises akkurat nå»). Nettverksfeil prøves igjen i samme innslag (ingen ny item.started), etter omtrent 30 sekunder og så sjeldnere, og playback.error meldes én gang per brudd. media-missing og not-available står resten av innslaget.
NRK direktekanal… viser «Sendingen kommer straks tilbake» og prøver igjen etter 15 sekunder, så sjeldnere, høyst hvert 5. minutt.
Lysbilder med en fil som mangler… skjuler bildet eller hopper over lydklippet, og serien fortsetter. Hver fil meldes én gang per innslag som media-missing med src og slideId (musikken uten slideId).
Ukjent innslagstype… (fra en nyere server enn TV-appen) viser pausebildet resten av innslaget og melder kind: unsupported.
Mediet slutter før tiden… (videoen er kortere enn duration) viser pausebildet til innslagets tid er ute. Er mediet lengre, avbrytes det når neste innslag begynner.

details i playback.error kan være stalled (avspillingen sto stille: NRK 45 s, YouTube 60 s), «Får ikke kontakt med NRK (nett eller CORS): …», «NRK svarte ikke» (NRK svarte ikke innen 15 s), feilkoden fra hls.js eller beskjeden fra NRK.

Del D · Innsikt og personvern

13Hendelser og historikk fra TV-en

TV-en melder hva som skjer på skjermen: hvilke kanaler og innslag som ble vist, hvor lenge, når man byttet, og når en samtale avbrøt. Serveren melder selv samtaler, endringer i kanaloppsettet og når TV-en kommer på nett. Hendelsene hører til profilen, og alle lagres. Leverandøren henter dem på to måter:

KallGir
GET /profiles/{ref}/historyHistorikken for én profil: visninger (fra kanalbytte til kanalbytte) med innslagene som ble sett, hvor lenge, og avbrudd av samtaler. Det enkleste stedet å begynne.
GET /eventsRå hendelser for alle egne profiler, i den rekkefølgen serveren tok imot dem, med en markør så du kan hente bare nye.

Hvordan hendelsene kommer inn

Felles konvolutt enkeltv-event/1

FeltBeskrivelse
idUnik per hendelse (uuid), laget der hendelsen oppstod. 8–64 tegn
typeHendelsestypen, se tabellen
atNår det skjedde (ISO 8601), etter serverens klokke
receivedAtNår serveren tok imot hendelsen
profileProfilens pseudonym (p_…)
viewIdKnytter sammen alt i én visning, fra channel.entered til channel.left
slot, channelIdKanalnummeret på profilen og kanalen i biblioteket. Klokkeskjermen er plass 0 med channelId: null
playlistId, playlistVersionHvilken spilleliste og versjon som gikk
itemId, kindInnslaget og typen
dataFelt som er spesielle for hendelsestypen

Felt som ikke gjelder, er null. Serverens hendelser og tv.started, sound.unlocked og remote.pressed har ingen visningsfelt.

Hendelsestyper

TypeNårdata
Fra TV-en
tv.startedTV-appen har logget innappVersion, screen (f.eks. "1920x1080")
channel.enteredEn plass stilles innfromSlot (null ved oppstart), via: startup · remote · lineup-changed · day-changed; source: profile · default · fallback · builtin · clock (plass 0)
channel.leftPlassen forlatesseconds, toSlot (null ved off), reason: remote · lineup-changed · day-changed · off (utlogging, siden lukkes eller lastes på nytt)
item.startedEt innslag begynner på skjermenoffsetSeconds (hvor langt inn), joined: start · middle, title, tags
item.endedInnslaget slutter eller forlateswatchedSeconds (uten pauser), itemSeconds, completed (spilt til slutt uten feil), reason: finished · channel-change · off · error · replaced
slide.shownEt lysbilde er vist ferdigslideshowId, slideshowVersion, slideId, seconds, audioPlayed
playback.pausedEn samtale ringer mens en kanal vises (ikke på klokkeskjermen, plass 0)reason: call
playback.resumedSamtalen er over og TV-en er tilbake på venteskjermen (vanligvis noen sekunder etter call.ended)reason: call, pausedSeconds
playback.startedMediet spiller faktisk: første bilde etter lasting (foreløpig bare NRK). Kommer igjen når en direktekanal har startet på nytt etter et utfallengine: hls.js · native-hls · file; live (sann/usann), startupMs (ms fra lasting til spilling), mediaSeconds (lengden på opptaket, ikke for direkte)
playback.stalledAvspillingen stoppet opp for å laste og fortsatte igjen (foreløpig bare NRK). Bare opphold på minst 1 s, høyst 20 per innslagseconds, positionSeconds
playback.errorNoe kunne ikke spilleskind: media-missing · not-embeddable · unsupported · network · not-available (NRK sier at programmet ikke kan spilles nå, f.eks. utløpt eller geoblokkert); src, slideId og details (tekst ≤ 200 tegn, f.eks. feilkoden fra hls.js eller NRK) når de er kjent
sound.mutedNettleseren nektet lyd–
sound.unlockedFørste tastetrykk slo på lyden–
remote.pressedTastetrykkkey: up · down · other
Fra serveren
tv.onlineTV-en har kontakt med serveren igjen etter minst 10 s utenofflineSeconds (null første gang)
call.incomingEn pårørende ringercallId
call.answeredTV-en svartecallId, mode: auto · manual
call.endedSamtalen er overcallId, status: completed · missed · cancelled · declined · failed · dropped; seconds (samtaletid)
lineup.changedProfilens kanaloppsett ble endretby: provider · staff, slots (plassene som ble endret), channelCount

Leverandører får aldri vite hvem som ringte – bare at og hvor lenge, fordi samtaler forklarer avbrudd. Bilder fra «titt inn» er aldri hendelser. Det finnes ingen egen hendelse for at TV-en går av nett; hullet ses som tv.online med offlineSeconds. Samtaler som er i gang, overlever en omstart av serveren; et anrop som ringer akkurat da, blir missed uten call.ended.

tv.started har appVersion = de 12 første tegnene i versjonen av TV-appen, så du ser når TV-en har fått ny programvare. Etter en omlasting (kap. 12) har channel.entered med via: startup plassen TV-en sto på før, ikke 0.

Direktekanaler (kap. 9) sender de vanlige innslagshendelsene, med kind: "nrk", itemId live.nrk1 (osv.) og playlistId og playlistVersion null. item.started har title = kanalnavnet og tags: ["direkte"], og item.ended har itemSeconds: null.

Eksempel: en kveld på én profil

TidTypeUtdrag
17:10:02channel.enteredplass 6 «Ingrids kanal», via: remote, fromSlot: 5, source: profile
17:10:03item.startedyt.bergensbanen, offsetSeconds: 600, joined: middle
17:30:00item.endedwatchedSeconds: 1197, completed: true, reason: finished
17:30:00item.startedlysbilder.bergen-1950, joined: start
17:30:14slide.showntorget, seconds: 14, audioPlayed: true
17:30:14item.endedwatchedSeconds: 14, completed: true
17:50:41call.incoming → playback.pausedsamtalen ringer, alt fryses
18:03:20call.ended → playback.resumedpausedSeconds: 759 – fortsetter der det stoppet
18:05:12channel.leftseconds: 3310, toSlot: 7, reason: remote
{
  "id": "5f0c9a7e-2b1d-4c43-9a51-7d0e3b6f2a18",
  "type": "item.ended",
  "at": "2026-10-05T15:30:14.204Z",
  "receivedAt": "2026-10-05T15:31:02.517Z",
  "profile": "p_7Kq2mPx9Qa",
  "viewId": "0b6e3f4a-91c2-4d7e-8f55-2a9c1e7d4b03",
  "slot": 6, "channelId": "enkeltv.ingrids-kanal",
  "playlistId": "enkeltv.ingrid-kveld-uke41", "playlistVersion": 3,
  "itemId": "lysbilder.bergen-1950", "kind": "slideshow",
  "data": { "watchedSeconds": 14, "itemSeconds": 14, "completed": true, "reason": "finished" }
}

Historikken: GET /profiles/{ref}/history?from=…&to=…

from og to er ISO 8601-tidspunkter (standard: de siste 24 timene, høyst 31 dager per kall). Hver visning går fra channel.entered til channel.left og har innslagene med sett tid (watchedSeconds, uten pauser) og completed, og avbrudd av samtaler (interruptions).

{
  "profile": "p_7Kq2mPx9Qa",
  "from": "2026-10-05T00:00:00.000Z",
  "to": "2026-10-06T00:00:00.000Z",
  "views": [
    { "viewId": "0b6e3f4a-91c2-4d7e-8f55-2a9c1e7d4b03", "slot": 6,
      "channelId": "enkeltv.ingrids-kanal",
      "from": "2026-10-05T15:10:02.000Z", "to": "2026-10-05T16:05:12.000Z", "seconds": 3310,
      "items": [
        { "itemId": "yt.bergensbanen", "kind": "youtube", "title": "Bergensbanen",
          "watchedSeconds": 1197, "completed": true },
        { "itemId": "lysbilder.bergen-1950", "kind": "slideshow", "title": "Bergen på 1950-tallet",
          "watchedSeconds": 14, "completed": true }
      ],
      "interruptions": [ { "type": "call", "from": "2026-10-05T15:50:41.000Z", "seconds": 759 } ] }
  ]
}

Rå hendelser: GET /events

Parametre: since (ISO-tidspunkt: mottatt fra og med), after (markøren next fra forrige svar), types (kommaseparert liste med hendelsestyper), profile (én ref), limit (standard 500, høyst 5000). Hendelsene kommer i den rekkefølgen serveren tok imot dem. Lagre next og send den som after neste gang – da mister du ingen og får ingen to ganger. Er hasMore sann, er det flere å hente nå. Er det ingen nye, er next den samme som du sendte.

GET /api/publish/events?since=2026-10-05T00:00:00Z&types=item.ended,channel.left&limit=500
{ "events": [ … ], "next": "184213", "hasMore": false }

GET /api/publish/events?after=184213
{ "events": [ … ], "next": "184377", "hasMore": false }

Tidspunkter i adressen (since, from, to): bruk Z (2026-10-05T04:00:00Z), eller kod + som %2B (2026-10-05T06:00:00%2B02:00). Et ukodet + foran tidssonen, som blir til mellomrom, godtas også.

Lagring og hvem som ser hva

14Profilen: bio, notater og bilder

Godt innhold for en person med demens tar utgangspunkt i livet personen har levd. Leverandøren intervjuer de pårørende utenfor systemet. I systemet bygges profilen opp av personalet og familien, og leverandøren kan lese den – bare når eieren av boksen har samtykket.

DelHvemHvorInnhold
BioPersonaletAdminFaste felt og en kort, nøytral livshistorie fra kartleggingen med de pårørende. Fanen «Bio» under «Innhold og profil» på TV-siden
NotaterFamilienMobilappenKorte opplysninger når noe har skjedd, f.eks. hvordan personen reagerte på et program. «Fortell om …» på boksen
BilderFamilienMobilappenBilder med bildetekst, år og sted – f.eks. til personlige lysbildeserier
SamtykkeEierenMobilappenHva leverandøren får se: bio, notater og bilder, hver for seg

Hvem kan bruke det familien sender? (share)

For hvert notat og bilde velger den som sender, hvem som kan bruke det. Skjermen viser en kort veiledning om å skrive nøytrale opplysninger, og bildene gjøres mindre før de sendes.

Fortell om Ingrid
Hjelp oss å finne innhold Ingrid liker
Fortell om noe som har skjedd, eller noe Ingrid er glad i. Enkel-TV lager innholdet på TV-en. Du har latt dem se bio, notater og bilder.
Skriv et notat

Skriv nøytrale opplysninger som kan hjelpe oss å finne innhold Ingrid liker – minner, interesser eller noe som har skjedd. Ikke skriv diagnoser, medisiner eller navn og opplysninger om andre.

Notat
Hun smilte og nynnet med da «Liten fuggel» gikk på TV-en i går.
63 / 2000
Hvem kan bruke dette?
Innholdsleverandøren (også deres KI)Enkel-TV kan bruke det til å lage innhold som passer.
Bare personaletAldri leverandøren, og aldri KI.
Send notatet
Send et bilde
Velg eller ta et bilde
Bildet gjøres mindre, og posisjon og kamerainformasjon fjernes før det sendes.
Bildetekst
Ingrid i hagen på Florø
År · Sted
1965 · Florø
De som er med på bildet, er enige i at det deles.
Hva Enkel-TV får se (bare eieren)
BioLivshistorien personalet har skrevet sammen med dere: steder, arbeid, interesser, musikk og hva som bør unngås.
NotaterNotater dere har valgt å dele med leverandøren.
BilderBilder dere har valgt å dele. De kan brukes i lysbilder som bare Ingrid ser.
shareBetyr
provider«Innholdsleverandøren (også deres KI)» – forhåndsvalgt i appen. Leverandøren får det når eieren har samtykket til kategorien (notater eller bilder). Til da ser bare personalet det
staff«Bare personalet». Aldri leverandøren, og aldri KI

Bildet skaleres i telefonen til høyst 2048 piksler på den lengste siden og lagres som JPEG, så posisjon og kamerainformasjon (EXIF) ikke blir med. Den som sendte et bidrag, og eieren, kan slette det i appen; personalet kan slette alle bidrag i admin («Fra familien»). Et slettet bidrag forsvinner fra /notes og /photos for leverandøren; et bilde som alt er brukt i en lysbildeserie, blir stående i serien til leverandøren fjerner det (kap. 8).

Samtykke

Bytte av leverandør

Toppledelsen velger leverandør på TV-siden i admin. Den forrige leverandøren mister med en gang tilgangen til profilens kanaloppsett, hendelser og profildata (en nøkkel med events:all kan fortsatt lese kanaloppsett, «nå», hendelser og historikk, men ikke bio, notater og bilder). Den kan verken lage nye eller endre eksisterende personlige objekter for profilen, bare slette dem som ikke er i bruk. Personlige kanaler, spillelister og serier den forrige leverandøren har laget, blir liggende og eies fortsatt av den; plasser i kanaloppsettet som peker på dem, vises til noen endrer dem. En ny profil har ingen leverandør før toppledelsen velger en; til da er den ikke med i GET /profiles, og alle kall for den gir 404 no-profile.

Bioen – format enkeltv-profile/1

Alle felt er valgfrie. Personalet lagrer hele bioen på én gang i admin; ukjente felt fjernes og tomme lister utelates.

FeltTypeEksempel og formål
callNametekst ≤ 60«Ingrid» – hva en fortellerstemme kan kalle personen. Bare fornavn eller kallenavn
birthYear1900–20301938 – gir hvilke tiår som var barndom og ungdom
language, dialecttekst ≤ 10, ≤ 60nb og «bergensk»
placesliste«Bergen (vokste opp)», «Florø»
workliste«sykepleier»
interestsliste«tog», «hagearbeid», «kaffe på trappa»
musicliste«gammeldans», «salmer», «allsang»
avoidlisteTema som kan uroe: «krig», «sykehus». Leverandøren skal respektere dette
adaptationslistelarge-text · slow-speech · subtitles · calm-pace – tilpasninger, ikke diagnoser
summarytekst ≤ 1000Kort, nøytral livshistorie. Ingen diagnoser, medisiner eller navn på andre

Listene (places, work, interests, music, avoid) har høyst 30 oppføringer à 80 tegn, uten like oppføringer.

GET /api/publish/profiles/p_7Kq2mPx9Qa/bio
{
  "format": "enkeltv-profile/1",
  "profile": "p_7Kq2mPx9Qa",
  "updatedAt": "2026-09-24T09:14:00.000Z",
  "bio": {
    "callName": "Ingrid", "birthYear": 1938, "language": "nb", "dialect": "bergensk",
    "places": ["Bergen (vokste opp)", "Florø"], "work": ["sykepleier"],
    "interests": ["tog", "hagearbeid", "kaffe på trappa"],
    "music": ["gammeldans", "salmer", "allsang"], "avoid": ["krig", "sykehus"],
    "adaptations": ["large-text", "slow-speech"],
    "summary": "Vokste opp i Bergen og arbeidet i mange år som sykepleier i Florø. …"
  }
}

GET /api/publish/profiles/p_7Kq2mPx9Qa/notes?after=0
{ "notes": [
  { "id": 1, "by": "family", "at": "2026-09-28T18:40:00.000Z",
    "text": "Mamma ble oldemor forrige uke og snakker mye om babyen. …" }
] }

GET /api/publish/profiles/p_7Kq2mPx9Qa/photos
{ "photos": [
  { "id": 4, "caption": "Ingrid i hagen på Florø", "year": 1965, "place": "Florø",
    "width": 2048, "height": 1365, "at": "2026-10-03T19:02:00.000Z",
    "sha256": "d7a13d148eb05ea057024bef468d20e3987a8d0f91e1452050930c319dc6e6c3",
    "url": "/media/d7a13d148eb05ea057024bef468d20e3987a8d0f91e1452050930c319dc6e6c3.jpg?e=…&s=…" }
] }

15Personvern

Bare det at noen bruker en tjeneste for personer med demens, kan være en helseopplysning (GDPR art. 9). Seervaner, bio, notater og bilder er derfor sensitive, også når de er pseudonyme.

Deles aldri med leverandører

Del E · Referanse

16Endepunkter for leverandører

Grunnadresse: https://<server>/api/publish, og nøkkelen sjekkes med GET https://<server>/api/publish. Én skråstrek til slutt godtas på alle adresser (/api/publish/ er det samme som /api/publish), men to gir 404. Alle kall krever API-nøkkel (kap. 3). Kropper er JSON: høyst 1 MB for kanaler, spillelister og lysbildeserier, og 512 KB for andre kall. Lysbildepakker (zip) kan være høyst 200 MB. Dette er hele leverandør-API-et; alle feilkodene står i kap. 3.

KallRettighetGjør
Grunnleggende – adressene under er relative til grunnadressen
GET /api/publish–Nøkkelen: leverandør, navn, rettigheter, formater, grenser og sendedøgn
GET /profiles–Egne profiler (alle med events:all): ref, på/av, sist sett, antall kanaler, samtykke
Kanaloppsett
GET /profiles/{ref}/lineuplineups:writeLes kanaloppsettet (kap. 10)
PUT /profiles/{ref}/lineuplineups:writeErstatt hele oppsettet (format og slots påkrevd)
PATCH /profiles/{ref}/lineuplineups:writeEndre enkeltplasser eller antallet
GET /profiles/{ref}/nowlineups:writeHva som går på hver plass akkurat nå
GET /lineups/default–Standardoppsettet og reserveprogrammet
PUT /lineups/defaultlineups:defaultErstatt standardoppsettet
Bibliotek – {type} er channels, playlists eller slideshows
GET /{type}–Felles objekter og egne personlige, som sammendrag
GET /{type}/{id}–Det lagrede dokumentet, med versjon, leverandør og tidspunkter
PUT /{type}/{id}library:writeLag eller endre. slideshows tar application/zip eller application/json
DELETE /{type}/{id}library:writeSlett eget objekt som ikke er i bruk → { "ok": true }
POST /channels/{id}/playlistslibrary:writePubliser en spilleliste til egen kanal: { playlistId, from?, to? }, standard fra i dag og til en nyere kommer (kap. 9). Direktekanaler gir 409 live-channel
GET /channels/{id}/schedule–Sendeplanen for et sendedøgn (?date=YYYY-MM-DD, standard i dag; kap. 11)
POST /media/checklibrary:writeHvilke av høyst 1000 filer serveren har (kap. 7)
Innsikt
GET /eventsevents:read / allHendelser i den rekkefølgen serveren tok imot dem, bladd med markør (kap. 13)
GET /profiles/{ref}/historyevents:read / allHistorikken: visninger med innslag og avbrudd – lettere å lese enn rå hendelser (kap. 13)
Profil (bare egne profiler, med samtykke)
GET /profiles/{ref}/bioprofiles:readBioen (kap. 14)
GET /profiles/{ref}/notesprofiles:readNotater merket provider; ?after=<id> gir bare nyere
GET /profiles/{ref}/photosphotos:readFamiliebilder merket provider, med signerte adresser

Lagring av kanaler, spillelister og lysbildeserier med PUT er idempotent: samme innhold to ganger gir samme versjon og "changed": false. Et kanaloppsett (PUT/PATCH, også standardoppsettet) uten endringer lagres ikke og gir ingen lineup.changed. Svaret er da oppsettet slik det var, uten changed.

GET /api/publish og GET /api/publish/profiles

{
  "provider": { "id": 1, "name": "Enkel-TV" },
  "key": "Produksjon, oktober 2026",
  "scopes": ["library:write", "lineups:write", "events:read"],
  "formats": { "lineup": "enkeltv-lineup/1", "channel": "enkeltv-channel/1",
               "playlist": "enkeltv-playlist/1", "slideshow": "enkeltv-slideshow/1",
               "event": "enkeltv-event/1", "profile": "enkeltv-profile/1" },
  "limits": { "jsonBytes": 1048576, "slideshowBytes": 209715200 },
  "broadcastDay": { "startsAt": "06:00", "timeZone": "Europe/Oslo" }
}

[
  { "ref": "p_7Kq2mPx9Qa", "online": true, "lastSeenAt": "2026-10-05T16:52:01.000Z",
    "channelCount": 15, "consent": { "bio": true, "notes": true, "photos": true } },
  { "ref": "p_R2v9xTq4Lm", "online": false, "lastSeenAt": "2026-10-04T19:10:44.000Z",
    "channelCount": 13, "consent": { "bio": false, "notes": false, "photos": false } }
]

online betyr at TV-en har hatt kontakt med serveren de siste 10 sekundene, og lastSeenAt er siste kontakt. Biblioteklistene gir sammendrag med id, visibility, profile (bare for egne personlige), version, provider, mine og updatedAt, pluss name, description, color, tags og type (loop eller live) (kanaler), title og antall items (spillelister), eller title, duration, slides og bytes (serier).

GET /profiles/{ref}/now

{
  "profile": "p_7Kq2mPx9Qa",
  "at": "2026-10-05T15:31:20.000Z",
  "showing": { "slot": 6, "since": "2026-10-05T15:10:02.000Z" },
  "slots": [
    { "slot": 1, "channelId": "nrk1", "name": "NRK1", "source": "default",
      "live": { "nrkChannel": "nrk1" },
      "kind": null, "itemId": null, "title": null, "startedAt": null, "endsAt": null },
    { "slot": 4, "channelId": "sporet", "name": "Sporet", "source": "default", "live": null,
      "kind": "youtube", "itemId": "yt.mv47Hexb4jY", "title": "Nordlandsbanen om vinteren",
      "startedAt": "2026-10-05T14:05:51.000Z", "endsAt": "2026-10-05T16:20:12.000Z" },
    { "slot": 6, "channelId": "enkeltv.ingrids-kanal", "name": "Ingrids kanal", "source": "profile",
      "live": null, "kind": "youtube", "itemId": "yt.ZlGyCfigmFg", "title": "Allsang: Liten fuggel",
      "startedAt": "2026-10-05T15:30:14.000Z", "endsAt": "2026-10-05T15:33:42.000Z" }
  ]
}

Alle plasser er med. «Nå» er direkte – uten forsinkelse etter samtaler – og endsAt er aldri etter sendedøgnets slutt. For en direktekanal er live { "nrkChannel": … } og kind, itemId, title, startedAt og endsAt null; ellers er live null. showing (plassen som vises nå) er bare med når nøkkelen også har events:read eller events:all og TV-en er på; ellers null.

GET /channels/{id}/schedule?date=2026-10-05

{
  "channelId": "enkeltv.ingrids-kanal",
  "date": "2026-10-05",
  "start": "2026-10-05T04:00:00.000Z",
  "end": "2026-10-06T04:00:00.000Z",
  "live": null,
  "playlistId": "enkeltv.ingrid-kveld-uke41",
  "playlistVersion": 1,
  "entries": [
    { "index": 0, "itemId": "yt.bergensbanen", "kind": "youtube", "title": "Bergensbanen",
      "duration": 1800,
      "startsAt": "2026-10-05T04:00:00.000Z", "endsAt": "2026-10-05T04:30:00.000Z" },
    { "index": 1, "itemId": "lysbilder.bergen-1950", "kind": "slideshow",
      "title": "Bergen på 1950-tallet", "duration": 14,
      "startsAt": "2026-10-05T04:30:00.000Z", "endsAt": "2026-10-05T04:30:14.000Z" },
    …
  ],
  "truncated": false
}

Alle løkkerunder i sendedøgnet, høyst 5000 oppføringer (truncated: true hvis det er flere). index er plassen i spillelisten. Det siste innslaget får endsAt = end, siden det avbrytes når sendedøgnet slutter. Har dagens spilleliste ingen innslag som er tilgjengelige den dagen, er entries tom, men playlistId er satt. Har kanalen ingen spilleliste den dagen, er entries tom og playlistId: null. For en direktekanal er live { "nrkChannel": "nrk1" } (osv.), entries tom og playlistId: null; ellers er live null.

Sletting av noe som er i bruk

DELETE /api/publish/playlists/enkeltv.allsang
HTTP/1.1 409 Conflict
{ "error": "Spillelisten «enkeltv.allsang» er i bruk og kan ikke slettes (channel:allsang)",
  "code": "in-use", "usedBy": ["channel:allsang"] }

17Eksempel: en personlig kanal for Ingrid

Ingrid (profil p_7Kq2mPx9Qa i eksempelet) har leverandøren Enkel-TV. Hun har allerede den personlige kanalen enkeltv.ingrids-kanal på plass 6, med spillelisten enkeltv.ingrid-kveld. Slik lager leverandøren ukens spilleliste og forbedrer den, bare med API-et.

#StegKall
1Finn profilene og se hva eieren har samtykket tilGET /profiles
2Les bioen og notatene: Bergen, tog og gammeldans, unngå krig og sykehus, og et notat om at hun blir glad av gamle familiebilderGET /profiles/p_7Kq2mPx9Qa/bio, /notes
3Lag en personlig lysbildeserie med gamle Bergen-bilder og rolig opplesning, og last den opp som pakkePUT /slideshows/enkeltv.ingrid-bergen-1950
4Lag ukens spilleliste: en halvtime av Bergensbanen, lysbildeserien, allsang og en episode av Fleksnes (kap. 5)PUT /playlists/enkeltv.ingrid-kveld-uke41
5Publiser spillelisten til kanalen for uke 41: { "playlistId": "enkeltv.ingrid-kveld-uke41", "from": "2026-10-05", "to": "2026-10-11" } (kap. 9)POST /channels/enkeltv.ingrids-kanal/playlists
6Sjekk sendeplanen for mandagGET /channels/…/schedule?date=2026-10-05
7Sørg for at kanalen ligger på plass 6PATCH /profiles/p_7Kq2mPx9Qa/lineup
8Mandag kl. 06:00 begynner spillelisten for uke 41 med første innslag. Ingrids TV har hentet den nye versjonen innen et par sekunder etter lagringen–
9Dagen etter: se hva hun så. Lysbildene ble sett til slutt; Fleksnes ble forlatt etter tre minutterGET /profiles/p_7Kq2mPx9Qa/history
10Hent nye notater fra familien siden sistGET /profiles/p_7Kq2mPx9Qa/notes?after=1
11Lag neste ukes spilleliste med mer allsang og uten Fleksnes, og publiser den til kanalen fra mandag 12. oktoberPUT /playlists/…-uke42, POST /channels/…/playlists

Manifestet i pakken fra steg 3 er personlig for Ingrid. En personlig serie er den eneste typen som kan bruke familiebildene hennes ("src": "sha256:…" fra GET /profiles/{ref}/photos, kap. 14; reglene står i kap. 8):

{
  "format": "enkeltv-slideshow/1",
  "title": "Bergen på 1950-tallet – for Ingrid",
  "language": "nb",
  "visibility": "personal",
  "profile": "p_7Kq2mPx9Qa",
  "defaults": { "text": { "style": { "size": 6, "background": "#00000099", "padding": 1.2 } } },
  "slides": [
    { "id": "torget", "duration": 14,
      "layers": [
        { "type": "image", "src": "media/torget-1954.jpg",
          "motion": { "from": { "scale": 1 }, "to": { "scale": 1.12, "x": 42, "y": 55 } } },
        { "type": "text", "text": "Du vokste opp i Bergen, Ingrid.", "x": 5, "y": 78, "w": 90,
          "align": "center", "start": 1, "fadeIn": 1 }
      ],
      "audio": [ { "src": "media/tale/torget.mp3", "start": 1.5, "duration": 9.8 } ],
      "credit": "Mittet & Co. / Nasjonalbiblioteket, 1954" }
  ]
}
curl -X PUT https://<server>/api/publish/slideshows/enkeltv.ingrid-bergen-1950 \
  -H "Authorization: Bearer etv_…" -H "Content-Type: application/zip" \
  --data-binary @enkeltv.ingrid-bergen-1950.zip

curl -X PUT https://<server>/api/publish/playlists/enkeltv.ingrid-kveld-uke41 \
  -H "Authorization: Bearer etv_…" -H "Content-Type: application/json" \
  --data-binary @enkeltv.ingrid-kveld-uke41.json

curl -X PATCH https://<server>/api/publish/profiles/p_7Kq2mPx9Qa/lineup \
  -H "Authorization: Bearer etv_…" -H "Content-Type: application/json" \
  -d '{ "slots": { "6": "enkeltv.ingrids-kanal" } }'

Leverandøren trenger aldri å vite hvem Ingrid er. Den ser bare p_7Kq2mPx9Qa, det eieren har samtykket til, og hva som ble sett – og serveren trenger aldri å vite hvordan innholdet ble valgt.

18Stabilitet og versjonering

Løftet: Feltene, formatene og feilkodene i dette dokumentet endres ikke. Nye, valgfrie felt, nye hendelsestyper og nye feilkoder for nye situasjoner kan komme. Skriv klienten slik at den tåler felt og hendelsestyper den ikke kjenner, og ser på HTTP-status og code – ikke på teksten i error.

Denne dokumentasjonen

Dokumentasjonen ligger på serveren, alltid i samme versjon som API-et: som nettside på https://<server>/docs/ og som PDF på https://<server>/docs/enkeltv-api.pdf.

19Senere

Dette er bevisst ikke bygget ennå. Modellen er laget slik at det kan komme til uten at formatene over må endres.

HvaStatus i dag
Daglige sendeplaner"programming": { "type": "schedule" } med faste klokkeslett avvises med 400. Kanaler bruker loop med én spilleliste per sendedøgn, eller live for NRK direkte.
NRK-avtaleAvspillingen bruker NRK sitt uoffisielle API (PSAPI), som ikke er dokumentert for tredjeparter og kan endres uten varsel. Avtale med NRK mangler fortsatt og må på plass før NRK-innhold brukes i et produkt vi tar betalt for.
Familien velger kanalerKanaloppsettet endres av leverandøren og av personalet i admin, ikke i mobilappen.
Grenser for antall kallAPI-et har ingen slike grenser i dag. Kommer det, blir det 429 med en egen code.
Varsler (webhooks)Serveren varsler ikke leverandøren. Hent nye hendelser med GET /events?after=… så ofte det trengs.