# ライブ文字起こし

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

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

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

ライブ専用のプランはありません。ライブ文字起こしも、ほかの機能と同じ分数の残高を使います。接続時間の数え方は[課金](https://transcript1.com/ja/docs/live#billing)を参照してください。

## ライブを使う場面

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

ライブが受け付けるのは、生のPCMの音声だけです。動画リンク、アップロード、WebM、Opus、MP3は使えません。クライアント側で音声を取り込み、先にPCMへ変換してください。[ブラウザーで音声を取り込む](https://transcript1.com/ja/docs/live#capture-audio-in-a-browser)に手順があります。

## SDKでクイックスタート

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

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

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

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

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

### セッションを作成する

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

APIは`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
}
```

`save`が`false`のとき、`transcription_id`は`null`です。トークンは使い捨てで、60秒で失効するため、すぐに接続してください。`rate`は、このセッションで開始した1分ごとに差し引かれる残高の分数です。詳しくは[課金](https://transcript1.com/ja/docs/live#billing)を参照してください。

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

| フィールド | デフォルト | ルール |
|---|---|---|
| `language` | `auto` | `auto`、またはライブ文字起こしが対応する2文字の言語コードです。 |
| `translate_to` | なし | 2文字の言語コードです。1セッションにつき翻訳先は1言語です。[ライブで翻訳する](https://transcript1.com/ja/docs/live#translate-live)を参照してください。 |
| `diarize` | `false` | 話者にラベルを付けます。`language: "auto"`との併用など、話者ラベルに対応しない組み合わせでは`422`を返します。[対応状況](https://transcript1.com/ja/docs/live#capabilities)で確認してください。 |
| `audio_format` | `pcm_s16le` | 指定できる値はこれだけです。将来フォーマットを追加できるようにフィールドを用意しています。 |
| `sample_rate` | `16000` | 8000〜48000の整数です。モノラルのみです。 |
| `save` | `true` | `false`にすると、セッション終了後は何も残りません。[保存される文字起こし](https://transcript1.com/ja/docs/live#saved-transcripts)を参照してください。 |

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

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

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

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

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

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

ライブの音声は、セッションの`sample_rate`で、16ビット符号付きリトルエンディアンのモノラルPCMにする必要があります。ブラウザーが渡すのは浮動小数点のサンプルなので、`AudioWorklet`で変換します。次のスニペットは、マイクの音声を16 kHzのPCMとして100 msごとに取り込み、ブラウザー向けのSDKエントリで送信します。`getAccessToken`はOAuthアクセストークンを返す関数です。ブラウザーにAPIキーを置いてはいけません。[セキュリティ](https://transcript1.com/ja/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();
}
```

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

## ライブで翻訳する

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

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

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

## WebSocketプロトコル

### 接続

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

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

すでにソケットがあるセッションへ有効な接続をすると、古いソケットが置き換えられます。[再開と引き継ぎ](https://transcript1.com/ja/docs/live#resume-and-takeover)を参照してください。

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

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

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

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

### サーバーからのイベント

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

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

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

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

`line`は1行の最新の状態をまるごと運びます。同じ`id`の行をこれで置き換えてください。[lineオブジェクト](https://transcript1.com/ja/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`は課金のたびに送られ、接続時間、音声の長さ、これまでに差し引かれた残高の分数、残りの分数を伝えます。

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

`warning`には`code`があります。

| `code` | 意味 |
|---|---|
| `balance_low` | 次の課金が失敗します。その時点が来ると、セッションは`balance_exhausted`で終了します。[自動追加購入](https://transcript1.com/ja/docs/billing-and-limits#auto-top-up)がオンの間は送信されません。 |
| `audio_dropped` | セッションの準備ができる前に音声が届いたか、音声認識プロバイダーへの接続中に音声が大量に届きました。古い音声から破棄されました。 |
| `idle_timeout_soon` | 4分間、音声がありません。音声のない状態が5分続くとセッションは終了します。続けるには音声を送ってください。 |

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

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

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

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

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

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

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

```json
{
  "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` | セッションの音声時間での開始と終了です。この時間は再接続をまたいで続きます。 |
| `speaker` | `diarize`がオンのときは話者ラベル、それ以外は`null`です。ラベルは通常の文字起こしと同じです。 |
| `language` | 検出された、または指定された行の言語です。まだ分からないときは`null`です。 |
| `text` | ここまでの原文です。 |
| `final` | 原文がこれ以上変わらなくなったら`true`になります。 |
| `translation` | `translate_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_timeout` | 5分間、音声がありませんでした。 |
| `max_duration` | 接続時間が300分に達しました。新しいセッションを開始してください。 |
| `disconnected` | クライアントが2分を超えて再開しませんでした。 |
| `upstream_error` | 音声認識プロバイダーで障害が起きました。 |
| `internal_error` | transcript1側で問題が起きました。 |
| `not_connected` | 5分以内に一度も接続されませんでした。課金はされず、`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`で終了したセッションは、すべての課金が戻ります。無音のために空だったセッションは課金され、空のまま保存されます。
- **残高がなくなったとき、** [自動追加購入](https://transcript1.com/ja/docs/billing-and-limits#auto-top-up)がオンなら、セッションがストリーミングを続ける間にtranscript1が登録済みのカードに請求し、セッションは続行されます。自動追加購入がオフまたは一時停止中の場合や、請求に失敗した場合は、セッションは分の区切りで確定、保存され、`balance_exhausted`で終了します。`balance_low`の警告は先に届きますが、自動追加購入がオンの場合は届きません。

使える残高が、現在のレートでの開始1分分に満たないと、セッションの作成は`402 insufficient_minutes`を返します。自動追加購入がオンなら、transcript1が先に追加購入を試みます。[料金と上限](https://transcript1.com/ja/docs/billing-and-limits#live-transcription)を参照してください。

## 上限

| | Free | Monthly | Annual |
|---|---|---|---|
| 同時ライブセッション数 | 1 | 2 | 3 |

- セッションは、`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`です。

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

ここでは配列を短くしています。実際の値は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`です。

```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`は`waiting`、`connected`、`disconnected`、`ended`のいずれかです。`end_reason`は、セッションが終了するまで`null`です。SDKでは`await t1.live.get(id)`です。

## エラー

リクエストのエラーは、通常の[エラーレスポンスの形式](https://transcript1.com/ja/docs/errors)に従います。`POST /v1/live/sessions`とその関連エンドポイントが返すエラーは次のとおりです。

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

ソケット上の問題は、`error`イベントと[クローズコード](https://transcript1.com/ja/docs/live#close-codes)で届きます。

## 保存される文字起こし

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

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

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

## セキュリティ

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