# Live transcriptie

> Stream microfoon- of tabaudio via een WebSocket naar transcript1 en krijg ondertitelregels terug terwijl mensen praten, met optioneel realtime vertaling.

Canonical: https://transcript1.com/nl/docs/live
Language: nl
Last updated: 2026-11-03

Live transcriptie zet een audiostream om in ondertitelregels terwijl mensen nog praten. Je client stuurt ruwe PCM-audio over een WebSocket, en transcript1 stuurt regels terug die eerst een voorlopige schatting zijn en daarna definitief worden. Voeg `translate_to` toe en elke regel bevat ook zijn vertaling. Als de sessie eindigt, wordt het resultaat opgeslagen als een gewone transcriptie.

Er zijn geen aparte live-abonnementen: live transcriptie gebruikt hetzelfde minutensaldo als al het andere. Zie [Facturatie](https://transcript1.com/nl/docs/live#billing) voor hoe verbonden tijd wordt geteld.

## Wanneer gebruik je live

| Gebruik live als | Gebruik [een gewone transcriptie](https://transcript1.com/nl/docs/getting-started) als |
|---|---|
| De audio nog wordt gemaakt: een gesprek, een lezing, een stream, een video die je nu bekijkt. | Je een link of een afgerond bestand hebt. |
| Je binnen een of twee seconden na het gesproken woord ondertitels nodig hebt. | Je de beste nauwkeurigheid wilt en op het resultaat kunt wachten. |
| Je realtime vertaling naast elke regel wilt. | Je een vertaling van een afgerond transcript wilt, of meerdere talen. |

Live accepteert alleen audio, als ruwe PCM. Videolinks, uploads, WebM, Opus en MP3 worden niet geaccepteerd. Neem de audio op in je client en zet hem eerst om naar PCM. [Audio opnemen in een browser](https://transcript1.com/nl/docs/live#capture-audio-in-a-browser) laat zien hoe.

## Snel starten met de SDK

Installeer SDK 1.5.0 of nieuwer: `npm i @transcript1/sdk@latest`. `t1.live.start` maakt de sessie aan, verbindt en wordt afgerond zodra de sessie is gestart, zodat je meteen audio kunt sturen.

```javascript
import { createReadStream } from 'node:fs';
import { setTimeout as sleep } from 'node:timers/promises';
import { Transcript1 } from '@transcript1/sdk';

const t1 = new Transcript1(); // reads TRANSCRIPT1_API_KEY

const live = await t1.live.start({ language: 'en', translateTo: 'es', sampleRate: 16000 });

live.on('line', line => {
  const mark = line.final ? 'final  ' : 'partial';
  console.log(mark, line.text, '→', line.translation?.text ?? '');
});
live.on('warning', warning => console.warn(warning.code));

// speech.pcm: 16 kHz, mono, signed 16-bit little-endian, no header
for await (const chunk of createReadStream('./speech.pcm', { highWaterMark: 3200 })) {
  live.send(chunk);
  await sleep(100); // 3,200 bytes are 100 ms of audio: send at real-time speed
}

const result = await live.end();
console.log(result.reason, result.transcriptionId, result.minutesCharged);
```

Regels worden ter plekke bijgewerkt. Vervang een regel die je al hebt zodra er een met dezelfde `id` binnenkomt, of lees ze allemaal uit `live.lines`. `end()` rondt de sessie af, wacht op de laatste regels en slaat het transcript op. In de [Node.js SDK](https://transcript1.com/nl/docs/sdk#live-transcription) staan alle methoden en events.

## Snel starten met een ruwe WebSocket

Zonder SDK zijn er twee stappen: maak een sessie aan via HTTPS en open daarna de WebSocket met het eenmalige token dat je terugkreeg.

### Een sessie aanmaken

```bash
curl -X POST https://api.transcript1.com/v1/live/sessions \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"language": "en", "translate_to": "es", "sample_rate": 16000}'
```

De API antwoordt met `201`:

```json
{
  "id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "status": "waiting",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "websocket_url": "wss://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD/stream",
  "token": "Zk3x...",
  "token_expires_at": "2026-11-02T12:01:00Z",
  "rate": 1
}
```

`transcription_id` is `null` als `save` op `false` staat. Het token is eenmalig en verloopt na 60 seconden, dus verbind meteen. `rate` is het aantal saldominuten dat per gestarte minuut van deze sessie wordt afgeschreven; zie [Facturatie](https://transcript1.com/nl/docs/live#billing).

De request body accepteert deze velden. Ze zijn allemaal optioneel en de scope is `transcriptions:write`:

| Veld | Standaard | Regel |
|---|---|---|
| `language` | `auto` | `auto`, of een taalcode van twee letters die live transcriptie ondersteunt. |
| `translate_to` | geen | Een taalcode van twee letters. Eén doeltaal per sessie. Zie [Live vertalen](https://transcript1.com/nl/docs/live#translate-live). |
| `diarize` | `false` | Labelt sprekers. Sommige combinaties kunnen geen sprekers labelen, bijvoorbeeld samen met `language: "auto"`, en geven `422`. Controleer de [mogelijkheden](https://transcript1.com/nl/docs/live#capabilities). |
| `audio_format` | `pcm_s16le` | De enige waarde. Het veld bestaat zodat er later formaten bij kunnen komen. |
| `sample_rate` | `16000` | Een geheel getal van 8000 tot 48000. Alleen mono. |
| `save` | `true` | `false` bewaart niets nadat de sessie is afgelopen. Zie [Opgeslagen transcripten](https://transcript1.com/nl/docs/live#saved-transcripts). |

### Verbinden en streamen

Open de WebSocket met het token als tweede subprotocol. Dit werkt in browsers en in Node 22+ zonder eigen headers. Wacht op `session.started` voordat je audio stuurt: binaire frames die eerder worden verstuurd, worden genegeerd en je krijgt een `audio_dropped`-waarschuwing.

```javascript
import { createReadStream } from 'node:fs';
import { setTimeout as sleep } from 'node:timers/promises';

const session = await fetch('https://api.transcript1.com/v1/live/sessions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TRANSCRIPT1_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ language: 'en', translate_to: 'es', sample_rate: 16000 }),
}).then(response => response.json());

const ws = new WebSocket(session.websocket_url, ['t1-live', session.token]);
const lines = new Map();

ws.addEventListener('message', async message => {
  const event = JSON.parse(message.data);

  if (event.type === 'session.started') {
    for await (const chunk of createReadStream('./speech.pcm', { highWaterMark: 3200 })) {
      ws.send(chunk);
      await sleep(100);
    }
    ws.send(JSON.stringify({ type: 'end' }));
  } else if (event.type === 'line') {
    lines.set(event.line.id, event.line); // the full current state: replace by id
    console.log(event.line.final, event.line.text, event.line.translation?.text);
  } else if (event.type === 'session.ended') {
    console.log('ended:', event.reason, event.minutes_charged);
  }
});

ws.addEventListener('close', event => console.log('closed', event.code));
```

Op Node 20 is er geen ingebouwde `WebSocket`: gebruik het pakket `ws`, dat dezelfde vorm `new WebSocket(url, protocols)` heeft.

De server antwoordt op een geldig token met het subprotocol `t1-live` en niets anders. Een ongeldig token laat de handshake nooit mislukken: de socket gaat open, je krijgt een `error`-event en de socket sluit met een 4xxx-code, zodat een browser de echte reden laat zien in plaats van een algemene 1006. Zie [Sluitcodes](https://transcript1.com/nl/docs/live#close-codes).

## Audio opnemen in een browser

Live audio moet 16-bit signed little-endian mono PCM zijn, met de `sample_rate` van de sessie. Browsers leveren samples als drijvende-kommagetallen, dus converteer ze in een `AudioWorklet`. Dit fragment neemt de microfoon op als 16 kHz PCM in brokken van 100 ms en verstuurt die via het browser-instappunt van de SDK. `getAccessToken` geeft een OAuth-toegangstoken terug; zet nooit een API-sleutel in een browser. Zie [Beveiliging](https://transcript1.com/nl/docs/live#security).

```javascript
import { createLiveClient } from '@transcript1/sdk/live';

const workletSource = `
class Pcm16Processor extends AudioWorkletProcessor {
  constructor() {
    super();
    this.buffer = new Int16Array(1600); // 100 ms at 16 kHz
    this.filled = 0;
  }

  process(inputs) {
    const channel = inputs[0][0];
    if (!channel) return true;
    for (let i = 0; i < channel.length; i++) {
      const sample = Math.max(-1, Math.min(1, channel[i]));
      this.buffer[this.filled++] = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
      if (this.filled === this.buffer.length) {
        this.port.postMessage(this.buffer.slice());
        this.filled = 0;
      }
    }
    return true;
  }
}
registerProcessor('pcm16', Pcm16Processor);
`;

const client = createLiveClient({ getAccessToken: () => myApp.getOAuthAccessToken() });

const stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1 } });
const context = new AudioContext({ sampleRate: 16000 });
const moduleUrl = URL.createObjectURL(new Blob([workletSource], { type: 'text/javascript' }));
await context.audioWorklet.addModule(moduleUrl);

// the context's real rate: a browser that cannot do 16 kHz picks its own
const live = await client.start({ language: 'auto', sampleRate: context.sampleRate });
live.on('line', renderLine);

const microphone = context.createMediaStreamSource(stream);
const capture = new AudioWorkletNode(context, 'pcm16');
capture.port.onmessage = event => live.send(event.data); // an Int16Array
const mute = context.createGain();
mute.gain.value = 0;
microphone.connect(capture).connect(mute).connect(context.destination);

async function stop() {
  stream.getTracks().forEach(track => track.stop());
  await context.close();
  return live.end();
}
```

De worklet is geschreven voor 16 kHz. Draait de context op een andere samplefrequentie, dan is een brok niet langer 100 ms, maar de PCM blijft correct omdat `sampleRate` overeenkomt met de context. Om het geluid van een tab of een video op te nemen in plaats van de microfoon, maak je de `MediaStream` met `getDisplayMedia` of met `captureStream()` van een media-element en verbind je hem op dezelfde manier.

## Live vertalen

Voeg `translate_to` toe aan de sessie en transcript1 vertaalt elke regel terwijl hij wordt gesproken.

- Eén doeltaal per sessie. Voeg achteraf meer talen toe aan het opgeslagen transcript met het [vertaal-endpoint](https://transcript1.com/nl/docs/live#saved-transcripts).
- Elke `line` heeft een origineel (`text`, `final`) en een `translation`-object (`language`, `text`, `final`). `translation` is `null` als `translate_to` niet is ingesteld.
- Het origineel en de vertaling worden onafhankelijk van elkaar definitief: een regel kan een definitief origineel hebben terwijl de vertaling nog verandert. Toon een regel pas als definitief als beide dat zijn.
- Een regel die al in de doeltaal is, gebruikt zijn eigen tekst als vertaling en is definitief zodra de regel sluit.
- Een regel sluit als de spreker pauzeert, de spreker wisselt, een pauze tussen woorden langer is dan 0,9 seconden, de regel twee ondertitelrijen (84 tekens) of 15 seconden bereikt, of je `finalize` stuurt. Met vertaling aan kan het ongeveer 3 seconden langer duren voordat een regel sluit, omdat de vertaling nog binnenkomt. In dat geval kan de vertaling open blijven (`final: false`) en blijven groeien nadat het origineel definitief is.
- Vertalen is gratis. Het kost geen minuten.

Welke doeltalen en taalparen beschikbaar zijn, hangt af van de live spraakprovider. Lees de [mogelijkheden](https://transcript1.com/nl/docs/live#capabilities) in plaats van regels hard te coderen. Een niet-ondersteunde doeltaal geeft `422 unsupported_translation`. Een provider kan bijvoorbeeld een expliciete `language` eisen in plaats van `auto`, of alleen paren toestaan waar Engels in zit.

## WebSocket-protocol

### Verbinden

```javascript
new WebSocket(websocket_url, ['t1-live', token]);
```

Het token reist mee als WebSocket-subprotocol. Het komt nooit in een URL of een toegangslog terecht. Stuur geen `Authorization`-header: het token is het inlogbewijs. De server pingt elke 30 seconden; browsers en het pakket `ws` antwoorden automatisch. Een socket die 60 seconden geen pong stuurt, wordt verbroken.

Een geldige verbinding met een sessie die al een socket heeft, vervangt de oude. Zie [Hervatten en overnemen](https://transcript1.com/nl/docs/live#resume-and-takeover).

### Berichten van de client

| Bericht | Betekenis |
|---|---|
| Binair frame | Audio: PCM s16le mono met de `sample_rate` van de sessie. Elk frame heeft een even lengte en is maximaal 256 KB. Ongeveer 100 ms per frame is een goede grootte. |
| `{"type":"finalize"}` | Sluit de huidige regel nu, bijvoorbeeld als de video is gepauzeerd. |
| `{"type":"end"}` | Rond de sessie af en sla hem op. De server antwoordt met `session.ended` en sluit de socket. |
| Al het andere | Een niet-fatale `error` met de code `invalid_message`. |

Een frame met een oneven lengte of dat te groot is, is een protocolschending en sluit de socket met `4008`.

Begin met audio sturen na `session.started`. Een client die sneller dan realtime audio stuurt, betaalt voor de audiotijd in plaats van de verbonden tijd (zie [Facturatie](https://transcript1.com/nl/docs/live#billing)), dus stuur in het tempo waarin de audio wordt gemaakt.

### Server-events

Elk event is een JSON-tekstframe met een `type`.

| Event | Verstuurd wanneer |
|---|---|
| `session.started` | De verbinding is geaccepteerd. Stuur vanaf nu audio. |
| `line` | Een ondertitelregel is aangemaakt of gewijzigd. |
| `usage` | Na elke afschrijving. |
| `warning` | Er is iets dat aandacht nodig heeft, maar de sessie gaat door. |
| `error` | Een niet-fataal probleem, of de reden voor een directe sluiting. |
| `reconnect` | De server gaat zo herstarten. Hervat de sessie. |
| `session.ended` | De sessie is afgelopen en opgeslagen. Het laatste event vóór een normale sluiting. |

`session.started` laat weten of dit een hervatting is en hoeveel audio de sessie al heeft verwerkt:

```json
{
  "type": "session.started",
  "session_id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "resumed": false,
  "offset_ms": 0
}
```

`line` bevat de volledige huidige staat van één regel, dus vervang de regel die je hebt met dezelfde `id`. Zie [Het line-object](https://transcript1.com/nl/docs/live#the-line-object).

```json
{
  "type": "line",
  "line": {
    "id": 12,
    "start_ms": 53120,
    "end_ms": 56040,
    "speaker": null,
    "language": "en",
    "text": "Welcome back to the stream",
    "final": true,
    "translation": { "language": "es", "text": "Bienvenidos de nuevo", "final": false }
  }
}
```

`usage` wordt na elke afschrijving verstuurd, met de verbonden tijd, de audiotijd, het aantal saldominuten dat tot nu toe is afgeschreven en de minuten die je nog hebt:

```json
{
  "type": "usage",
  "connected_ms": 61240,
  "audio_ms": 60100,
  "minutes_charged": 2,
  "minutes_remaining": 898
}
```

`warning` heeft een `code`:

| `code` | Betekenis |
|---|---|
| `balance_low` | De volgende afschrijving zou mislukken. De sessie eindigt met `balance_exhausted` zodra die aan de beurt is. Wordt niet verstuurd als [automatisch opwaarderen](https://transcript1.com/nl/docs/billing-and-limits#auto-top-up) aanstaat. |
| `audio_dropped` | Er kwam audio binnen voordat de sessie klaar was, of er kwam te veel audio binnen terwijl de spraakprovider verbond. De oudste audio is weggegooid. |
| `idle_timeout_soon` | 4 minuten geen audio. De sessie eindigt na 5 minuten zonder audio. Stuur audio om hem aan te houden. |

```json
{ "type": "warning", "code": "idle_timeout_soon" }
```

`error` heeft een `code` en een `message`. Deze codes kunnen op een live socket voorkomen:

| `code` | Fataal | Betekenis |
|---|---|---|
| `invalid_message` | Nee | Een tekstframe dat niet `finalize` of `end` is. |
| `invalid_token` | Ja | Het token is ongeldig, verlopen of al gebruikt. De socket sluit met `4001`. |
| `not_found` | Ja | De sessie bestaat niet. De socket sluit met `4004`. |
| `session_ended` | Ja | De sessie is al afgelopen. De socket sluit met `4004`. |

```json
{ "type": "error", "code": "invalid_message", "message": "Expected {\"type\":\"finalize\"} or {\"type\":\"end\"}" }
```

`reconnect` komt voordat de server de socket sluit met `1012`, bijvoorbeeld tijdens een deploy. Haal een nieuw token op en hervat.

```json
{ "type": "reconnect", "reason": "server_restart" }
```

`session.ended` is het laatste event van elke sessie. De getallen zijn de eindtotalen:

```json
{
  "type": "session.ended",
  "reason": "client_ended",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "minutes_charged": 3,
  "connected_ms": 142500,
  "audio_ms": 141900
}
```

### Het line-object

| Veld | Betekenis |
|---|---|
| `id` | Een 0-gebaseerd nummer dat bij elke regel met één oploopt. Het is de segmentindex in het opgeslagen transcript. |
| `start_ms`, `end_ms` | Begin en einde in de audiotijd van de sessie. Deze tijd loopt door na heraansluitingen. |
| `speaker` | Het sprekerlabel als `diarize` aan staat, anders `null`. De labels zijn dezelfde als in gewone transcripten. |
| `language` | De gedetecteerde of opgegeven taal van de regel, of `null` als die nog niet bekend is. |
| `text` | De originele tekst tot nu toe. |
| `final` | `true` zodra de originele tekst niet meer verandert. |
| `translation` | `null` zonder `translate_to`, anders `language`, `text` en `final`. |

Toon regels met `final: false` in een lichtere stijl: hun tekst kan nog veranderen. De tekst van een regel met `final: true` verandert nooit meer en een definitieve regel wordt nooit verwijderd.

De SDK gebruikt `camelCase` voor regelvelden: `startMs`, `endMs`.

### Eindredenen

`session.ended` en `GET /v1/live/sessions/:id` geven een `end_reason`:

| Reden | Betekenis |
|---|---|
| `client_ended` | Je stuurde `end`. |
| `balance_exhausted` | De volgende afschrijving mislukte. De sessie eindigt op een minuutgrens en wordt opgeslagen. |
| `idle_timeout` | 5 minuten geen audio. |
| `max_duration` | De sessie was 300 minuten verbonden. Start een nieuwe. |
| `disconnected` | De client was langer dan 2 minuten weg zonder te hervatten. |
| `upstream_error` | De spraakprovider faalde. |
| `internal_error` | Er ging iets mis aan onze kant. |
| `not_connected` | De sessie kreeg binnen 5 minuten nooit verbinding. Hij wordt nooit in rekening gebracht en is alleen zichtbaar via `GET`. |

### Sluitcodes

| Code | Betekenis |
|---|---|
| `1000` | Normale sluiting. Komt altijd na `session.ended`. |
| `1011` | Onverwachte serverfout. De server herstelt de sessie: hervat met een nieuw token. |
| `1012` | Serverherstart. Komt na `reconnect`: hervat met een nieuw token. |
| `4001` | Het token is ongeldig, verlopen of al gebruikt. |
| `4004` | De sessie is niet gevonden of is al afgelopen. |
| `4008` | Protocolschending: een binair frame met oneven lengte of dat te groot is. |
| `4010` | Vervangen door een nieuwere verbinding met dezelfde sessie. |

Terwijl de API herstart, kan een nieuw WebSocket-verzoek met HTTP `503` worden beantwoord. Probeer het zo opnieuw.

## Hervatten en overnemen

Een sessie overleeft een verbroken netwerk, een API-deploy en een crash. Na een verbreking wacht hij 2 minuten op je; daarna eindigt hij als `disconnected` en wordt hij opgeslagen.

1. Roep `POST /v1/live/sessions/:id/token` aan. Het antwoord bevat `token`, `token_expires_at` en `websocket_url`, dezelfde velden als bij het aanmaken van de sessie. Het mislukt met `409 session_ended` zodra de sessie is afgelopen.
2. Open een nieuwe WebSocket met dat token.
3. `session.started` komt binnen met `resumed: true`. `offset_ms` is de hoeveelheid audio die de sessie al heeft verwerkt: ga vanaf dat punt verder met de audio. De regel-id's lopen door.

Regels die al definitief waren, blijven bewaard. De onafgeronde staart op het moment van de verbreking gaat verloren, dus speel een paar seconden audio vanaf `offset_ms` opnieuw af als je die buffert. Gaten tijdens het heraansluiten worden niet in rekening gebracht.

Overnemen is dezelfde aanroep. Denkt de server nog dat een socket openstaat, dan vervangt een nieuwe geldige verbinding die, en wordt de oude socket gesloten met `4010` nadat zijn regels zijn opgeslagen. Een hervatting wordt nooit geblokkeerd door de limiet voor gelijktijdige sessies.

De SDK doet dit allemaal voor je: bij een verbreking of een `reconnect`-event haalt hij een token op en verbindt hij opnieuw met een backoff van 0,5 tot 8 seconden, tot 2 minuten lang, en speelt hij de laatste 10 seconden audio die tijdens het gat zijn verstuurd opnieuw af.

## Facturatie

Live transcriptie gebruikt hetzelfde minutensaldo als batchtranscripties.

- **Verbonden tijd wordt in rekening gebracht**, vanaf het moment dat de socket wordt geaccepteerd tot hij sluit. Stilte en een gepauzeerde video tellen mee. De tijd tussen een verbreking en een hervatting niet.
- **Per gestarte minuut, minimaal 1.** De eerste minuut wordt afgeschreven als de eerste verbinding wordt geaccepteerd, en elke volgende minuut als die begint.
- **Sneller dan realtime wordt als audiotijd gerekend.** Als de audio die je stuurt langer is dan de verbonden tijd, wordt de audiotijd gerekend.
- **De rate lees je uit de API.** Eén gestarte minuut kost `rate` saldominuten, naar boven afgerond. De rate is meestal 1 en kan decimalen hebben. Lees de live rate uit `rates.live` in `GET /v1/account`, uit `rate` in `GET /v1/live/capabilities` en uit `rate` in de sessie die je aanmaakt. De rate van een sessie staat vast zodra hij is aangemaakt.
- **Vertalen is gratis.**
- **Nooit verbonden betekent niet in rekening gebracht.** Een sessie die binnen 5 minuten geen verbinding krijgt, eindigt als `not_connected`.
- **Mislukkingen worden terugbetaald.** Als een sessie eindigt met `upstream_error` of `internal_error` voordat hij één regel heeft opgeleverd, worden alle afschrijvingen teruggegeven. Een lege sessie door stilte wordt wel in rekening gebracht en leeg opgeslagen.
- **Als het saldo op is** en [automatisch opwaarderen](https://transcript1.com/nl/docs/billing-and-limits#auto-top-up) aanstaat, belast transcript1 je opgeslagen kaart terwijl de sessie blijft streamen, en gaat de sessie door. Staat automatisch opwaarderen uit of gepauzeerd, of mislukt de afschrijving, dan wordt de sessie afgerond, opgeslagen en beëindigd met `balance_exhausted` op een minuutgrens. De waarschuwing `balance_low` komt eerst, tenzij automatisch opwaarderen aanstaat.

Een sessie aanmaken geeft `402 insufficient_minutes` als je bruikbare saldo lager is dan één gestarte minuut tegen de huidige rate. Staat automatisch opwaarderen aan, dan probeert transcript1 eerst op te waarderen. Zie [Facturatie en limieten](https://transcript1.com/nl/docs/billing-and-limits#live-transcription).

## Limieten

| | Free | Monthly | Annual |
|---|---|---|---|
| Gelijktijdige live sessies | 1 | 2 | 3 |

- Een sessie telt mee zolang hij `waiting`, `connected` of `disconnected` is (binnen zijn venster van 2 minuten). Boven de limiet geeft `POST /v1/live/sessions` `429 too_many_live_sessions` met `Retry-After: 30`.
- Live sessies tellen niet mee voor je limiet voor actieve jobs.
- Een sessie duurt maximaal 300 minuten verbonden tijd. Daarna eindigt hij met `max_duration`.
- Een sessie zonder audio gedurende 5 minuten eindigt met `idle_timeout`; na 4 minuten krijg je `idle_timeout_soon`. Een gepauzeerde stream kost nog steeds verbonden tijd en telt als inactief, dus beëindig de sessie als de pauze lang duurt.
- transcript1 begrenst ook alle live sessies samen. Zijn ze allemaal in gebruik, dan geeft `POST /v1/live/sessions` `503 live_capacity`, wat je opnieuw mag proberen. Wacht op `Retry-After` (30 seconden) en probeer het nog eens. Dit wordt alleen gecontroleerd bij het aanmaken van een sessie, nooit midden in een sessie.
- Sessies aanmaken en tokens ophalen tellen mee voor je requests per minuut.
- Audio is alleen ruwe PCM: `pcm_s16le`, mono, 8000 tot 48000 Hz.

## Mogelijkheden

`GET /v1/live/capabilities` beschrijft wat live transcriptie op dit moment ondersteunt, zodat clients nooit regels van de provider hard hoeven te coderen. De scope is `transcriptions:read`.

```json
{
  "available": true,
  "auto_language": true,
  "languages": ["en", "es"],
  "translation": {
    "targets": ["es", "de"],
    "pairs": "any",
    "requires_language": false
  },
  "diarize": true,
  "audio_formats": ["pcm_s16le"],
  "sample_rate": { "min": 8000, "max": 48000 },
  "max_session_minutes": 300,
  "rate": 1
}
```

De lijsten zijn hier ingekort. Lees ze uit de API.

- `translation.pairs` is `"any"` (elke gedetecteerde taal naar elke doeltaal), of een lijst objecten met `from` en `to`.
- `translation.requires_language` is `true` als vertalen een expliciete `language` nodig heeft in plaats van `auto`.
- Als live transcriptie niet is geconfigureerd, is het antwoord `available: false` met alleen `rate`, en geeft een sessie aanmaken `503 live_unavailable`.

Met de SDK: `await t1.live.capabilities()`.

## Een sessie opvragen

`GET /v1/live/sessions/:id` geeft de status van een sessie terug. De scope is `transcriptions:read`.

```bash
curl https://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY"
```

```json
{
  "id": "lv_8fK2Lm0PqRsTuVwXyZaBcD",
  "status": "ended",
  "end_reason": "client_ended",
  "transcription_id": "tr_3NcQvXy7BtHjKd5ErFgLmA",
  "options": {
    "language": "en",
    "translate_to": "es",
    "diarize": false,
    "audio_format": "pcm_s16le",
    "sample_rate": 16000,
    "save": true
  },
  "connected_ms": 142500,
  "audio_ms": 141900,
  "minutes_charged": 3,
  "rate": 1,
  "created_at": "2026-11-02T11:58:12Z",
  "ended_at": "2026-11-02T12:00:35Z"
}
```

`status` is `waiting`, `connected`, `disconnected` of `ended`. `end_reason` is `null` totdat de sessie is afgelopen. Met de SDK: `await t1.live.get(id)`.

## Fouten

Requestfouten gebruiken de gebruikelijke [foutenvelop](https://transcript1.com/nl/docs/errors). Dit zijn de fouten die `POST /v1/live/sessions` en de bijbehorende endpoints teruggeven:

| HTTP-status | `code` | Opnieuw proberen | Wanneer |
|---|---|---|---|
| 422 | `invalid_request` | Nee | Een veld ontbreekt of is ongeldig, bijvoorbeeld een `sample_rate` buiten 8000 tot 48000. |
| 422 | `unsupported_language` | Nee | `language` wordt niet ondersteund voor live transcriptie. |
| 422 | `unsupported_translation` | Nee | De doeltaal, of het taalpaar, wordt niet ondersteund. |
| 402 | `insufficient_minutes` | Nee | Je bruikbare saldo is lager dan één gestarte minuut tegen de huidige rate. |
| 403 | `email_not_verified` | Nee | Bevestig eerst je e-mailadres. |
| 429 | `too_many_live_sessions` | Ja | De gelijktijdige live sessies van je abonnement zijn in gebruik. Wacht op `Retry-After`. |
| 503 | `live_capacity` | Ja | Alle live capaciteit is in gebruik. Wacht op `Retry-After` (30 seconden). |
| 503 | `live_unavailable` | Nee | Live transcriptie is op dit moment niet beschikbaar. |
| 409 | `session_ended` | Nee | `POST /v1/live/sessions/:id/token` voor een sessie die is afgelopen. |

Problemen op de socket komen binnen als `error`-events en [sluitcodes](https://transcript1.com/nl/docs/live#close-codes).

## Opgeslagen transcripten

Met `save: true` (de standaard) wordt de sessie bij het einde opgeslagen als een gewone transcriptie. De audio wordt nooit bewaard.

- `transcription_id` krijg je terug als je de sessie aanmaakt. De status is `live` zolang de sessie loopt en `completed` erna. Een sessie die nooit verbinding kreeg, laat een transcriptie met status `canceled` achter.
- De transcriptie heeft `source: { "platform": "live", "session_id": "lv_…" }`. Er is geen link of bestand. Het dashboard toont haar als live sessie.
- Elke regel wordt één segment, met dezelfde index als de `id` van de regel. Lees het transcript als JSON, tekst, SRT of VTT zoals bij elke andere transcriptie: zie de [API-referentie](https://transcript1.com/nl/docs/api-reference).
- De live vertaling wordt mee opgeslagen. Lees hem met de parameter `translation`, zoals beschreven bij [Vertalen](https://transcript1.com/nl/docs/translation#read-the-translation).
- Voeg later meer vertaaltalen toe met `POST /v1/transcriptions/:id/translations` zodra de transcriptie `completed` is. Zie [Vertalen](https://transcript1.com/nl/docs/translation#translate-an-existing-transcript).

Met `save: false` blijft er niets bewaard nadat de sessie is afgelopen: er is geen transcriptie en `transcription_id` is `null`. Je krijgt nog wel elke regel via de socket en de gebruikelijke afschrijving geldt.

## Beveiliging

- Het token dat je krijgt als je een sessie aanmaakt, is eenmalig en verloopt na 60 seconden. Het wordt als WebSocket-subprotocol verstuurd en niet in de URL, zodat het buiten toegangslogs blijft. Een gelekt token is nutteloos zodra het is gebruikt of verlopen.
- Gebruik een API-sleutel alleen vanaf een server die je zelf beheert. Zet nooit een API-sleutel in een webpagina of een extensie.
- Laat gebruikers in een browser of extensie inloggen met OAuth en geef het OAuth-toegangstoken door aan `getAccessToken` van `createLiveClient`. Het token heeft de scope `transcriptions:write` nodig om sessies te starten. OAuth-toegangstokens verlopen na 1 uur en bij elke hervatting vraagt de SDK `getAccessToken` om een nieuw token.
- Je server kan de sessie ook met zijn API-sleutel aanmaken en alleen de `websocket_url` en het `token` aan de browser geven. De client verbindt dan direct. Zonder nieuw token van jou kan hij niet hervatten.
