ドキュメントライブ文字起こし

ライブ文字起こし

マイクやタブの音声をWebSocketでtranscript1へストリーミングし、話している最中に字幕行を受け取ります。リアルタイム翻訳にも対応しています。

ライブ文字起こしは、話している最中の音声を字幕行に変換する機能です。クライアントがWebSocketで生のPCM音声を送ると、transcript1が字幕行を返します。各行は最初は暫定の認識結果で、やがて確定します。translate_toを指定すると、すべての行に翻訳も付きます。セッションが終わると、結果は通常の文字起こしとして保存されます。

ライブ専用のプランはありません。ライブ文字起こしも、ほかの機能と同じ分数の残高を使います。接続時間の数え方は課金を参照してください。

ライブを使う場面

ライブが向いているケース通常の文字起こしが向いているケース
音声がまだ流れている最中です。通話、講演、配信、いま見ている動画などです。リンクや完成したファイルがあります。
発話から1〜2秒ほどで字幕が欲しい場合です。結果を待てるので、最高の精度を優先したい場合です。
各行のすぐ隣にリアルタイム翻訳が欲しい場合です。完成した文字起こしの翻訳や、複数言語の翻訳が欲しい場合です。

ライブが受け付けるのは、生のPCMの音声だけです。動画リンク、アップロード、WebM、Opus、MP3は使えません。クライアント側で音声を取り込み、先にPCMへ変換してください。ブラウザーで音声を取り込むに手順があります。

SDKでクイックスタート

SDK 1.5.0以降をインストールします: npm i @transcript1/sdk@latest。t1.live.startはセッションの作成と接続を行い、セッションが開始した時点で解決されるので、すぐに音声を送れます。

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);

行はその場で更新されます。すでに持っている行は、同じidの行が届いたら置き換えてください。すべての行はlive.linesでも読めます。end()はセッションを終了し、最後の行を待ってから文字起こしを保存します。メソッドとイベントの一覧はNode.js SDKをご覧ください。

生のWebSocketでクイックスタート

SDKを使わない場合は2段階です。まずHTTPSでセッションを作成し、返ってきた使い捨てトークンでWebSocketを開きます。

セッションを作成する

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}'

APIは201を返します:

{
  "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
}

saveがfalseのとき、transcription_idはnullです。トークンは使い捨てで、60秒で失効するため、すぐに接続してください。rateは、このセッションで開始した1分ごとに差し引かれる残高の分数です。詳しくは課金を参照してください。

リクエストボディのフィールドは次のとおりです。すべて省略でき、必要なスコープはtranscriptions:writeです。

フィールドデフォルトルール
languageautoauto、またはライブ文字起こしが対応する2文字の言語コードです。
translate_toなし2文字の言語コードです。1セッションにつき翻訳先は1言語です。ライブで翻訳するを参照してください。
diarizefalse話者にラベルを付けます。language: "auto"との併用など、話者ラベルに対応しない組み合わせでは422を返します。対応状況で確認してください。
audio_formatpcm_s16le指定できる値はこれだけです。将来フォーマットを追加できるようにフィールドを用意しています。
sample_rate160008000〜48000の整数です。モノラルのみです。
savetruefalseにすると、セッション終了後は何も残りません。保存される文字起こしを参照してください。

接続してストリーミングする

トークンを2番目のサブプロトコルに指定してWebSocketを開きます。ブラウザーでもNode 22以降でも、カスタムヘッダーなしで動きます。音声はsession.startedを受け取ってから送ってください。それより前に送ったバイナリフレームは破棄され、audio_droppedの警告が届きます。

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));

Node 20には組み込みのWebSocketがありません。同じnew WebSocket(url, protocols)の形で使えるwsパッケージを利用してください。

有効なトークンで接続すると、サーバーはt1-liveサブプロトコルだけを返します。無効なトークンでもハンドシェイクは失敗しません。ソケットはいったん開き、errorイベントが届いてから4xxxのコードで閉じられるため、ブラウザーでは汎用の1006ではなく本当の理由が分かります。クローズコードを参照してください。

ブラウザーで音声を取り込む

ライブの音声は、セッションのsample_rateで、16ビット符号付きリトルエンディアンのモノラルPCMにする必要があります。ブラウザーが渡すのは浮動小数点のサンプルなので、AudioWorkletで変換します。次のスニペットは、マイクの音声を16 kHzのPCMとして100 msごとに取り込み、ブラウザー向けのSDKエントリで送信します。getAccessTokenはOAuthアクセストークンを返す関数です。ブラウザーにAPIキーを置いてはいけません。セキュリティを参照してください。

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();
}

このワークレットは16 kHz向けに書かれています。コンテキストが別のレートで動くとチャンクは100 msではなくなりますが、sampleRateがコンテキストと一致しているため、PCM自体は正しいままです。マイクではなくタブや動画の音声を取り込むには、getDisplayMedia、またはメディア要素のcaptureStream()でMediaStreamを作り、同じように接続します。

ライブで翻訳する

セッションにtranslate_toを加えると、transcript1は話された行をそのつど翻訳します。

  • 翻訳先は1セッションにつき1言語です。言語を増やしたい場合は、あとから保存済みの文字起こしに翻訳エンドポイントで追加します。
  • すべてのlineに、原文(text、final)とtranslationオブジェクト(language、text、final)が付きます。translate_toを指定しない場合、translationはnullです。
  • 原文と翻訳はそれぞれ別に確定します。原文が確定していても、翻訳はまだ変わることがあります。両方が確定した時点で、その行を確定として表示してください。
  • すでに翻訳先の言語で話されている行は、その行自身のテキストが翻訳になり、行が閉じた時点で確定します。
  • 行が閉じるのは、話者が間を置いたとき、話者が替わったとき、単語間の間隔が0.9秒を超えたとき、行が字幕2行分(84文字)または15秒に達したとき、finalizeを送ったときです。翻訳をオンにしていると、翻訳が届くまで行が閉じるのが3秒ほど遅れることがあります。その場合、原文が確定したあとも、翻訳が未確定(final: false)のまま伸び続けることがあります。
  • 翻訳は無料です。分数は消費しません。

使える翻訳先の言語や言語ペアは、ライブの音声認識プロバイダーによって変わります。ルールを決め打ちせず、対応状況を読んでください。未対応の翻訳先を指定すると422 unsupported_translationが返ります。たとえば、プロバイダーによってはautoではなくlanguageの明示が必要だったり、英語を含むペアしか使えなかったりします。

WebSocketプロトコル

接続

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

トークンはWebSocketのサブプロトコルとして送るため、URLやアクセスログには残りません。Authorizationヘッダーは送らないでください。認証はトークンだけで行います。サーバーは30秒ごとにpingを送り、ブラウザーとwsパッケージは自動でpongを返します。60秒間pongがないソケットは切断されます。

すでにソケットがあるセッションへ有効な接続をすると、古いソケットが置き換えられます。再開と引き継ぎを参照してください。

クライアントからのメッセージ

メッセージ意味
バイナリフレーム音声です。セッションのsample_rateのPCM s16leモノラルで送ります。各フレームは偶数バイト長で、最大256 KBです。1フレームあたり100 msほどが目安です。
{"type":"finalize"}現在の行をいますぐ閉じます。たとえば動画を一時停止したときに使います。
{"type":"end"}セッションを終了して保存します。サーバーはsession.endedを返してソケットを閉じます。
それ以外コードがinvalid_messageの、致命的ではないerrorイベントが届きます。

奇数長のフレームや大きすぎるフレームはプロトコル違反で、ソケットは4008で閉じられます。

音声はsession.startedのあとに送り始めてください。リアルタイムより速く音声を送ると、接続時間ではなく音声の長さで課金されます(課金を参照)。音声が生成される速さに合わせて送ってください。

サーバーからのイベント

イベントはすべて、typeを持つJSONのテキストフレームです。

イベント送られるタイミング
session.started接続が受け付けられたときです。以降、音声を送れます。
line字幕行が作られた、または変わったときです。
usage課金のたびに送られます。
warning注意が必要ですが、セッションは続行されます。
error致命的ではない問題、またはすぐに閉じる理由です。
reconnectサーバーがまもなく再起動します。再開してください。
session.endedセッションが終了し、保存されました。通常のクローズ前の最後のイベントです。

session.startedは、再開かどうかと、セッションがここまでに処理した音声の量を伝えます。

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

lineは1行の最新の状態をまるごと運びます。同じidの行をこれで置き換えてください。lineオブジェクトを参照してください。

{
  "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は課金のたびに送られ、接続時間、音声の長さ、これまでに差し引かれた残高の分数、残りの分数を伝えます。

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

warningにはcodeがあります。

code意味
balance_low次の課金が失敗します。その時点が来ると、セッションはbalance_exhaustedで終了します。自動追加購入がオンの間は送信されません。
audio_droppedセッションの準備ができる前に音声が届いたか、音声認識プロバイダーへの接続中に音声が大量に届きました。古い音声から破棄されました。
idle_timeout_soon4分間、音声がありません。音声のない状態が5分続くとセッションは終了します。続けるには音声を送ってください。
{ "type": "warning", "code": "idle_timeout_soon" }

errorにはcodeとmessageがあります。ライブのソケットでは、次のコードが届くことがあります。

code致命的か意味
invalid_messageいいえfinalizeでもendでもないテキストフレームです。
invalid_tokenはいトークンが無効、期限切れ、または使用済みです。ソケットは4001で閉じられます。
not_foundはいセッションが存在しません。ソケットは4004で閉じられます。
session_endedはいセッションはすでに終了しています。ソケットは4004で閉じられます。
{ "type": "error", "code": "invalid_message", "message": "Expected {\"type\":\"finalize\"} or {\"type\":\"end\"}" }

reconnectは、デプロイ中などにサーバーがソケットを1012で閉じる前に届きます。新しいトークンを取得して再開してください。

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

session.endedは、すべてのセッションの最後のイベントです。数値は最終的な合計です。

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

lineオブジェクト

フィールド意味
id行ごとに1ずつ増える、0始まりの番号です。保存された文字起こしのセグメントのインデックスにもなります。
start_ms、end_msセッションの音声時間での開始と終了です。この時間は再接続をまたいで続きます。
speakerdiarizeがオンのときは話者ラベル、それ以外はnullです。ラベルは通常の文字起こしと同じです。
language検出された、または指定された行の言語です。まだ分からないときはnullです。
textここまでの原文です。
final原文がこれ以上変わらなくなったらtrueになります。
translationtranslate_toがなければnull、あればlanguage、text、finalを持ちます。

final: falseの行は、テキストがまだ変わるので薄いスタイルで表示してください。final: trueの行のテキストは二度と変わらず、確定した行が削除されることもありません。

SDKでは行のフィールドをcamelCaseで表します: startMs、endMs。

終了理由

session.endedとGET /v1/live/sessions/:idはend_reasonを返します。

理由意味
client_endedクライアントがendを送りました。
balance_exhausted次の課金に失敗しました。セッションは分の区切りで終了し、保存されます。
idle_timeout5分間、音声がありませんでした。
max_duration接続時間が300分に達しました。新しいセッションを開始してください。
disconnectedクライアントが2分を超えて再開しませんでした。
upstream_error音声認識プロバイダーで障害が起きました。
internal_errortranscript1側で問題が起きました。
not_connected5分以内に一度も接続されませんでした。課金はされず、GETでのみ確認できます。

クローズコード

コード意味
1000正常なクローズです。必ずsession.endedのあとに送られます。
1011サーバーの予期しないエラーです。セッションはサーバー側で復旧されます。新しいトークンで再開してください。
1012サーバーの再起動です。reconnectのあとに送られます。新しいトークンで再開してください。
4001トークンが無効、期限切れ、または使用済みです。
4004セッションが見つからないか、すでに終了しています。
4008プロトコル違反です。奇数長または大きすぎるバイナリフレームを送りました。
4010同じセッションへの新しい接続に置き換えられました。

APIの再起動中は、新しいWebSocketリクエストにHTTP 503が返ることがあります。少し待って再試行してください。

再開と引き継ぎ

セッションは、ネットワークの切断、APIのデプロイ、クラッシュがあっても維持されます。切断から2分間は再接続を待ち、それを過ぎるとdisconnectedとして終了し、保存されます。

  1. POST /v1/live/sessions/:id/tokenを呼びます。レスポンスは、セッション作成時と同じtoken、token_expires_at、websocket_urlです。セッションが終了していると409 session_endedで失敗します。
  2. そのトークンで新しいWebSocketを開きます。
  3. resumed: trueのsession.startedが届きます。offset_msは、セッションがここまでに処理した音声の量です。その位置の続きから音声を送ってください。行のidも続きの番号になります。

すでに確定していた行は残ります。切断した時点で作成中だった末尾は失われるため、音声をバッファしているならoffset_msから数秒分をさかのぼって送り直してください。再接続の空白時間は課金されません。

引き継ぎも同じ呼び出しです。サーバーがまだソケットが開いていると見なしていても、新しい有効な接続が古いものを置き換え、古いソケットは行を保存したあと4010で閉じられます。同時セッション数の上限によって再開が妨げられることはありません。

SDKはこれをすべて自動で行います。切断やreconnectイベントを受け取ると、トークンを取得し直し、0.5〜8秒のバックオフで最大2分間再接続を試みます。その間に送信した音声のうち、直近10秒分は再接続後に再送します。

課金

ライブ文字起こしは、通常の文字起こしと同じ分数の残高を使います。

  • 接続時間が課金対象です。 ソケットが受け付けられてから閉じるまでが対象で、無音の時間や動画の一時停止中も含みます。切断から再開までの時間は含みません。
  • 開始した1分ごとに、最低1分です。 最初の1分は最初の接続が受け付けられたときに、以降の各分は始まったときに課金されます。
  • リアルタイムより速い場合は音声の長さで課金します。 送った音声が接続時間より長いときは、音声の長さが課金されます。
  • レートはAPIから読み取ります。 開始した1分にはrate分の残高がかかり、小数は切り上げます。通常は1で、小数になることもあります。ライブのレートは、GET /v1/accountのrates.live、GET /v1/live/capabilitiesのrate、作成したセッションのrateで確認できます。セッションのレートは、作成時に固定されます。
  • 翻訳は無料です。
  • 一度も接続しなければ課金されません。 5分以内に接続されなかったセッションはnot_connectedで終了します。
  • 失敗した場合は返金されます。 行が1つも生成される前にupstream_errorまたはinternal_errorで終了したセッションは、すべての課金が戻ります。無音のために空だったセッションは課金され、空のまま保存されます。
  • 残高がなくなったとき、 自動追加購入がオンなら、セッションがストリーミングを続ける間にtranscript1が登録済みのカードに請求し、セッションは続行されます。自動追加購入がオフまたは一時停止中の場合や、請求に失敗した場合は、セッションは分の区切りで確定、保存され、balance_exhaustedで終了します。balance_lowの警告は先に届きますが、自動追加購入がオンの場合は届きません。

使える残高が、現在のレートでの開始1分分に満たないと、セッションの作成は402 insufficient_minutesを返します。自動追加購入がオンなら、transcript1が先に追加購入を試みます。料金と上限を参照してください。

上限

FreeMonthlyAnnual
同時ライブセッション数123
  • セッションは、waiting、connected、disconnected(2分間の再開猶予の間)のいずれかの間、数えられます。上限を超えると、POST /v1/live/sessionsはRetry-After: 30付きの429 too_many_live_sessionsを返します。
  • ライブセッションは、同時に処理できるジョブ数には数えません。
  • 1セッションの接続時間は最大300分です。それを過ぎるとmax_durationで終了します。
  • 5分間音声のないセッションはidle_timeoutで終了します。4分たつとidle_timeout_soonが届きます。一時停止中のストリームも接続時間が課金され、アイドルとして数えられます。停止が長引くときはセッションを終了してください。
  • transcript1はすべてのライブセッションの合計数にも上限を設けています。すべて使用中のときは、POST /v1/live/sessionsが503 live_capacityを返します。これは再試行できるエラーです。Retry-After(30秒)だけ待ってからやり直してください。この確認はセッションの作成時にだけ行われ、セッションの途中で行われることはありません。
  • セッションの作成とトークンの取得は、1分間のリクエスト上限に数えられます。
  • 音声は生のPCMのみです: pcm_s16le、モノラル、8000〜48000 Hz。

対応状況

GET /v1/live/capabilitiesは、ライブ文字起こしがいま何に対応しているかを返します。クライアントにプロバイダーのルールを決め打ちさせないための仕組みです。必要なスコープはtranscriptions:readです。

{
  "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
}

ここでは配列を短くしています。実際の値はAPIから読み取ってください。

  • translation.pairsは、"any"(検出した任意の言語から任意の翻訳先へ)か、fromとtoを持つオブジェクトの配列です。
  • translation.requires_languageがtrueのときは、翻訳にautoではなく明示的なlanguageが必要です。
  • ライブ文字起こしが設定されていない場合、レスポンスはrateだけを持つavailable: falseになり、セッションの作成は503 live_unavailableを返します。

SDKではawait t1.live.capabilities()です。

セッションを取得する

GET /v1/live/sessions/:idは、セッションの状態を返します。必要なスコープはtranscriptions:readです。

curl https://api.transcript1.com/v1/live/sessions/lv_8fK2Lm0PqRsTuVwXyZaBcD \
  -H "Authorization: Bearer $TRANSCRIPT1_API_KEY"
{
  "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はwaiting、connected、disconnected、endedのいずれかです。end_reasonは、セッションが終了するまでnullです。SDKではawait t1.live.get(id)です。

エラー

リクエストのエラーは、通常のエラーレスポンスの形式に従います。POST /v1/live/sessionsとその関連エンドポイントが返すエラーは次のとおりです。

HTTPステータスcode再試行発生する場面
422invalid_request不可フィールドが足りないか不正です。たとえばsample_rateが8000〜48000の範囲外です。
422unsupported_language不可languageがライブ文字起こしに対応していません。
422unsupported_translation不可翻訳先の言語、または言語の組み合わせに対応していません。
402insufficient_minutes不可使える残高が、現在のレートでの開始1分分に満たない状態です。
403email_not_verified不可先にメールアドレスを認証してください。
429too_many_live_sessions可プランの同時ライブセッション数をすべて使っています。Retry-Afterの秒数だけ待ってください。
503live_capacity可ライブの処理容量がすべて使用中です。Retry-After(30秒)だけ待ってください。
503live_unavailable不可ライブ文字起こしをいまは利用できません。
409session_ended不可終了済みのセッションに対してPOST /v1/live/sessions/:id/tokenを呼びました。

ソケット上の問題は、errorイベントとクローズコードで届きます。

保存される文字起こし

save: true(デフォルト)なら、セッション終了時に通常の文字起こしとして保存されます。音声そのものは保存されません。

  • transcription_idはセッション作成時に返されます。ステータスはセッション中はlive、終了後はcompletedです。一度も接続しなかったセッションは、canceledの文字起こしとして残ります。
  • 文字起こしのsourceは{ "platform": "live", "session_id": "lv_…" }です。リンクやファイルはありません。ダッシュボードにはライブセッションとして表示されます。
  • 各行は1つのセグメントになり、インデックスは行のidと同じです。文字起こしは、ほかの文字起こしと同様にJSON、テキスト、SRT、VTTで取得できます。APIリファレンスを参照してください。
  • ライブ翻訳もいっしょに保存されます。翻訳で説明しているtranslationパラメーターで読み出せます。
  • 翻訳の言語は、文字起こしがcompletedになってからPOST /v1/transcriptions/:id/translationsで追加できます。翻訳を参照してください。

save: falseの場合、セッション終了後は何も残りません。文字起こしは作られず、transcription_idはnullです。それでもすべての行はソケットで受け取れ、通常どおり課金されます。

セキュリティ

  • セッションの作成時に受け取るトークンは使い捨てで、60秒で失効します。URLではなくWebSocketのサブプロトコルで送るため、アクセスログに残りません。漏れたトークンも、使用済みか期限切れになれば無効です。
  • APIキーは、自分で管理しているサーバーでのみ使ってください。Webページや拡張機能にAPIキーを埋め込んではいけません。
  • ブラウザーや拡張機能では、ユーザーをOAuthでサインインさせ、OAuthアクセストークンをcreateLiveClientのgetAccessTokenに渡します。セッションを開始するには、トークンにtranscriptions:writeスコープが必要です。OAuthアクセストークンは1時間で失効するため、再開のたびにgetAccessTokenが新しいトークンを取得します。
  • サーバー側でAPIキーを使ってセッションを作成し、websocket_urlとtokenだけをブラウザーに渡すこともできます。クライアントはそのまま直接接続します。ただし、新しいトークンを渡さない限り、クライアントは再開できません。

最終更新 2026年11月3日