感情・スタイル指定対応、非同期ジョブ方式
API 利用にあたっての重要事項
これらを理解した上でご利用ください。
Advanced TTS API は 感情表現や読み上げスタイル指定に対応した、より自然な音声合成エンドポイントです。非同期ジョブ方式で、POST してジョブ ID を受け取り → ポーリングで結果を取得します。
現在の提供形態: Advanced TTS は Web UI 向けのベータ機能として提供されています。公開ドキュメントで案内するエンドポイントは /api/advanced-tts/ のみです。認証は 標準 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 | 必須 | 読み上げるテキスト(無料 400 文字 / 有料 1000-3000 文字) |
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",
},
)
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},
).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 | ヤン |
Advanced TTS では、リクエスト直後の HTTP エラーだけでなく、ジョブ投入後のポーリング結果として status=failed の JSON が返る場合があります。Web UI では code に応じて案内音声を再生しますが、API レスポンス自体に案内音声 URL は含まれません。
| HTTP | code | 原因 |
|---|---|---|
| 400 | validation_error | text 形式不正 / 短すぎる |
| 400 | invalid_voice | 不明な voice 名 |
| 400 | text_too_long | プラン上限超過 |
| 400 | invalid_speed / invalid_pitch | 範囲外 |
| 400 | invalid_token | token ヘッダーが不正、または一致するユーザー設定がない |
| 403 | (HTML) | CSRF 不一致 / 未ログイン |
| 429 | quota_exceeded | 利用可能な文字数不足。Web UI では案内音声とプラン案内を表示 |
| 429 | rate_limited | レート制限超過。retry_after 秒後に再試行。Web UI では案内音声を再生 |
| 200 | status=failed | ポーリング時のジョブ失敗。HTTP ステータスではなく JSON の status / code を確認 |
| プラン | 上限 |
|---|---|
| 無料 | 400 文字 |
| 有料(標準) | 1,000 文字 |
| 有料(Beta 文字数拡張オプトイン) | 3,000 文字 |
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(時限)です。取得後は速やかにダウンロード・保存してください以下の用途での利用を禁止します:
詳細は 利用規約 を確認してください。
不具合や予期しない挙動を確認したら、まず以下を試してください:
{"text":"テスト","voice":"Ruby"} で 202 が返るか上記で解消しない場合は お問い合わせ からご連絡ください。再現に必要な以下の情報を添えていただけるとスムーズです:
変更履歴について: Advanced TTS はベータ機能です。API 仕様は予告なく変更される可能性があります。大きな変更は 音読さんトップ 等でアナウンスする場合がありますが、軽微な変更はサイレントに行われることがあります。