API reference
Audio — Speech-to-Text & Text-to-Speech
POST /v1/audio/transcriptions e /v1/audio/speech — OpenAI-compatible. Whisper large-v3 self-hosted in Svizzera + Piper TTS multilingua.
Last updated: 2026-05-26
Audio API — Speech-to-Text & Text-to-Speech
Due endpoint, due use case:
| Endpoint | Funzione | Modello | Backend |
|---|---|---|---|
POST /v1/audio/transcriptions |
Audio → testo (STT) | Whisper large-v3 / large-v3-turbo | GPU on-prem, Svizzera |
POST /v1/audio/speech |
Testo → audio (TTS) | Piper multilingua | CPU on-prem, Svizzera |
Sovranità: tutti gli audio e i testi restano sull'infrastruttura siati.ai. Nessun dato sale a cloud terzi (OpenAI, Google, Microsoft).
Speech-to-Text
POST https://api.siati.ai/v1/audio/transcriptions
Trascrive un file audio in testo. Auto-detect lingua di default, o forzala con language. Supporta italiano, inglese, tedesco, francese e altre 90+ lingue.
Request — cURL
curl https://api.siati.ai/v1/audio/transcriptions \
-H "Authorization: Bearer $SIATI_API_KEY" \
-F file=@messaggio.m4a \
-F model=whisper-1 \
-F language=it
Request — Python (OpenAI SDK)
from openai import OpenAI
client = OpenAI(
base_url="https://api.siati.ai/v1",
api_key="sk-siati-...",
)
with open("messaggio.m4a", "rb") as f:
result = client.audio.transcriptions.create(
model="whisper-1",
file=f,
language="it", # opzionale, auto-detect altrimenti
response_format="json", # json | verbose_json | text
prompt="vocabolario tecnico", # hint per migliorare trascrizione
)
print(result.text)
Request — JavaScript / Node (fetch)
const form = new FormData();
form.append('file', audioBlob, 'messaggio.m4a');
form.append('model', 'whisper-1');
form.append('language', 'it');
const res = await fetch('https://api.siati.ai/v1/audio/transcriptions', {
method: 'POST',
headers: { 'Authorization': `Bearer ${SIATI_API_KEY}` },
body: form,
});
const { text } = await res.json();
console.log(text);
Request — Swift (iOS, URLSession)
let url = URL(string: "https://api.siati.ai/v1/audio/transcriptions")!
let boundary = UUID().uuidString
var req = URLRequest(url: url)
req.httpMethod = "POST"
req.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
req.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
// audio file part
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"audio.m4a\"\r\n".data(using: .utf8)!)
body.append("Content-Type: audio/m4a\r\n\r\n".data(using: .utf8)!)
body.append(audioData)
body.append("\r\n".data(using: .utf8)!)
// model + language fields
for (k, v) in [("model", "whisper-1"), ("language", "it")] {
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"\(k)\"\r\n\r\n\(v)\r\n".data(using: .utf8)!)
}
body.append("--\(boundary)--\r\n".data(using: .utf8)!)
req.httpBody = body
let (data, _) = try await URLSession.shared.data(for: req)
let result = try JSONDecoder().decode(TranscriptionResponse.self, from: data)
print(result.text)
Parameters
| Param | Type | Required | Description |
|---|---|---|---|
file |
binary | ✓ | Audio file. Formati: wav, mp3, m4a, mp4, ogg, webm, flac. Max 25 MB. |
model |
string | – | whisper-1 (default, ~10× faster) o whisper-1-hd (max accuracy). |
language |
string | – | ISO-639-1 (es. it, en, de, fr). Auto-detect se omesso. |
prompt |
string | – | Hint contestuale (max 1024 char). Migliora la trascrizione su vocabolario tecnico o nomi propri. |
response_format |
string | – | json (default), verbose_json (con segments + timestamps), o text (solo plain text). |
temperature |
float | – | 0..1, default 0. Aumentare se la trascrizione "sbatte" su parole rare. |
Response — response_format=json (default)
{
"text": "Ciao, come stai oggi? Volevo chiederti se possiamo vederci domani."
}
Response — response_format=verbose_json
{
"task": "transcribe",
"language": "it",
"duration": 4.32,
"text": "Ciao, come stai oggi? Volevo chiederti se possiamo vederci domani.",
"segments": [
{
"id": 0,
"start": 0.0,
"end": 2.1,
"text": "Ciao, come stai oggi?",
"avg_logprob": -0.21,
"no_speech_prob": 0.02
},
{
"id": 1,
"start": 2.1,
"end": 4.32,
"text": "Volevo chiederti se possiamo vederci domani."
}
]
}
Quale modello scegliere?
| Use case | Modello consigliato |
|---|---|
| Voice messages WhatsApp-style (5–60s) | whisper-1 (turbo) — più veloce, qualità eccellente su clip brevi |
| Dictation lunga (1–10 min) | whisper-1 per default; passa a whisper-1-hd se utente lamenta errori |
| Audio rumoroso, accenti regionali pesanti, audio telefonico | whisper-1-hd (large-v3 puro) |
| Trascrizione meeting/podcast | whisper-1-hd + response_format=verbose_json |
Text-to-Speech
POST https://api.siati.ai/v1/audio/speech
Sintetizza voce a partire da testo. Sei voci installate in quattro lingue.
Request — cURL
curl https://api.siati.ai/v1/audio/speech \
-H "Authorization: Bearer $SIATI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "tts-1",
"voice": "paola",
"input": "Ciao! Sono Paola, la voce italiana di SIATI.",
"response_format": "wav"
}' \
--output benvenuto.wav
Request — Python (OpenAI SDK)
res = client.audio.speech.create(
model="tts-1", # accettato per compat OpenAI
voice="paola", # paola, riccardo, amy, ryan, thorsten, siwis
input="Ciao! Sono Paola.",
response_format="wav", # wav | mp3
# speed=1.0, # accettato ma ignorato dal MVP
)
res.stream_to_file("benvenuto.wav")
Request — JavaScript / Node
const res = await fetch('https://api.siati.ai/v1/audio/speech', {
method: 'POST',
headers: {
'Authorization': `Bearer ${SIATI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'tts-1',
voice: 'paola',
input: 'Ciao!',
response_format: 'mp3',
}),
});
const audioBlob = await res.blob();
// In browser:
const url = URL.createObjectURL(audioBlob);
new Audio(url).play();
Parameters
| Param | Type | Required | Description |
|---|---|---|---|
input |
string | ✓ | Testo da sintetizzare. Max 5000 caratteri per request. |
voice |
string | ✓ | Nome voce (vedi tabella sotto). Case-sensitive. |
model |
string | – | Accettato per compat (tts-1, tts-1-hd). Piper è single-model. |
response_format |
string | – | wav (default) o mp3. WAV = qualità lossless, MP3 = 80% più piccolo. |
speed |
float | – | 0.5..2.0. Accettato per compat OpenAI ma ignorato nel MVP (Piper non supporta speed control nativamente). |
language |
string | – | Estensione siati (non OpenAI). ISO-639-1 per scegliere voce default se voice omesso. |
Voci disponibili
| Voce | Lingua | Genere | Caratteristiche |
|---|---|---|---|
paola |
🇮🇹 Italiano | Femminile | Default IT. Prosodia naturale, ottima per assistant vocale. |
riccardo |
🇮🇹 Italiano | Maschile | Voce leggera/mobile-friendly. Bitrate ridotto. |
amy |
🇺🇸 Inglese | Femminile | Default EN. Prosodia US standard, news-anchor style. |
ryan |
🇺🇸 Inglese | Maschile | Voce maschile US, leggermente più calda. |
thorsten |
🇩🇪 Tedesco | Maschile | Default DE. Voce solida, leggermente formale. |
siwis |
🇫🇷 Francese | Femminile | Default FR. Prosodia parigina chiara. |
Response
Il body è il file audio binario. Headers di rilievo:
Content-Type: audio/wav (o audio/mpeg per mp3)
Content-Length: 152043
X-Request-Id: req_abc123...
Mobile API (my.siati.ai/api/v1/audio/*)
Per l'app mobile (iOS/Android/web chat) gli endpoint sono autenticati con JWT invece che API key, e usano un naming siati-native (snake_case esplicito) anziché lo schema OpenAI:
| Endpoint mobile | Equivalente dev |
|---|---|
POST /api/v1/audio/transcribe (form field audio) |
POST /v1/audio/transcriptions (form field file) |
POST /api/v1/audio/synthesize (JSON text) |
POST /v1/audio/speech (JSON input) |
GET /api/v1/audio/voices |
– (nuova, ritorna catalogo voci) |
Esempio mobile transcribe
curl https://my.siati.ai/api/v1/audio/transcribe \
-H "Authorization: Bearer $JWT_TOKEN" \
-F audio=@nota.m4a \
-F language=it \
-F with_segments=true
Response:
{
"text": "Promemoria: chiamare Marco alle 15.",
"language": "it",
"duration_seconds": 3.4,
"model": "whisper-1",
"segments": [...]
}
Esempio mobile synthesize
curl https://my.siati.ai/api/v1/audio/synthesize \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text":"Ciao!","voice":"paola","format":"mp3"}' \
--output out.mp3
Esempio mobile lista voci
curl https://my.siati.ai/api/v1/audio/voices \
-H "Authorization: Bearer $JWT_TOKEN"
{
"voices": [
{ "name": "paola", "language": "it", "gender": "female" },
{ "name": "riccardo", "language": "it", "gender": "male" },
...
],
"defaults_by_language": {
"it": "paola",
"en": "amy",
"de": "thorsten",
"fr": "siwis",
"rm": "paola"
}
}
Errors
Schema standard. Vedi Errors per il dettaglio.
| Status | Code | Quando |
|---|---|---|
400 |
invalid_request_error |
File mancante, modello sconosciuto, JSON malformato. |
401 |
invalid_api_key |
API key non valida o revocata. Vedi Authentication. |
413 |
request_too_large |
Audio sopra 25 MB o input TTS sopra 5000 char. |
415 |
unsupported_media_type |
Formato audio non supportato. Convertilo in wav/mp3/m4a. |
429 |
rate_limit_exceeded |
Vedi Rate limits. Include Retry-After. |
502 |
bad_gateway |
Backend STT/TTS momentaneamente irraggiungibile. Retry esponenziale. |
Rate limits
| Endpoint | Limite | Periodo |
|---|---|---|
POST /v1/audio/transcriptions |
30 req | 1 min |
POST /v1/audio/speech |
120 req | 1 min |
POST /api/v1/audio/transcribe (mobile) |
30 req | 1 min |
POST /api/v1/audio/synthesize (mobile) |
60 req | 1 min |
I limiti sono pensati per uso interattivo umano + batch jobs ragionevoli. Per quote più alte contatta info@siati.ai.
Pricing
| Operazione | Costo |
|---|---|
| STT (qualsiasi modello) | 0.006 CHF / minuto di audio |
| TTS | 5 CHF / 1M caratteri (≈ gratis nel pratico) |
Esempi:
- 30s di voice message trascritto: 0.003 CHF
- 1h di meeting trascritto: 0.36 CHF
- 1000 frasi TTS da 100 char ciascuna: 0.50 CHF
Lo storico consumo è visibile nella dashboard.
Tips & pattern comuni
Voice mode chat (utente parla → AI risponde a voce)
# 1. Trascrivi voice message utente
with open("user-message.m4a", "rb") as f:
transcript = client.audio.transcriptions.create(model="whisper-1", file=f, language="it")
# 2. Chiedi al LLM
resp = client.chat.completions.create(
model="apertus-70b-instruct",
messages=[{"role": "user", "content": transcript.text}],
)
ai_text = resp.choices[0].message.content
# 3. Sintetizza la risposta
audio = client.audio.speech.create(model="tts-1", voice="paola", input=ai_text)
audio.stream_to_file("ai-reply.mp3")
Dictation con vocabolario di dominio
Quando trascrivi audio con termini tecnici o nomi propri ricorrenti, passa prompt:
client.audio.transcriptions.create(
model="whisper-1-hd",
file=open("riunione.m4a", "rb"),
language="it",
prompt="SIATI, Apertus, Mistral, Qdrant, GPU, embedding, RAG, tier",
response_format="verbose_json",
)
Whisper userà il prompt per "biasare" verso quella terminologia → meno errori su nomi propri.