POST /v1/audio/speech
Generate audio from text (unit-billed, chars)
Task #15 specialty route (route_type: speech); per-alias input caps added task #17. The response is BINARY audio via chunked transfer: bytes leave the gateway as they arrive from the card (flush per read — no idle gap past load-balancer walls), never a Content-Length. Content-Type forwards from the upstream (default audio/mpeg); no other upstream header crosses. No stream param — chunked transfer is the only mode. Input is length-capped per alias where the card does not cap itself (400 invalid_param param=input, refused before any upstream connection, unbilled; values in the gateway's models.yaml, sized so buffered synthesis fits the card host's ~55-60 s no-byte edge await and the gateway's per-alias first-byte wall: chatterbox-tts 400 (measured 2026-09-09: 0.085 s/char buffered synthesis — 600 chars reached 53.6 s to first byte; the stub-era 20000 is gone) · kokoro-tts 3500 (F-3 measured 0.0149 s/char — ~52.8 s at cap; 4000 would run ~60 s, an edge kill) · pocket-tts 2500 (F-3 measured 0.0188 s/char — ~48 s at cap) · audio8-tts 250 runes (review F-2 2026-09-09: ~0.22 s/char measured × 250 = ~56 s under the edge kill — the old 400 derived from a ~100 s wall that does not exist)). The card synthesizes BEFORE the first byte crosses — first-byte latency of tens of seconds is normal at the caps (chatterbox ~41 s at 400 chars); text longer than a cap is the client's to chunk (one request per chunk, concatenated at playback). Metering: unit_type='chars' = rune count of input, counted at the gateway. Idempotency-Key is accepted with NO replay semantics on this route (§8 route-scope) — a retrying client re-executes and re-bills. Client contract: do not set client read timeouts below the expected generation time.
Request
Request body fields for createSpeech | Field | Type | Required | Description |
| model | string | yes | An IronStratum alias whose route_type is speech. |
| input | string | yes | The text to synthesize (billed per rune). |
| voice | string | yes | An upstream catalog voice id (GET /v1/audio/voices). |
| response_format | string | no | — |
| speed | number | no | — |
Responses
400 400
400 response fields for createSpeech | Field | Type | Required | Description |
| error | object | yes | — |
401 401
401 response fields for createSpeech | Field | Type | Required | Description |
| error | object | yes | — |
402 402
402 response fields for createSpeech | Field | Type | Required | Description |
| error | object | yes | — |
403 403
403 response fields for createSpeech | Field | Type | Required | Description |
| error | object | yes | — |
404 404
404 response fields for createSpeech | Field | Type | Required | Description |
| error | object | yes | — |
500 500
500 response fields for createSpeech | Field | Type | Required | Description |
| error | object | yes | — |
502 502
502 response fields for createSpeech | Field | Type | Required | Description |
| error | object | yes | — |
503 503
503 response fields for createSpeech | Field | Type | Required | Description |
| error | object | yes | — |
Status codes
- 200 — Chunked binary audio. Content-Type is the upstream's declared audio type (default audio/mpeg); a mid-generation upstream death ends the transfer abruptly — delivered bytes stand, the turn is not billed.
Example
curl -X POST https://api.ironstratum.com/v1/audio/speech \
-H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"<model>","input":"<input>","voice":"<voice>"}'
All operations ·
interactive reference ·
openapi.yaml