Back to home
Lanson Flow Docs
Short version

"Realtime vs batch" is the wrong framing. The real decision is who does VAD (server or you) and what happens to silence. LansonAI exposes three planes — Realtime WS, Segment HTTP, Batch jobs — and the right one is determined by your audio's shape (stream / clip / file) and your latency need. Pick by the table, not by instinct.

How to choose between realtime and batch transcription

Problem

You have audio and you need text. It sounds like one problem, but it's three different problems:

  • A person is talking right now and you want text as they speak.
  • Your app already cut the speech into short clips (a voice UI) and you want each clip transcribed.
  • You have a finished file (a meeting recording, a podcast) at a URL and you want it processed.
  • Most integrations pick a plane by instinct — "I'll just stream everything" or "I'll just upload a file" — and then hit a wall: unexpected latency, billing for silence, or a WebSocket where they needed a simple HTTP call. The wall is almost always the same root cause: nobody decided who does VAD.

    Architecture

    LansonAI exposes three transcription planes. The differentiator that matters is VAD ownership and when you need the text:

    DimensionRealtime WSSegment HTTPBatch jobs
    When you need textWhile they're speakingPer clip, immediatelyMinutes later (async)
    Who does VADServer (on the stream)You (client)Server (time-slice)
    TransportWebSocketHTTP POSTHTTP POST + GET poll
    InputPCM16LE streamMultipart fileJSON audio_url
    OutputEvent streamTranscript JSONFull job JSON
    LatencyMilliseconds~≤1.6 s (sync)Minutes
    Success signalWS events200202 + poll

    The three planes, in one line each:

  • Realtime WS (WS /v1/audio/transcriptions/stream) — you send a continuous PCM stream; the server runs VAD and emits utterances as they stabilize. For live captions and dialogue.
  • Segment HTTP (POST /v1/audio/transcriptions) — you send one pre-cut clip (multipart file); you get a synchronous 200 with the transcript. For voice UIs that already detect boundaries locally.
  • Batch jobs (POST /v1/audio/transcriptions/jobs) — you send a public audio_url; you get 202 and poll. The server slices by time, transcribes chunks concurrently, and aggregates. For meeting/podcast files.
  • The trap to know up front — Segment has no server-side silence filtering. Whatever you POST is transcribed and billed. If your client VAD is loose and you send 3 seconds of silence, you pay for 3 seconds of silence. Realtime and Batch handle silence for you; Segment does not, because you own the boundary.

    Code

    Minimal, real example for each plane. (Full audio pipeline for the WS plane is in How to stream microphone audio over WebSocket.)

    Realtime WS — text while they speak

    // Server-side: Bearer header. Browser: ?access_token=rt_... (from your backend)
    const ws = new WebSocket("wss://audio.lansonai.com/v1/audio/transcriptions/stream");
    // ws = new WebSocket(url, { headers: { Authorization: `Bearer ${SK}` } })  // Node
    
    ws.onopen = () => {
      ws.send(JSON.stringify({ type: "session.update", language: "zh" }));
      // ... send PCM16LE/16kHz/mono frames as input_audio_buffer.append ...
      // ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: "<base64>" }));
      // ws.send(JSON.stringify({ type: "input_audio_buffer.flush" })); // end of segment
    };
    
    ws.onmessage = (e) => {
      const ev = JSON.parse(e.data);
      if (ev.type === "conversation.item.input_audio_transcription.completed") {
        console.log(`[${ev.utterance_index}] ${ev.text}`); // stable text
      }
    };
    

    Segment HTTP — you already cut the clip

    curl -X POST https://audio.lansonai.com/v1/audio/transcriptions \
      -H "Authorization: Bearer sk-..." \
      -F "file=@utterance.wav" \
      -F "language=zh" \
      -F "response_format=verbose_json"
    # → 200 { "text": "...", "language": "zh", "duration": 1.2, ... }
    

    Batch jobs — you have a file at a URL

    # 1. Submit (202)
    curl -X POST https://audio.lansonai.com/v1/audio/transcriptions/jobs \
      -H "Authorization: Bearer sk-..." -H "Content-Type: application/json" \
      -d '{ "audio_url": "https://cdn.example.com/meeting.wav", "language": "zh" }'
    # → { "workflow_id": "cf_5519...", "status": "queued", "poll_endpoint": "GET /v1/audio/transcriptions/jobs/cf_5519..." }
    
    # 2. Poll until status == "complete"
    curl https://audio.lansonai.com/v1/audio/transcriptions/jobs/cf_5519... \
      -H "Authorization: Bearer sk-..."
    

    Result

    You pick a plane you can defend in one sentence:

  • "Live captions → Realtime, because the server should run VAD on a stream and I need text in milliseconds."
  • "Voice input → Segment, because my client already detects utterance boundaries and I want a synchronous result per clip."
  • "Podcast processing → Batch, because I have a file at a URL and can wait minutes for a complete, aggregated transcript."
  • And the whole decision compresses into three questions you can run top-down:

    Is someone talking right now, and must text appear while they speak?
    ├─ Yes → Realtime WS  (server VAD on a continuous stream)
    └─ No  → Have you already cut the speech into short clips (your own VAD)?
              ├─ Yes → Segment HTTP  (sync 200 per clip; you own silence)
              └─ No  → Is it a complete recording at a URL?
                       ├─ Yes → Batch jobs  (202 + poll; server time-slices)
                       └─ No  → Capture or upload it first, then re-ask.
    

    The latency figures in the table come from the API contract itself (Realtime: millisecond-level event stream; Segment: ~≤1.6s wall-clock budget on the warm path; Batch: minutes, async). Cost varies by plan and usage — check the rate-limits page before committing.

    Why it matters

    1. VAD ownership is the real decision, not "realtime vs batch." The question "do I need it live?" gets you 80% there, but the deeper question is "who cuts the speech into utterances, and who is responsible for silence?" Answer that and the plane chooses itself.

    2. Silence is a billing and quality decision. On Segment you own the boundary, so you own the silence — send it and you're billed for it. On Realtime and Batch the service handles it. Knowing which plane pushes silence-handling onto you prevents a class of "why am I paying for nothing" surprises.

    3. The transferable lesson. Match the transport to the shape of your data: a stream wants a full-duplex socket, a clip wants a synchronous request, a file wants an async job you poll. Reaching for the wrong shape is how you end up writing reconnect logic you didn't need, or blocking a request for minutes.


    Further reading (Docs)

  • Choose an API — Decision table + overview of the three planes
  • Segment Transcription API — Client-VAD synchronous interface
  • Batch Transcription Jobs — Asynchronous job interface
  • Realtime Quickstart — Minimal runnable example
  • Related cookbook recipes

  • How to stream microphone audio over WebSocket — Audio pipeline for the Realtime plane
  • How to build realtime captions that don't jitter — Rendering side for the Realtime plane