感情・スタイル指定対応、非同期ジョブ方式
提供条件
token ヘッダー(アクセストークン 64 文字)。ポーリングにはジョブ作成時に返る job_token を X-Job-Token ヘッダーで送ります(概要と認証方式)Advanced TTS API は 感情表現や読み上げスタイル指定に対応した、より自然な音声合成エンドポイントです。非同期ジョブ方式で、POST してジョブ ID を受け取り → ポーリングで結果を取得します。
現在の提供形態: Advanced TTS API は Beta 版として提供しています。公開エンドポイントは POST /api/advanced-tts/(ジョブ作成)と GET /api/advanced-tts/jobs/{job_id}/(ポーリング)です。認証は 標準 TTS API と同じ token ヘッダー、またはログイン済みのセッション cookie + CSRF トークンです。
下記サンプルは、token ヘッダーでジョブを投入する流れです。Web UI と同じセッション認証で利用する場合は、ログイン済み cookie と X-CSRFToken を送信してください。
| ヘッダー | 値 |
|---|---|
Content-Type | application/json |
token | アクセストークン(64 文字)。token 認証時は CSRF 不要 |
X-Job-Token | ジョブ参照トークン。GET ポーリング時は POST レスポンスの job_token をこのヘッダーで送信 |
Cookie | sessionid=...; csrftoken=...(セッション認証時) |
X-CSRFToken | cookie の csrftoken と同じ値(セッション認証時) |
Referer | https://ondoku3.com/ja/advanced-tts-beta/(セッション認証時) |
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
text | string | 必須 | 読み上げるテキスト(10 文字以上。API の受付上限は無料 400 文字 / 有料 3,000 文字) |
voice | string | 任意 | 音声名(下記リストから指定)。デフォルト: Ruby |
tone | string | 任意 | 読み上げスタイル(例: warm and friendly, storytelling)。最大 100 文字 |
speed | number | 任意 | 0.5 〜 1.5(デフォルト 1.0) |
pitch | number | 任意 | -1.0 〜 1.0(デフォルト 0.0) |
model | string | 任意 | flash(高速) / pro(高品質) |
seed | integer | 任意 | 声色を固定する乱数シード(-2147483648 〜 2147483647) |
?token={job_token} クエリパラメータでのポーリングはサポートされません。ジョブステータス確認は必ず X-Job-Token ヘッダーで送信してください。
# 1) TTS ジョブを投入
JOB=$(curl -s \
-X POST https://ondoku3.com/api/advanced-tts/ \
-H "Content-Type: application/json" \
-H "token: $ONDOKU_TOKEN" \
-d '{"text":"こんにちは、音読さんです。","voice":"Ruby","tone":"warm and friendly"}')
JOB_ID=$(echo "$JOB" | jq -r .job_id)
JOB_TOKEN=$(echo "$JOB" | jq -r .job_token)
sleep "$(echo "$JOB" | jq -r '.min_poll_after_ms / 1000')"
# 2) ジョブ完了までポーリング
while :; do
R=$(curl -s \
-H "X-Job-Token: ${JOB_TOKEN}" \
"https://ondoku3.com/api/advanced-tts/jobs/${JOB_ID}/")
STATUS=$(echo "$R" | jq -r .status)
echo "status: $STATUS"
[ "$STATUS" = "succeeded" ] && break
[ "$STATUS" = "failed" ] && { echo "$R"; exit 1; }
sleep "$(echo "$R" | jq -r '(.poll_after_ms // 3000) / 1000')"
done
# 3) MP3 をダウンロード
URL=$(echo "$R" | jq -r .url)
curl -L "$URL" -o output.mp3
import os
import time
import requests
TOKEN = os.environ["ONDOKU_TOKEN"]
# 1) ジョブを投入
res = requests.post(
"https://ondoku3.com/api/advanced-tts/",
headers={
"Content-Type": "application/json",
"token": TOKEN,
},
json={
"text": "こんにちは、音読さんです。",
"voice": "Ruby",
"tone": "warm and friendly",
},
timeout=30,
)
res.raise_for_status()
job = res.json()
job_id, job_token = job["job_id"], job["job_token"]
time.sleep(job["min_poll_after_ms"] / 1000)
# 2) ポーリング
while True:
r = requests.get(
f"https://ondoku3.com/api/advanced-tts/jobs/{job_id}/",
headers={"X-Job-Token": job_token},
timeout=10,
).json()
print("status:", r["status"])
if r["status"] == "succeeded":
break
if r["status"] == "failed":
raise RuntimeError(r)
time.sleep(r.get("poll_after_ms", 3000) / 1000)
# 3) MP3 をダウンロード
mp3 = requests.get(r["url"], timeout=60).content
with open("output.mp3", "wb") as f:
f.write(mp3)
print(f"saved: output.mp3 ({len(mp3)} bytes)")
import { writeFile } from "node:fs/promises";
const TOKEN = process.env.ONDOKU_TOKEN!;
// 1) ジョブを投入
const submit = await fetch("https://ondoku3.com/api/advanced-tts/", {
method: "POST",
headers: {
"Content-Type": "application/json",
token: TOKEN,
},
body: JSON.stringify({
text: "こんにちは、音読さんです。",
voice: "Ruby",
tone: "warm and friendly",
}),
});
type AdvancedTTSJobResponse = {
job_id: string;
job_token: string;
status: "pending" | "running" | "succeeded" | "failed";
min_poll_after_ms: number;
poll_url: string;
};
type AdvancedTTSPollResponse = {
job_id: string;
status: "pending" | "running" | "succeeded" | "failed";
poll_after_ms?: number;
url?: string;
pk?: number;
seed?: number;
code?: string;
error?: string;
};
const job = (await submit.json()) as AdvancedTTSJobResponse;
// 2) ポーリング
let result: AdvancedTTSPollResponse;
await new Promise((resolve) => setTimeout(resolve, job.min_poll_after_ms));
while (true) {
const r = await fetch(`https://ondoku3.com/api/advanced-tts/jobs/${job.job_id}/`, {
headers: { "X-Job-Token": job.job_token },
}).then((res) => res.json() as Promise);
console.log("status:", r.status);
if (r.status === "succeeded") { result = r; break; }
if (r.status === "failed") throw new Error(JSON.stringify(r));
await new Promise((resolve) => setTimeout(resolve, r.poll_after_ms ?? 3000));
}
// 3) MP3 をダウンロード
if (!result.url) throw new Error("audio URL is missing");
const mp3 = Buffer.from(await (await fetch(result.url)).arrayBuffer());
await writeFile("output.mp3", mp3);
console.log(`saved: output.mp3 (${mp3.byteLength} bytes)`);
POST レスポンス(202 Accepted):
{
"job_id": "YOUR_JOB_ID",
"job_token": "YOUR_JOB_TOKEN",
"status": "pending",
"min_poll_after_ms": 4550,
"poll_url": "/api/advanced-tts/jobs/YOUR_JOB_ID/"
}
| フィールド | 型 | 説明 |
|---|---|---|
job_id | string | ジョブ ID(UUID) |
job_token | string | このジョブのポーリングに必要な参照トークン |
status | string | 投入直後の状態。通常は pending |
min_poll_after_ms | number | 初回ポーリングまで待つ推奨ミリ秒 |
poll_url | string | ポーリング先の相対 URL。アクセス時は POST レスポンスの job_token を X-Job-Token ヘッダーで送信する |
GET ポーリング(処理中):
{
"job_id": "YOUR_JOB_ID",
"status": "pending",
"poll_after_ms": 1000
}
GET ポーリング(成功時):
{
"job_id": "YOUR_JOB_ID",
"status": "succeeded",
"url": "https://storage.googleapis.com/ondoku3/voice/...",
"pk": 12345,
"seed": 123456789
}
GET ポーリング(失敗時):
{
"job_id": "YOUR_JOB_ID",
"status": "failed",
"code": "internal_error",
"error": "内部サーバーエラーが発生しました"
}
| フィールド | 型 | 説明 |
|---|---|---|
job_id | string | ジョブ ID(UUID) |
status | string | pending / running / succeeded / failed |
poll_after_ms | number | 処理中に返る、次回ポーリングまで待つ推奨ミリ秒 |
url | string | 成功時のみ返る音声ファイル URL |
pk | number | 成功時のみ返る生成履歴(Sentence)の ID |
seed | number | seed が保存されている場合のみ返る、生成に使われた乱数シード |
code | string | 失敗時のみ返るエラーコード(一覧は エラー・案内レスポンス) |
error | string | 失敗時のみ返るエラーメッセージ |
Advanced TTS API の voice パラメータに指定できる音声名 25 種類。各音声のサンプルは 音声サンプル記事で試聴できます。
| 音声名 | 読み仮名 |
|---|---|
Anna | アンナ |
Chloe | クロエ |
Ellis | エリス |
Emma | エマ |
Flora | フローラ |
Iris | アイリス |
Lena | レナ |
Luna | ルナ |
Misa | ミサ |
Ruby | ルビー |
Sophie | ソフィー |
Tina | ティナ |
| 音声名 | 読み仮名 |
|---|---|
Ash | アッシュ |
Chris | クリス |
Eden | エデン |
Gray | グレイ |
Hope | ホープ |
Hugo | ヒューゴ |
Kai | カイ |
Leo | レオ |
Noah | ノア |
Reid | リード |
Roy | ロイ |
Sam | サム |
Yann | ヤン |
エラーは JSON の code(エラーコード)と error(メッセージ)で返ります(403 の CSRF エラーと 405 を除く)。HTTP ステータスだけでなく code を見て処理してください。ジョブ投入後の失敗は、ポーリング結果の status=failed で返ります。Web UI では code に応じて案内音声を再生しますが、API レスポンス自体に案内音声 URL は含まれません。
POST /api/advanced-tts/| HTTP | code | 原因 |
|---|---|---|
| 400 | invalid_token | token ヘッダーが 64 文字でない、または一致する有効なユーザーがない |
| 400 | invalid_json | 本文が JSON として読めない |
| 400 | validation_error | text が 10 文字未満 / 絵文字だけ / 同じ文字が 20 文字以上続く |
| 400 | invalid_speed / invalid_pitch | 数値でない、または範囲外 |
| 400 | invalid_temperature | temperature 項目(この文書の対象外。送る必要はありません)を送り、値が数値でないか 0 / 0.5 / 0.7 / 1.0 以外 |
| 400 | invalid_seed | seed が整数でない、または範囲外 |
| 400 | invalid_model | model が flash / pro 以外 |
| 400 | client_ip_unresolved | token なし・未ログインの利用で、接続元 IP を確定できない |
| 400 | text_too_long | 受付上限(無料 400 文字 / 有料 3,000 文字)を超えている |
| 400 | invalid_voice | 不明な voice 名 |
| 400 | content_policy_violation | 利用ポリシーにより読み上げできないテキスト |
| 400 | invalid_request | その他の入力エラー(ユーザー辞書で置き換えた後の本文が上限を超えた場合など) |
| 403 | (HTML) | token ヘッダーなしのセッション認証で、CSRF トークンが不一致 / 未ログイン |
| 405 | (本文なし) | POST 以外のメソッド |
| 429 | quota_exceeded | 利用可能な文字数が不足。available(残りの文字数)も返る |
| 429 | rate_limited | レート制限超過(文字数上限・レート制限)。retry_after 秒(Retry-After ヘッダーも同じ値)待って再試行 |
| 500 | service_not_configured | サーバー側の設定不備 |
| 500 | internal_error | サーバー側エラー(時間を置いて再試行) |
| 503 | unsupported_location | 音声合成を提供できない地域からの処理。互換のため error_code(UNSUPPORTED_LOCATION)も返る |
| 503 | tts_retry_exhausted | 一時的に受け付けられない(時間を置いて再試行) |
GET /api/advanced-tts/jobs/{job_id}/| HTTP | code | 原因 |
|---|---|---|
| 200 | status=failed | ジョブの失敗。HTTP ステータスではなく JSON の status / code を確認(code は下の表) |
| 403 | forbidden | X-Job-Token がジョブと一致しない(ログイン済みセッションでは、自分のジョブでない) |
| 404 | not_found | ジョブが存在しない |
| 405 | (本文なし) | GET 以外のメソッド |
status は pending(処理待ち)→ running(処理中)→ succeeded(完了)または failed(失敗)と進みます。failed の時の code は次のとおりです。音声生成の処理から下表以外の code が返ることもあるため、知らない code も失敗として扱ってください。
| code | 原因 |
|---|---|
content_policy_violation | 利用ポリシーにより読み上げできないテキスト。本文を変えて再投入 |
unsupported_location | 音声合成を提供できない地域からの処理 |
tts_retry_exhausted | 一時的に生成できなかった。時間を置いて再投入 |
invalid_request | 生成時の入力エラー |
job_timeout | 生成に時間がかかりすぎて打ち切った。再投入 |
enqueue_failed | ジョブを処理待ちに入れられなかった |
user_unavailable | 生成中にアカウントが退会・削除された |
internal_error | サーバー側エラー |
| プラン | API の受付上限 |
|---|---|
| 無料 | 400 文字 |
| 有料 | 3,000 文字 |
補足: Web UI の入力欄は、有料プランでも既定では 1,000 文字までの表示です(Beta 設定の文字数拡張で 3,000 文字まで広がります)。API はこの設定に関係なく、有料プランなら 3,000 文字まで受け付けます。
現在適用している制限: ジョブ作成 POST /api/advanced-tts/ は、接続元 IP ごとに 60 秒あたり 10 回までです(同じ IP から複数のトークンを使っても共有)。超えると HTTP 429 の rate_limited を返し、retry_after と Retry-After ヘッダーに待つ秒数が入ります。
このほかに、Advanced TTS 専用のレート制限は、現在 shadow mode で観測中です。この専用制限ではまだリクエストを遮断せず、超過しても 429 は返しません。正式適用後は、下記の値を超えた場合に rate_limited の JSON と Retry-After ヘッダーを返す予定です。
| 対象 | 単位 | 予定制限 | 補足 |
|---|---|---|---|
ジョブ作成 POST /api/advanced-tts/ | ユーザー ID(未ログイン時は IP) | 30 回 / 60 秒 180 回 / 1 時間 | 有料・無料などのユーザーステータスでは分けません |
ステータス確認 GET /api/advanced-tts/jobs/{job_id}/ | ログインユーザーまたはジョブ参照トークン + job_id | 120 回 / 60 秒 | 同じジョブへの過剰ポーリングを抑制します |
| ステータス確認の合計 | ログインユーザーまたはジョブ参照トークン | 600 回 / 60 秒 | 複数ジョブの同時ポーリングを抑制します。API トークン認証またはログイン済みセッションで作成したジョブを X-Job-Token ヘッダーで確認する場合、所有者ユーザーではなくジョブ参照トークン単位で扱います |
| 存在しないジョブ / 権限なしジョブの確認 | IP | 30 回 / 60 秒 | 不正な job_id の総当たりを抑制します |
既存の文字数不足は quota_exceeded の JSON で返ります。現行の汎用レート制限に当たった場合は rate_limited の JSON で返ります。Web UI では案内音声を再生しますが、API クライアントは HTTP ステータスだけでなく JSON の code を見て処理してください。
429 のレスポンス例:
{
"code": "rate_limited",
"error": "レート制限を超えました。しばらく経ってから再試行してください。",
"retry_after": 55
}
| HTTP / 状況 | 対応 |
|---|---|
| 429 (Rate limit) | retry_after 秒、または最低 5 秒待ってから再試行 |
| 5xx | exponential backoff(2 秒 → 4 秒 → 8 秒、最大 3 回) |
| 4xx(400・403 等) | リトライしない。CSRF トークンや voice 名を確認 |
| ジョブ status=failed | code を確認。生成失敗は同じ seed でリトライしても同じ結果になることがあるため、seed を変えて再試行 |
| ジョブが pending のまま長時間 | 5 分以上 pending の場合はサーバー側で滞留している可能性。新規ジョブとして再投入 |
min_poll_after_ms を最低の待機時間として尊重してくださいpoll_after_ms が含まれる場合は、その値を次回ポーリングまでの最低待機時間として尊重してくださいpoll_after_ms がない場合は 3 〜 5 秒間隔でポーリングしてください。短すぎるとレート制限に当たる可能性がありますurl は署名付き URL(時限)です。取得後は速やかにダウンロード・保存してください以下の用途での利用を禁止します:
詳細は 利用規約 を確認してください。
Advanced TTS API(Beta)は、月間稼働率 98.0% を SLA として提供します。ジョブの作成(POST)とポーリング(GET)の両方のリクエストを数え、音声生成の失敗(ポーリング結果のジョブの status が failed)は停止に数えません。
稼働率の数え方・停止に含めないもの・SLA を下回った場合の扱いは両 API に共通で、標準 TTS API の SLA 節 を正本とします。SLA の対象は稼働率のみで、応答時間は含みません。
互換性を壊す変更(項目の削除・型や意味の変更・エンドポイントの廃止など)は、実施の 30 日前にこの節で告知します。項目や声の追加など互換を保つ主な変更も、実施後にこの節に記録します(事前告知はしません)。緊急のセキュリティ対応は例外です。
| 日付 | 内容 |
|---|---|
| 2026-10-02 | Advanced TTS API を公式 API(Beta 版)として提供開始し、SLA(月間稼働率 98.0%)を設定。互換性を壊す変更は 30 日前にこの節で告知するルールを開始 |
不具合や予期しない挙動を確認したら、まず以下を試してください:
{"text":"これは疎通テストです。","voice":"Ruby"}(text は 10 文字以上) で 202 が返るか上記で解消しない場合は お問い合わせ からご連絡ください。再現に必要な以下の情報を添えていただけるとスムーズです:
仕様の変更について: API 仕様の変更は 変更履歴 に記載します。互換性を壊す変更は実施の 30 日前に告知します。