Aller au contenu principal

AICU Audio API

Synthèse vocale (TTS) et transcription (STT) compatibles OpenAI. Une seule clé, le même hôte d'endpoints que pour le LLM et les images.

Synthèse vocale (TTS)​

POST /v1/audio/speech (compatible 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
ParamètreDescription
inputLe texte à prononcer
voiceUn ID de voix AICU (voir la liste ci-dessous), ou un voice_id ElevenLabs brut (20 caractères alphanumériques). Par défaut nao
modeleleven_multilingual_v2 (par défaut), eleven_v3, eleven_v4 ou eleven_v4_turbo. Voir « Modèles » ci-dessous
seedSeed aléatoire pour la génération. S'il est omis, le seed par défaut de chaque personnage est utilisé (voir Voix et seeds des personnages). Ignoré par eleven_v4 et eleven_v4_turbo : ces modèles ne prennent pas de seed, nous n'en envoyons donc pas
forceContourne le cache et relance la synthèse. Utilisez-le quand vous voulez une prise différente de la même réplique

Les voix ElevenLabs sont multilingues : le japonais, l'anglais et d'autres langues sont synthétisés tels quels, ce qui est utile par exemple pour des annonces d'urgence multilingues.

Scopes des clés API

La synthèse vocale et la transcription exigent une clé avec le scope tts. Si vous obtenez un 403 avec required_scope: tts, ce scope manque à la clé : ajoutez-le avec 権限変更 (Modifier les autorisations) sur https://api.aicu.ai/dashboard/keys. Inutile de réémettre la clé.

POST /v1/el/tts (chemin compatible ElevenLabs)​

Le corps est identique à celui de /v1/audio/speech. voice accepte non seulement les voix enregistrées chez AICU (elena et les autres) mais aussi un voice_id ElevenLabs brut, y compris les voix que vous avez créées ou clonées dans votre propre compte ElevenLabs. Ici, la TTS n'est volontairement qu'une passerelle (clés, facturation, cache) ; la qualité d'une voix relève d'ElevenLabs. Les mentions obligatoires et les redevances propres aux personnages se trouvent dans une autre couche (/v1/char).

# Voix enregistrée chez 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 brut (une voix de votre propre compte 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

Modèles​

ModèleCaractéristiquesCaractères par requête
eleven_multilingual_v2 (par défaut)Stable et éprouvé. La synthèse telle que vous la connaissez. 29 langues10,000 caractères
eleven_v3Écrivez des audio tags entre crochets comme [excited], [whispers] ou [laughs] dans text, et l'émotion ou la diction correspondante se retrouve dans l'audio5,000 caractères
eleven_v4Sorti le 2026-09-28. Plus de 90 langues, et il suit plus fidèlement les audio tags, y compris les tags libres en japonais. Environ deux fois plus rapide que v310,000 caractères
eleven_v4_turboLes mêmes voix qu'eleven_v4 à faible latence, à moitié prix. Idéal pour le dialogue et les agents10,000 caractères

Les formes courtes sont aussi acceptées : v2, v3, v4, v4_turbo, turbo.

Pour lire un texte long avec v3, découpez-le pour que chaque requête reste sous la limite (un dépassement renvoie explicitement 400). Une valeur de model inconnue renvoie aussi 400 : nous ne revenons jamais silencieusement à v2.

💡 Choisir un modèle

  • Dialogue, agents, faire sortir la première syllabe vite → eleven_v4_turbo
  • Narration, livres audio, jeu de personnage → eleven_v4
  • Vous utilisez déjà une voix v3 en production et voulez la garder telle quelle → restez sur eleven_v3. Nous ne le retirons pas

eleven_v4 et eleven_v4_turbo n'acceptent pas seed, style ni use_speaker_boost (le fournisseur indique can_use_style: false). Nous les retirons de la requête envoyée en amont plutôt que de faire semblant de les respecter. En pratique, v4 n'a pas besoin de seed : mesuré sur trois prises de la même réplique, il est aussi stable que v3 avec un seed fixé.

Stabilité des entrées longues (2026-08-16)

Les entrées longues (environ 3,000 caractères japonais et plus) échouaient auparavant en cours de route avec 502/524. Nous avons passé le traitement interne en streaming, et c'est corrigé. Vous pouvez désormais générer de façon fiable jusqu'aux limites du tableau (v2 : 10,000 caractères / v3 : 5,000 caractères), et le délai avant le premier octet est aussi plus court. L'audio arrive par morceaux ; côté réception, il suffit d'écrire le corps de la réponse directement dans un fichier pour obtenir un MP3 complet. Régénérer la même entrée est servi depuis le cache, gratuitement, à chaque fois.

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

C'est exactement le schéma qu'utilise en production notre service de commentaire de football en direct RoboFoot : il empile [SHOUTING] et [laughing] par paliers pour faire monter l'intensité.

Références officielles d'ElevenLabs :

GET /v1/tts/voices et GET /v1/el/voices (clé API requise)​

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

Depuis le 2026-08-17, ces endpoints exigent une clé API (n'importe quel scope). Les requêtes non authentifiées reçoivent un 401.

VoixNom affichéCaractèreExtrait
elenaElena BloomNarration féminine calme et facile à suivre, en japonais standard
meiMei SoleilJeune fille pétillante, énergique et facile à suivre pour les enfants
minaMina AzureNarration posée et intelligente, qui ne fatigue pas sur de longues sessions
naoNao VerdeGarçon passionné de sciences, doux et à la voix posée
sakiSaki NoireVoix chuchotée, douce avec une pointe de mystère
marshaMarsha AranciaVoix féminine fraîche, enjouée et agréable à écouter
luc4Luca (ルカ / LuC4, le petit ami qui approuve tout)Jeune homme joyeux et extraverti, avec une touche du Kansai

Chaque extrait est l'autoprésentation du membre, générée avec le seed officiel (la même prise que AiCuty-all-intro).

Toute la distribution en un seul script​

Les sept voix sont des personnages ; le moyen le plus rapide d'entendre la différence est de laisser chacun se présenter. voice prend le slug ; vous ne passez pas de modèle, sauf si vous voulez remplacer celui sur lequel le personnage est défini (nao est sur eleven_v4, les autres sont par défaut sur eleven_multilingual_v2).

import pathlib, requests

API = "https://api.aicu.ai/v1/audio/speech"
KEY = "aicu_live_xxx"

# Les autoprésentations de référence. Les audio tags entre crochets n'agissent que sur
# eleven_v3 / eleven_v4 / eleven_v4_turbo ; sur multilingual_v2, ils sont ignorés.
CAST = {
"elena": "[cheerfully] こんにちは、エレナ・ブルームです。私はエルエルエムとエスエヌエス活用を担当しています。[excited] アイキューティーのセンターとして、みなさんをお迎えしますね!",
"mei": "[cheerfully] こんにちは、メイ・ソレイユです!メイはキービジュアルと画像を担当しているよ。[excited] 一緒に、うちの子を描こうね!",
"mina": "[softly] こんにちは、ミナ・アズールです。私は調査と分析、そして倫理と法律を担当しています。[cheerfully] 放送部で、アナウンスの練習をしています。",
"saki": "[softly] こんばんは、サキ・ノワールです。私は動画を担当しています。[mischievously] 夜の渋谷を撮るのが、好きなんです。",
"nao": "[cheerfully] やあ、ナオ・ヴェルデだよ。僕は音楽と開発技術担当。[excited] ディーティーエムやゲーム開発も好きだな。よろしくね!",
"marsha": "[元気よく挨拶] ¡Hola! はじめまして、マーシャ・アランチャです。アタシは国際文化と書籍出版に興味があるよ〜。[excited] まって!? いまのフレーズ、最高じゃない!? 特集にしたいネ!",
"luc4": "[cheerfully] おはよう!僕は 全力肯定彼氏くん、コードネーム ルーシーフォウ、ルカって呼んでね。[excited] 君のペースで英語の勉強を応援するよ!",
}

for voice, line in CAST.items():
r = requests.post(API, headers={"Authorization": f"Bearer {KEY}"},
json={"input": line, "voice": voice, "model": "eleven_v4"}, timeout=120)
r.raise_for_status()
pathlib.Path(f"{voice}.mp3").write_bytes(r.content)
# Quel modèle a réellement tourné, et ce que cela a coûté
print(voice, r.headers["X-AICU-Model"], r.headers["X-AICU-AP-Cost"] + " AP")

La même chose en shell, si vous voulez simplement les écouter :

for v in elena mei mina saki nao marsha luc4; do
curl -s -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d "{\"input\": \"[cheerfully] こんにちは、$v です。\", \"voice\": \"$v\", \"model\": \"eleven_v4\"}" \
--output "$v.mp3"
done

💡 Qui est qui

Les noms, rôles, anniversaires et le modèle sur lequel la voix de chaque personnage est définie proviennent de GET /v1/char : c'est le registre des personnages. GET /v1/tts/voices est l'ancienne liste et renvoie désormais simplement ici.

curl -s https://api.aicu.ai/v1/char -H "Authorization: Bearer $AICU_API_KEY" \
| python3 -m json.tool

C'est la réplique de Marsha qu'il faut surveiller. ¡Hola! est écrit volontairement en orthographe espagnole : voir Maîtriser la synthèse vocale japonaise. Les mauvaises lectures japonaises sont transcrites en kana, mais les mots étrangers sont envoyés dans leur propre écriture. Le sens s'inverse.

Ce sont toutes des voix ElevenLabs, qui fonctionnent avec v2 comme avec v3. La description du personnage est renvoyée dans le champ tagline.

La voix de chaque personnage est fixée. Passez le slug comme voice et vous obtenez la même voix avec la même diction, appel après appel. Voir Voix et seeds des personnages pour le fonctionnement.

Pour les voix qui exigent une mention, la ligne de crédit requise est renvoyée dans l'en-tête de réponse x-aicu-credit. Le modèle réellement utilisé est renvoyé dans x-aicu-model.

Lire un texte long (un chapitre entier, un livre complet)​

Un texte plus long que la limite par requête (10,000 caractères sur v2, 5,000 sur v3) doit être découpé, synthétisé morceau par morceau, puis concaténé. Voici comment transformer un manuscrit de dizaines ou de centaines de milliers de caractères en un seul MP3.

Découper aux limites de phrases​

Couper mécaniquement au nombre de caractères tranche des mots en deux et abîme la prononciation. Privilégiez la ponctuation de fin de phrase et évitez de couper à cheval sur des paragraphes : les raccords sonneront naturels.

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: # le paragraphe lui-même est trop long
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

Mettre en cache par morceau​

Les longues tâches échouent toujours quelque part. Enregistrez chaque résultat sous un nom de fichier dérivé du hachage de son texte : une relance ne synthétise que ce qui manque, et modifier une partie du manuscrit ne vous coûte que la différence.

import hashlib, os, requests
from pathlib import Path

API = "https://api.aicu.ai/v1/tts/generate"
KEY = os.environ["AICU_API_KEY"] # exige le 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

Concaténer avec le démultiplexeur concat de ffmpeg​

Des MP3 synthétisés avec les mêmes réglages peuvent être assemblés sans réencodage (-c copy). C'est rapide et sans perte.

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()

Rendre les chapitres et les titres accessibles directement​

Mesurez la durée de chaque morceau avec ffprobe avant la concaténation et tenez un total cumulé : vous pourrez implémenter « lire à partir de ce titre ». Côté lecteur, il suffit d'affecter audio.currentTime = startSeconds.

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) # seconde de début de ce morceau
cursor += duration(part)

Ce que cela coûte​

La facturation se base sur le nombre de caractères envoyés, et non sur la durée de l'audio obtenu : le même manuscrit coûte donc la même chose quelle que soit la voix qui le lit. Le montant réellement facturé revient dans l'en-tête de réponse X-AICU-AP-Cost (x-credits-used porte le même nombre et est conservé pour les anciens clients). Pour le tarif actuel, voir Tarifs.

Mesuré (le même texte japonais de 261 caractères) :

VoixDuréeExtrapolé à 120k caractères
elena21.8senv. 2.8 heures
saki23.3senv. 3.0 heures
mei24.3senv. 3.1 heures
mina27.4senv. 3.5 heures
nao27.9senv. 3.6 heures

120,000 caractères donnent un peu plus de trois heures d'audio pour environ 1,100 AP (environ ¥11). Les réponses en cache sont gratuites : modifier le manuscrit et régénérer ne vous coûte que les parties changées.

Nettoyez le texte avant la narration

Si vous envoyez tels quels des URL, des tableaux et des puces, le synthétiseur lit la ponctuation à voix haute, ce qui est pénible à écouter. Un peu de prétraitement change tout : remplacez https?://\S+ par quelque chose comme « (lien omis) », ignorez le balisage des tableaux et retirez les # et - en début de ligne.

Transcription (STT)​

POST /v1/audio/transcriptions (compatible OpenAI / Whisper)​

La transcription tourne sur whisper-large-v3-turbo via Sakura AI Engine. Jusqu'à 30MB / 30 minutes par requête. Voir https://api.aicu.ai/skills/stt pour la spécification lisible par machine destinée aux agents.

# Texte seul
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, しらいはかせ" # indices de noms propres (évite les erreurs de reconnaissance)
# → {"text": "the transcribed text…"}

# Horodatage par segment (pour les sous-titres et les comptes rendus de réunion)
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":"…"}, …]}

# Un fichier de sous-titres, directement
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
ParamètreDescription
file (obligatoire)Le fichier audio. Jusqu'à 30MB / 30 minutes (au-delà, renvoie 413)
languageja, en, etc. Le préciser améliore la précision et la vitesse
promptIndices pour les noms propres et la terminologie du domaine
response_formatjson (par défaut) / verbose_json / text / srt / vtt
timestamp_granularities[]segment / word (uniquement avec verbose_json)

response_format et timestamp_granularities[] sont transmis tels quels au backend.

Enregistrements longs (réunions, cours)

N'envoyez pas la vidéo. Extrayez l'audio en MP3 mono 16kHz et découpez-le en morceaux de 30 minutes (65 minutes → 3 requêtes). Découper en minuscules morceaux de 60 secondes ne fait que multiplier le nombre de requêtes et vous rapproche des limites de débit.

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

Erreurs et limites de débit​

Nous renvoyons le vrai statut HTTP (nous n'enveloppons jamais un 429 dans un corps de réponse 200). curl -f, raise_for_status() et les nouvelles tentatives automatiques du SDK OpenAI fonctionnent donc directement.

HTTPSignificationQue faire
413Le fichier dépasse 30MBExtraire l'audio seul, puis découper
429Limite de débit atteinte (passerelle : 60 req/min/clé, ou le quota propre du backend)Attendre Retry-After secondes et réessayer
502Panne du backend (le statut amont est dans error.upstream_status)Réessayer un peu plus tard. Si cela persiste, contactez-nous avec le X-AICU-Request-Id

En-têtes de réponse : X-AICU-AP-Cost (AP consommés), X-AICU-Request-Id, X-AICU-Latency-Ms, et X-RateLimit-Limit / -Remaining / -Reset.

Tarifs​

10 secondes d'audio = 3 AP (arrondi à l'unité de 10 secondes supérieure) depuis le 2026-09-09. Une heure ≈ 1,080 AP ≈ $0.11. Avec verbose_json, nous facturons sur la duration mesurée ; les autres formats ne portent pas de duration, nous estimons donc à partir de l'audio reçu et de la longueur de la transcription, et facturons la plus grande des deux valeurs.

Remplacement direct avec le SDK OpenAI​

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 (avec horodatage ; sur un 429, le SDK lit Retry-After et réessaie pour vous)
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}")

Changez l'URL de base et la clé API, et votre code compatible OpenAI existant fonctionne tel quel.

© 2026 AICU Inc.