# エラーコード

機械可読な版は [errors.json](../errors.json) にあります。

## WebSocket のコード

### `unauthorized` {#unauthorized}

- 対処: 新しいセッショントークン（Ingest は ingest:write、Subscribe は transcript:read）や有効な API キーを使う。
- WebSocket（Ingest・Subscribe）: 文字おこしくんが接続を切る。クライアントは、再接続しない。
- 起きたこと: クライアントが資格情報を送らなかった。または、送った資格情報が、不正か、期限切れか、失効している。または、トークンの権限（scope）が足りない。
- HTTP: 401「認証が必要です」（WWW-Authenticate: Bearer realm="mojiokoshi"）

### `protocol_version_unsupported` {#protocol_version_unsupported}

- 対処: hello の protocolVersion を 1 にする（クライアントを更新する）。
- WebSocket（Ingest・Subscribe）: 文字おこしくんが接続を切る。クライアントは、再接続しない。
- 起きたこと: hello の protocolVersion を文字おこしくんが扱えない。

### `invalid_message` {#invalid_message}

- 対処: 送るメッセージを JSON Schema で検証して直す。fatal なら、文字おこしくんが hello を拒んだので、同じ hello で再接続しない。
- WebSocket（Ingest・Subscribe）: fatal なら、文字おこしくんが接続を切る。クライアントは、再接続しない。fatal でなければ、クライアントは接続を使い続ける。
- 起きたこと: クライアントが、JSON として読めないメッセージか、形式に違反するメッセージを送った。hello の前にほかのメッセージを送ったときと、hello が時間内（10 秒）に届かないときも、文字おこしくんはこのコードを送る。

### `unknown_track` {#unknown_track}

- 対処: track_open でトラックを開いてから、その channel で音声を送り、その trackId で speech_start / speech_end を送る。
- WebSocket（Ingest）: 文字おこしくんは接続を切らない。クライアントは接続を使い続ける。
- 起きたこと: クライアントが、開いていない trackId か channel を参照した。

### `unknown_speaker` {#unknown_speaker}

- 対処: speaker_upsert で話者を宣言してから、その話者の single トラックを track_open する。
- WebSocket（Ingest）: 文字おこしくんは接続を切らない。クライアントは接続を使い続ける。
- 起きたこと: クライアントが、speaker_upsert で宣言していない speakerId を参照した。たとえば、single トラックの speakerId が、宣言していない話者を指していた。

### `channel_in_use` {#channel_in_use}

- 対処: 使っていない channel 番号で track_open し直す（track_close した番号は再利用してよい）。
- WebSocket（Ingest）: 文字おこしくんは接続を切らない。クライアントは接続を使い続ける。
- 起きたこと: track_open で指定した channel を、同じ接続の別のトラックが使っている。

### `track_limit_exceeded` {#track_limit_exceeded}

- 対処: 使っていないトラックを track_close してから、新しいトラックを開く。文字おこしくんが開かなかったトラックの音声は送らない。全部の API キーを合わせた上限に当たったときは、時間をおいてから、文字おこしくんが開かなかったトラックを開き直す。
- WebSocket（Ingest）: 文字おこしくんは接続を切らない。クライアントは接続を使い続ける。
- 起きたこと: トラックの数が、1 接続あたりの上限（64 本）を超えた。または、API キーで作ったセッションで開いているトラックの数が、全部の API キー（ほかの利用者のキーを含む）を合わせた上限を超えた。または、テナント（Discord サーバー）の同時トラック数が、文字おこしくんの運営者が付けた上限を超えた。公式の文字おこしくん（moji.kaitoranma.com）には、テナントの同時トラック数の上限は無い。

### `unsupported_format` {#unsupported_format}

- 対処: opus は 48000 Hz・1/2ch、pcm_s16le は 8000〜48000 Hz・1/2ch で track_open し直す。
- WebSocket（Ingest）: 文字おこしくんは接続を切らない。クライアントは接続を使い続ける。
- 起きたこと: track_open の format（コーデック・サンプルレート・チャンネル数）を文字おこしくんが扱えない。

### `rate_limited` {#rate_limited}

- 対処: 送る量を減らす。HTTP では、Retry-After の秒数だけ待ってから要求をやり直す。WebSocket では、文字おこしくんが接続を切ったら、待ち時間をおいて再接続する。
- WebSocket（Ingest・Subscribe）: fatal なら、文字おこしくんが接続を切る。クライアントは、30 秒待って再接続する。fatal でなければ、クライアントは接続を使い続ける。
- 起きたこと: 送信量・要求数・接続数・話者数などが上限を超えた。または、Subscribe のクライアントの受信が、文字おこしくんからの送信に追いつかない。
- HTTP: 429「要求が多すぎます」（Retry-After、retryAfterSeconds、RateLimit / RateLimit-Policy）

### `session_ended` {#session_ended}

- 対処: 再接続しない。文字起こしを続けるなら、新しいセッションを作る。
- WebSocket（Ingest・Subscribe）: 文字おこしくんが接続を切る。クライアントは、再接続しない。
- 起きたこと: セッションが終了しているか、存在しない。保持期間を過ぎて消えたセッションにも、文字おこしくんはこのコードを送る。
- HTTP: 409「セッションは終了しています」

### `superseded` {#superseded}

- 対処: 再接続しない。新しい接続のほうが有効になっている。1 セッションに、Ingest の送り手は 1 つだけにする。
- WebSocket（Ingest）: 文字おこしくんが接続を切る。クライアントは、再接続しない。
- 起きたこと: 同じセッションに新しい Ingest の接続が来たので、文字おこしくんがこの接続を新しい接続に置き換えた。

### `quota_exceeded` {#quota_exceeded}

- 対処: 次の UTC 0 時（日本時間 9:00）の後に新しいセッションを作る。公式の文字おこしくん（moji.kaitoranma.com）には、1 日の利用量の上限は無い。上限を変えたいときは、文字おこしくんの運営者（contact@kaitoranma.com）に連絡する。
- WebSocket（Ingest）: 文字おこしくんが接続を切る。クライアントは、再接続しない。
- 起きたこと: テナント（Discord サーバー）の利用量が、今日（UTC）の利用上限に達した。Ingest では、セッションも終わっている。
- HTTP: 429「利用上限に達しました」（Retry-After（次の UTC 0 時までの秒数）、retryAfterSeconds、quota）

### `internal` {#internal}

- 対処: 指数バックオフ（ジッター付き）でやり直す。問題が続くなら、文字おこしくんの運営者（contact@kaitoranma.com）に連絡する。
- WebSocket（Ingest・Subscribe）: fatal なら、文字おこしくんが接続を切る。クライアントは、バックオフで再接続する。fatal でなければ、クライアントは接続を使い続ける。
- 起きたこと: 文字おこしくんの内部で問題が起きた。
- HTTP: 500「文字おこしくんの内部のエラーです」

## HTTP だけのコード

### `forbidden` {#forbidden}

- 対処: 対象のセッションとテナントに合った資格情報を使う。
- 起きたこと: 要求に使ったトークンが、別のセッションのものだった。または、要求の tenantId か externalRef が、API キーのテナントと違っていた。
- HTTP: 403「この操作は許可されていません」

### `insufficient_scope` {#insufficient_scope}

- 対処: 応答の requiredScope が示すスコープを持つ API キーを、Discord サーバーの管理者に管理画面で発行してもらう。
- 起きたこと: 要求に使った API キーに、その操作のスコープが無い。
- HTTP: 403「API キーの権限が足りません」（WWW-Authenticate（error="insufficient_scope", scope="…"）、requiredScope）

### `api_access_disabled` {#api_access_disabled}

- 対処: 文字おこしくんの運営者（contact@kaitoranma.com）に連絡する。
- 起きたこと: このテナント（Discord サーバー）では、API キーの利用が止まっている。公式の文字おこしくん（moji.kaitoranma.com）では、文字おこしくんの運営者が止めたときだけ、この状態になる。
- HTTP: 403「API キーの利用が無効です」

### `invalid_json` {#invalid_json}

- 対処: Content-Type: application/json で正しい JSON のオブジェクトを送る。
- 起きたこと: 文字おこしくんが、要求の本文を JSON として読めなかった。
- HTTP: 400「本文が JSON ではありません」

### `invalid_request` {#invalid_request}

- 対処: detail が示すフィールドを直す。
- 起きたこと: 要求の値が不正だった。本文の形・クエリの形・パスの tenantId の形・Idempotency-Key の形のどれかが不正なときも、文字おこしくんはこのコードを返す。
- HTTP: 400「要求の値が不正です」

### `session_not_found` {#session_not_found}

- 対処: sessionId を確かめる。消えた記録は戻らないので、残したい記録は保持期間の内に書き出す。
- 起きたこと: 指定したセッションが無い。保持期間を過ぎて消えたセッションを指定したときと、API キーのテナントとは別のテナントのセッションを指定したときにも、文字おこしくんはこのコードを返す。
- HTTP: 404「セッションが見つかりません」

### `session_active` {#session_active}

- 対処: POST /v1/sessions/:id/end でセッションを終えてから削除する。終了の途中なら、少し待ってから削除をやり直す。
- 起きたこと: クライアントが、稼働中か終了の途中のセッションを削除しようとした。
- HTTP: 409「セッションは終了していません」

### `idempotency_in_progress` {#idempotency_in_progress}

- 対処: Retry-After の秒数だけ待ってから、同じキーと同じ本文で要求をやり直す。
- 起きたこと: 同じ Idempotency-Key の前の要求を、文字おこしくんがまだ処理している。
- HTTP: 409「同じ Idempotency-Key の要求を処理中です」（Retry-After: 1）

### `idempotency_mismatch` {#idempotency_mismatch}

- 対処: 別の操作には新しい Idempotency-Key を使う。
- 起きたこと: クライアントが、前の要求と同じ Idempotency-Key で、本文の違う要求を送った。
- HTTP: 422「この Idempotency-Key は、本文の違う要求で使用済みです」

### `not_found` {#not_found}

- 対処: パスを確かめる。API のパスは /v1 から始まる。
- 起きたこと: 要求したパスの API が無い。
- HTTP: 404「見つかりません」

### `method_not_allowed` {#method_not_allowed}

- 対処: リファレンス（openapi.yaml）が示すメソッドを使う。
- 起きたこと: そのパスでは、要求したメソッドを使えない。
- HTTP: 405「このメソッドは使えません」

### `payload_too_large` {#payload_too_large}

- 対処: 要求の本文を小さくする。
- 起きたこと: 要求の本文が 16 KiB を超えた。
- HTTP: 413「本文が大きすぎます」

### `upgrade_required` {#upgrade_required}

- 対処: WebSocket（wss://）でつなぐ。
- 起きたこと: クライアントが、WebSocket のパス（/v1/ingest・/v1/subscribe）に普通の HTTP でアクセスした。
- HTTP: 426「WebSocket で接続してください」（Upgrade: websocket）

### `too_many_connections` {#too_many_connections}

- 対処: 普通の切断と同じく指数バックオフで再接続する。使っていない接続を閉じる。
- 起きたこと: WebSocket の同時接続数が、上限（文字おこしくんの API 全体で 4,096、同じ送信元から 1,024）を超えた。文字おこしくんは、upgrade の要求への HTTP の応答でこのコードを返す。
- HTTP: 503 / 429「接続が多すぎます」（API 全体の上限は 503、同じ送信元の上限は 429）

### `shutting_down` {#shutting_down}

- 対処: バックオフしてから、要求をやり直す。文字おこしくんが再起動した後は、要求が通る。
- 起きたこと: 再起動かデプロイのために、文字おこしくんは停止の途中だった。
- HTTP: 503「文字おこしくんは停止の途中です」

## 使わなくなったコード

文字おこしくんは、この節のコードを、今は返しません。クライアントは、このコードを扱う処理を残しておいても、消しても構いません。

### `credits_exhausted` {#credits_exhausted}

- 使わなくなったコード: 文字おこしくんは、2026-10-14 から、このコードを返さない。
- 対処: 対処は要らない。古いクライアントは、このコードを扱う処理を残しておいてよい。
- 起きたこと: 以前、Discord サーバーのクレジットが残っていないときに返していたコード。文字おこしくんは、2026-10-14 から、このコードを返さない。
- HTTP: 402「クレジットが残っていません」

## 一覧

| code | WebSocket の接続 | HTTP |
|---|---|---|
| [`unauthorized`](#unauthorized) | 文字おこしくんが切り、クライアントは再接続しない | 401 |
| [`protocol_version_unsupported`](#protocol_version_unsupported) | 文字おこしくんが切り、クライアントは再接続しない | — |
| [`invalid_message`](#invalid_message) | fatal なら文字おこしくんが切り、クライアントは再接続しない | — |
| [`unknown_track`](#unknown_track) | 文字おこしくんは切らない | — |
| [`unknown_speaker`](#unknown_speaker) | 文字おこしくんは切らない | — |
| [`channel_in_use`](#channel_in_use) | 文字おこしくんは切らない | — |
| [`track_limit_exceeded`](#track_limit_exceeded) | 文字おこしくんは切らない | — |
| [`unsupported_format`](#unsupported_format) | 文字おこしくんは切らない | — |
| [`rate_limited`](#rate_limited) | fatal なら文字おこしくんが切り、クライアントは 30 秒待って再接続する | 429 |
| [`session_ended`](#session_ended) | 文字おこしくんが切り、クライアントは再接続しない | 409 |
| [`superseded`](#superseded) | 文字おこしくんが切り、クライアントは再接続しない | — |
| [`quota_exceeded`](#quota_exceeded) | 文字おこしくんが切り、クライアントは再接続しない | 429 |
| [`internal`](#internal) | fatal なら文字おこしくんが切り、クライアントはバックオフで再接続する | 500 |
| [`forbidden`](#forbidden) | — | 403 |
| [`insufficient_scope`](#insufficient_scope) | — | 403 |
| [`api_access_disabled`](#api_access_disabled) | — | 403 |
| [`invalid_json`](#invalid_json) | — | 400 |
| [`invalid_request`](#invalid_request) | — | 400 |
| [`session_not_found`](#session_not_found) | — | 404 |
| [`session_active`](#session_active) | — | 409 |
| [`idempotency_in_progress`](#idempotency_in_progress) | — | 409 |
| [`idempotency_mismatch`](#idempotency_mismatch) | — | 422 |
| [`not_found`](#not_found) | — | 404 |
| [`method_not_allowed`](#method_not_allowed) | — | 405 |
| [`payload_too_large`](#payload_too_large) | — | 413 |
| [`upgrade_required`](#upgrade_required) | — | 426 |
| [`credits_exhausted`](#credits_exhausted)（使わなくなった） | — | 402 |
| [`too_many_connections`](#too_many_connections) | — | 503 / 429 |
| [`shutting_down`](#shutting_down) | — | 503 |

クライアントは、未知のコードも受け入れ、error.fatal に従う。fatal な未知のコードでは、クライアントは internal と同じくバックオフで再接続する。HTTP では、クライアントは未知のコードをステータスで扱う（5xx と 429 は待ってやり直し、それ以外の 4xx はやり直さない）。

クライアントがいつ再接続し、いつ再接続せずに終えるかは[再接続とエラーの扱い](../reconnection.md)に、HTTP のエラーの形は [HTTP API リファレンス](../http.md#エラーproblem-details)にあります。

## 公式のアプリだけが受け取るコード

文字おこしくんは、公式のアプリ（管理画面と Discord アクティビティ）と文字おこしくんの運営者だけが使う操作に、この節のコードを返します。

### `key_not_found` {#key_not_found}

- 対処: keyId を確かめる。
- 起きたこと: 指定した API キーが無いか、別のテナントのものだった。
- HTTP: 404「API キーが見つかりません」

### `webhook_not_found` {#webhook_not_found}

- 対処: webhookId を確かめる。
- 起きたこと: 指定した Webhook の送信先が無いか、別のテナントのものだった。
- HTTP: 404「Webhook の送信先が見つかりません」

### `webhook_delivery_not_found` {#webhook_delivery_not_found}

- 対処: deliveryId と webhookId の組を確かめる。
- 起きたこと: 指定した送信の記録がその送信先に無い。終わった送信の記録は 7 日で削除される。削除された記録を指定したときにも、文字おこしくんはこのコードを返す。
- HTTP: 404「Webhook の送信の記録が見つかりません」

### `key_limit_exceeded` {#key_limit_exceeded}

- 対処: 使っていないキーを管理画面で取り消してから、新しいキーを作る。
- 起きたこと: テナントの有効な API キーの数が、上限（既定 25 個）に達した。
- HTTP: 409「API キーの数が上限に達しています」（limit）

### `webhook_limit_exceeded` {#webhook_limit_exceeded}

- 対処: 使っていない送信先を管理画面で削除してから、新しい送信先を追加する。
- 起きたこと: テナントの Webhook の送信先の数が上限（既定 5 個）に達した。
- HTTP: 409「Webhook の送信先の数が上限に達しています」（limit）

### `activity_not_configured` {#activity_not_configured}

- 対処: あなたの側では直せない（原因は、文字おこしくんの運営者の側の設定にある）。問題が続くときは、文字おこしくんの運営者に連絡する。
- 起きたこと: 文字おこしくんは、Discord アクティビティ専用の操作にだけ、このコードを返す。文字おこしくんのサーバーに、アクティビティの設定が無い。
- HTTP: 503「アクティビティの設定がありません」

### `invalid_grant` {#invalid_grant}

- 対処: アクティビティを開き直して認可をやり直す。
- 起きたこと: 文字おこしくんは、Discord アクティビティ専用の操作にだけ、このコードを返す。クライアントが送った OAuth2 の認可コードが、不正か、期限切れか、使用済みだった。
- HTTP: 400「認可コードが無効です」

### `not_in_channel` {#not_in_channel}

- 対処: ボイスチャンネルに入ってから、アクティビティを開く。
- 起きたこと: 文字おこしくんは、Discord アクティビティ専用の操作にだけ、このコードを返す。要求した人が、アクティビティを開いた Discord サーバーのどのボイスチャンネルにもいない。要求した人がボイスチャンネルにいるかを確かめられないときも、文字おこしくんはこのコードを返す。
- HTTP: 403「ボイスチャンネルにいません」

### `no_active_session` {#no_active_session}

- 対処: 誰かが /start を実行して、文字起こしが始まるのを待つ。
- 起きたこと: 文字おこしくんは、Discord アクティビティ専用の操作にだけ、このコードを返す。要求した人が今いるボイスチャンネルで、文字起こしが動いていない。
- HTTP: 404「稼働中のセッションがありません」

### `discord_unavailable` {#discord_unavailable}

- 対処: 少し待ってから、要求をやり直す。
- 起きたこと: 文字おこしくんは、Discord アクティビティ専用の操作にだけ、このコードを返す。Discord の障害か上限のために、文字おこしくんが Discord の API に問い合わせられない。
- HTTP: 502「Discord に接続できません」
