メインコンテンツまでスキップ

AICU 音声 API

OpenAI 互換の音声合成(TTS)と音声認識(STT)。1本のキーで LLM・画像と同じエンドポイントから使えます。

音声合成 TTS​

POST /v1/audio/speech(OpenAI 互換)​

curl -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer aicu_live_xxx" \
-H "Content-Type: application/json" \
-d '{"input": "こんにちは、AICU です", "voice": "nao"}' \
--output speech.mp3
パラメータ説明
input読み上げるテキスト
voiceAICU 登録ボイスID(下記一覧)、または ElevenLabs の生 voice_id(20桁英数字)。既定 nao
modeleleven_multilingual_v2(既定)または eleven_v3。下記「モデル」参照
seed生成の乱数種。省略時はキャラクターごとの既定値が使われます(キャラクターボイスと seed 参照)

多言語対応の ElevenLabs ボイスで、日本語・英語などをそのまま合成できます。災害時の多言語音声案内などに。

:::info API キーのスコープ 読み上げ・文字起こしには tts スコープ付きのキーが必要です。403 で required_scope: tts が返る場合はスコープ不足です。https://api.aicu.ai/dashboard/keys の「権限変更」で付けられます(発行し直す必要はありません)。 :::

POST /v1/el/tts(ElevenLabs 互換パス)​

body は /v1/audio/speech と同一。voice には AICU 登録ボイス(elena 等)だけでなく、ElevenLabs の生 voice_id をそのまま渡せます(自分の ElevenLabs アカウントで作成・クローンしたボイスなど)。TTS はゲートウェイ(鍵・課金・キャッシュ)に徹し、ボイスの善し悪しは ElevenLabs 側に任せる設計です — キャラクター固有の権利表記や royalty はまた別のレイヤー(/v1/char)で扱います。

# AICU 登録ボイス
curl -X POST https://api.aicu.ai/v1/el/tts \
-H "Authorization: Bearer aicu_live_xxx" \
-H "Content-Type: application/json" \
-d '{"input": "こんにちは、AICU です", "voice": "nao", "model": "eleven_v3"}' \
--output speech.mp3

# 生 voice_id(自分の ElevenLabs アカウントのボイス)
curl -X POST https://api.aicu.ai/v1/el/tts \
-H "Authorization: Bearer aicu_live_xxx" \
-H "Content-Type: application/json" \
-d '{"input": "Hello from AICU", "voice": "pqHfZKP75CvOlQylNhV4", "model": "eleven_v3"}' \
--output speech.mp3

モデル: Multilingual v2 と v3​

モデル特徴1リクエストの文字数上限
eleven_multilingual_v2(既定)安定・実績重視。従来どおりの合成10,000文字
eleven_v3text 中に [excited] [whispers] [laughs] のような角括弧の audio tag を書くと、その感情・話し方が音声に反映される5,000文字

長文を v3 で読み上げたい場合は、上限に収まるようチャンク分割してください(上限超過は 400 で明示的にエラーになります)。model に未知の値を渡した場合も 400(黙って v2 にフォールバックしません)。

:::info 長文の安定化(2026-08-16) 以前は長い入力(日本語で約3,000文字以上)が途中で 502/524 になることがありましたが、内部をストリーミング化して解消しました。表の上限(v2: 10,000文字 / v3: 5,000文字)まで安定して生成でき、応答が始まるまでの時間も短くなっています。音声はチャンクとして届き始めるので、受信側はレスポンスボディをそのままファイルに書けば完全な MP3 になります。同じ入力の再生成はキャッシュから返り、何度でも無料です。 :::

curl -X POST https://api.aicu.ai/v1/el/tts \
-H "Authorization: Bearer aicu_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"input": "[SHOUTING][excited] GOOOAL! What a strike!!",
"voice": "nao",
"model": "eleven_v3"
}' \
--output goal.mp3

上記は実際にサッカー実況サービス RoboFoot が使っているパターンです(tier に応じて [SHOUTING] [laughing] を重ねて熱量を上げる)。

ElevenLabs 公式の参考資料:

GET /v1/tts/voices・GET /v1/el/voices(要 API キー)​

curl https://api.aicu.ai/v1/tts/voices \
-H "Authorization: Bearer aicu_live_xxx"

2026-08-17 から API キーが必要になりました(scope は問いません)。未認証は 401 が返ります。

ボイス表記声質試聴
elenaエレナ・ブルーム落ち着いて聞き取りやすい、標準語の女性ナレーション
meiメイ・ソレイユ元気で子供でも聴きやすい、明るい少女の声
minaミナ・アズール落ち着いた知的なナレーション。長時間でも聴き疲れしない
naoナオ・ヴェルデ優しい理系男子のソフトボイス
sakiサキ・ノワール優しさとあやしい魅力がある囁きボイス
marshaマーシャ・アランチャさわやかで聴きやすい元気な女性
luc4ルカ(全力肯定彼氏くん)日本の関西を感じさせる明朗快活な青年

各サンプルは公式 seed で生成したメンバー自己紹介です(AiCuty-all-intro と同一テイク)。

いずれも ElevenLabs(多言語・v2/v3 両対応)。声質は tagline フィールドで返ります。

声はキャラクターごとに固定されています。 voice に slug を渡すだけで、 何度呼んでも同じ声・同じ抑揚が返ります。仕組みは キャラクターボイスと seed を参照してください。

クレジット表記義務があるボイスは、レスポンスヘッダ x-aicu-credit に表記文言が返ります。使用したモデルは x-aicu-model に返ります。

長文の読み上げ(章まるごと・書籍全文)​

1リクエストの上限(v2 は 10,000文字、v3 は 5,000文字)を超えるテキストは、 分割 → 個別に合成 → 連結します。数万〜十数万文字の原稿を1本のMP3にする場合の実装例です。

分割は句点で切る​

文字数で機械的に切ると語の途中で切れて発音が乱れます。句点を優先し、 段落をまたがないようにすると繋ぎ目が自然になります。

def split_chunks(text: str, limit: int = 4800):
chunks, buf = [], ""
for para in text.split("\n"):
para = para.strip()
if not para:
continue
if len(buf) + len(para) + 1 <= limit:
buf = f"{buf}\n{para}" if buf else para
continue
if buf:
chunks.append(buf); buf = ""
while len(para) > limit: # 段落自体が長い場合
cut = para.rfind("。", 0, limit)
cut = cut + 1 if cut > limit // 2 else limit
chunks.append(para[:cut]); para = para[cut:]
buf = para
if buf:
chunks.append(buf)
return chunks

チャンク単位でキャッシュする​

長文は必ずどこかで失敗します。テキストのハッシュをファイル名にして保存しておけば、 再実行時に未生成分だけを合成でき、原稿を一部だけ直したときも差分で済みます。

import hashlib, os, requests
from pathlib import Path

API = "https://api.aicu.ai/v1/tts/generate"
KEY = os.environ["AICU_API_KEY"] # scope: tts が必要
VOICE, CACHE = "mina", Path(".cache")
CACHE.mkdir(exist_ok=True)

def synth(text: str, i: int) -> Path:
tag = hashlib.sha1((VOICE + text).encode()).hexdigest()[:8]
dest = CACHE / f"{i:03d}-{tag}.mp3"
if dest.exists():
return dest
r = requests.post(API,
headers={"Authorization": f"Bearer {KEY}"},
json={"text": text, "slug": VOICE, "format": "mp3"}, timeout=600)
r.raise_for_status()
dest.write_bytes(r.content)
print(f" {i}: {r.headers.get('x-credits-used')} AP")
return dest

連結は ffmpeg の concat デミューサで​

同じ設定で合成された MP3 同士なら、再エンコードなし(-c copy)で連結できます。速く、劣化もしません。

import subprocess

def concat(parts, dest="out.mp3"):
listing = Path("concat.txt")
listing.write_text("".join(f"file '{p.resolve()}'\n" for p in parts))
subprocess.run(["ffmpeg", "-y", "-loglevel", "error",
"-f", "concat", "-safe", "0", "-i", str(listing),
"-c", "copy", dest], check=True)
listing.unlink()

章や見出しへシークできるようにする​

連結前に各チャンクの秒数を ffprobe で測って積算しておくと、 「この見出しから再生」を実装できます。プレイヤー側は audio.currentTime = 開始秒 を代入するだけです。

def duration(p) -> float:
out = subprocess.run(["ffprobe", "-v", "error", "-show_entries",
"format=duration", "-of", "csv=p=0", str(p)],
capture_output=True, text=True).stdout.strip()
return float(out or 0)

offsets, cursor = {}, 0.0
for i, ch in enumerate(chunks):
part = synth(ch, i)
offsets[i] = round(cursor, 2) # このチャンクの開始秒
cursor += duration(part)

コストの目安​

課金は送った文字数基準です(出来上がった音声の長さではありません)。 したがって、どの声で読んでも同じ原稿なら同じ金額になります。 実績はレスポンスヘッダ X-AICU-AP-Cost に返ります(x-credits-used も同じ値を返しますが、古いクライアント向けの別名です)。 現在の単価は料金を参照してください。

実測(同一テキスト261字、日本語):

ボイス所要12万字換算
elena21.8秒約2.8時間
saki23.3秒約3.0時間
mei24.3秒約3.1時間
mina27.4秒約3.5時間
nao27.9秒約3.6時間

12万字を読み上げて3時間強、約1,100 AP(¥11前後) です。 キャッシュヒットは無課金なので、原稿を直して再生成しても差分ぶんしかかかりません。

:::tip 読み上げ前にテキストを整える URL・表・箇条書き記号をそのまま渡すと、記号を読み上げてしまい聴きづらくなります。 https?://\S+ は「(リンク省略)」に置換、表組みは読み飛ばし、# や - の行頭記号は落とす、 といった前処理を入れるだけで完成度が大きく変わります。 :::

音声認識 STT​

POST /v1/audio/transcriptions(OpenAI 互換 / Whisper)​

さくらの AI Engine の whisper-large-v3-turbo で文字起こしします。1リクエスト最大 30MB / 30分。エージェント向けの機械可読な仕様は https://api.aicu.ai/skills/stt にあります。

# テキストだけ
curl -X POST https://api.aicu.ai/v1/audio/transcriptions \
-H "Authorization: Bearer aicu_live_xxx" \
-F file=@meeting.mp3 -F language=ja \
-F prompt="AICU, ComfyUI, しらいはかせ" # 固有名詞のヒント(誤変換対策)
# → {"text": "文字起こしされたテキスト…"}

# セグメント単位のタイムスタンプ(字幕・議事録向け)
curl -X POST https://api.aicu.ai/v1/audio/transcriptions \
-H "Authorization: Bearer aicu_live_xxx" \
-F file=@meeting.mp3 -F language=ja \
-F response_format=verbose_json -F "timestamp_granularities[]=segment"
# → {"task":"transcribe","language":"ja","duration":60.0,"text":"…","segments":[{"id":0,"start":0.0,"end":4.32,"text":"…"}, …]}

# 字幕ファイルをそのまま
curl -X POST https://api.aicu.ai/v1/audio/transcriptions \
-H "Authorization: Bearer aicu_live_xxx" \
-F file=@meeting.mp3 -F language=ja -F response_format=srt -o meeting.srt
パラメータ説明
file(必須)音声ファイル。30MB / 30分まで(超過は 413)
languageja / en など。指定すると精度と速度が上がる
prompt固有名詞・専門用語のヒント
response_formatjson(既定)/ verbose_json / text / srt / vtt
timestamp_granularities[]segment / word(verbose_json のときのみ)

response_format と timestamp_granularities[] はバックエンドにそのまま透過します。

:::tip 長い収録(会議・講義の録画) 動画をそのまま送らず、音声だけを mono 16kHz の mp3 に落として、30分ごとに分割してください(65分 → 3リクエスト)。60秒刻みのような細切れは、リクエスト数だけ増えてレート制限に当たりやすくなります。

ffmpeg -y -i recording.mp4 -vn -map 0:a:0 -ac 1 -ar 16000 -b:a 64k audio.mp3 # 3GB → 約8MB
ffmpeg -y -i audio.mp3 -f segment -segment_time 1800 -c copy part_%02d.mp3 # 30分ごと

:::

エラーとレート制限​

HTTP ステータスをそのまま返します(429 を本文に包んで 200 で返すことはしません)。curl -f / raise_for_status() / OpenAI SDK の自動リトライがそのまま効きます。

HTTP意味対処
413ファイルが 30MB 超音声のみ抽出・分割
429レート制限(ゲートウェイ 60 req/分/キー、またはバックエンドの枠)Retry-After 秒待って再送
502バックエンド障害(error.upstream_status に上流のステータス)時間をおいて再送。続くなら X-AICU-Request-Id を添えて問い合わせ

応答ヘッダ: X-AICU-AP-Cost(消費 AP)・X-AICU-Request-Id・X-AICU-Latency-Ms・X-RateLimit-Limit / -Remaining / -Reset。

料金​

音声 10 秒 = 3 AP(10秒単位切り上げ・2026年9月9日から)。1時間 ≈ 1,080 AP ≈ $0.11。verbose_json のときは実測の duration で課金します。それ以外の形式には duration が無いので、受け取った音声の長さからの見積もりと、文字起こしの文字数からの見積もりのうち、大きいほうで課金します。

OpenAI SDK からそのまま​

from openai import OpenAI
client = OpenAI(base_url="https://api.aicu.ai/v1", api_key="aicu_live_xxx")

# TTS
speech = client.audio.speech.create(model="tts-1", voice="nao", input="こんにちは")
speech.stream_to_file("out.mp3")

# STT(タイムスタンプ付き。429 は SDK が Retry-After を見て自動再送)
with open("meeting.mp3", "rb") as f:
tr = client.audio.transcriptions.create(
model="whisper-large-v3-turbo", file=f, language="ja",
response_format="verbose_json", timestamp_granularities=["segment"],
)
for seg in tr.segments:
print(f"{seg.start:8.2f} --> {seg.end:8.2f} {seg.text}")

ベース URL と API キーを差し替えるだけで、既存の OpenAI 互換コードから使えます。

© 2026 AICU Inc.