REST API (v1)
The full Tonetta API: projects, characters, voices, saved content, and audio generation — enforced by scoped API keys
Basics
Base URL: https://<host>/api/v1
Authenticate every request with a scoped API key via Authorization: Bearer <key> (or X-API-Key). Requests are rate-limited to 60/minute per key. Responses use a Cloudflare-style envelope:
// Success
{ "success": true, "result": { ... } }
// Failure (401 / 402 / 403 / 404 / 409 / 429 / 500)
{ "success": false, "errors": [{ "code": "missing_scope", "message": "..." }] }Scopes
Each endpoint below lists its required scope. write implies read. Content endpoints resolve the scope per item: lines need lines:*, sound effects need sfx:*, 3D models need models:*.
Verify
/api/v1/verifyNo scope required. Returns the key's name, organization, and scopes. Use it in health checks.
Organization
The key's organization and its organization-wide art direction for 3D models — the layer every project inherits (see the 3D Models guide). Scopes organization:read / organization:write.
/api/v1/organizationorganization:readReturns id, name, artDirection (the house style, or empty) and limits — { maxActiveJobs, activeJobs }, how many model jobs this organization may run at once and how many are running now.
/api/v1/organizationorganization:write| Field | Type | Description |
|---|---|---|
artDirection* | string | The house style for 3D models, in plain prose — shapes, era, materials, colours, sizes, what to avoid (≤ 8000 chars). Empty string clears it |
Text naming a trademarked franchise is refused with 422 prompt_rejected; bad values answer 400 invalid_field.
Projects
A project is the workspace that voices, characters, and saved content belong to. A fresh organization has none — create one before anything else.
/api/v1/projectsprojects:readLists the organization's projects: id, name, description, defaultVoiceSampleText, artDirection (the 3D-model house style, when set), timestamps.
/api/v1/projects/{'{id}'}projects:read/api/v1/projectsprojects:write| Field | Type | Description |
|---|---|---|
name* | string | Display name |
description | string | Free-text description |
defaultVoiceSampleText | string | Text the audition endpoints speak by default |
artDirection | string | Art direction for 3D models, in plain prose (≤ 8000 chars) — see the 3D Models guide |
/api/v1/projects/{'{id}'}projects:writePartial update of name, description, defaultVoiceSampleText, artDirection (send an empty string to clear it). Only provided fields change. Text that names a trademarked franchise is refused with 422 prompt_rejected.
/api/v1/projects/{'{id}'}projects:writeReturns 409 in_use while characters, voices, or saved content still belong to the project.
Live Endpoints
A live endpoint is a stored text + character + settings combination that the Public TTS API serves as cached audio at a stable URL. Every item returned here carries publicTtsPath.
/api/v1/live-endpoints?projectId={'{id}'}&characterId={'{id}'}live_endpoints:read/api/v1/live-endpoints/{'{id}'}live_endpoints:read/api/v1/live-endpointslive_endpoints:write| Field | Type | Description |
|---|---|---|
name* | string | Display name |
text* | string | The line the endpoint speaks |
characterId* | string | Character whose voice and persona are used |
projectId | string | Defaults to the character’s project |
ttlMinutes | number | How long generated audio stays cached (default 60) |
useSSML | boolean | Add delivery markup when generating (default true) |
changeTone | boolean | Rewrite the text in the character’s voice when generating (default true) |
ttsSettings | object | Voice overrides — same fields as the /tts settings object |
/api/v1/live-endpoints/{'{id}'}live_endpoints:writePartial update of the same fields; ttsSettings merges into the stored settings. Cached audio is keyed by endpoint id and expires by TTL, so a changed text is served once the current cache entry expires.
/api/v1/live-endpoints/{'{id}'}live_endpoints:writeCharacters
A character binds a voice to a persona (characterDefinition holds tone instructions used for text enhancement). external tells Tonetta which kind of voice voiceId references.
/api/v1/characters?projectId={'{id}'}characters:read/api/v1/characters/{'{id}'}characters:read/api/v1/characterscharacters:write| Field | Type | Description |
|---|---|---|
name* | string | Display name |
projectId* | string | Project the character belongs to |
voiceId* | string | Internal (Voice Studio) voice id — or a project voice id with external: true |
external | boolean | true when voiceId is a project (external provider) voice |
characterDefinition | string | Persona/tone instructions used to enhance text before speaking |
The referenced voice is validated against the right store before the character is created.
/api/v1/characters/{'{id}'}characters:writeReturns 409 if live endpoints still use the character.
Project Voices
/api/v1/voices?projectId={'{id}'}voices:read/api/v1/voices/{'{id}'}voices:read/api/v1/voicesvoices:write| Field | Type | Description |
|---|---|---|
name* | string | Your name for the voice |
projectId* | string | Project to add the voice to |
voiceProviderId* | string | Provider connection id (see External Voices below) |
externalVoiceId* | string | The provider-native voice id |
/api/v1/voices/{'{id}'}voices:writeReturns 409 if characters still use the voice.
Internal Voices (Voice Studio)
/api/v1/internal-voicesinternal_voices:read/api/v1/internal-voices/designinternal_voices:write| Field | Type | Description |
|---|---|---|
prompt* | string | Description of the desired voice |
Returns requestId and preview files with temporary URLs (~1 hour). Spends the voice-creation credit cost; refunded on failure.
/api/v1/internal-voicesinternal_voices:write| Field | Type | Description |
|---|---|---|
name* | string | Voice name |
requestId* | string | From the design call |
audioFileUrl* | string | The chosen preview URL from the design call |
prompt* | string | The prompt the voice was designed from |
description | string | Optional description |
projectId | string | Optional project association |
/api/v1/internal-voices/{'{id}'}internal_voices:writeReturns 409 if characters still use the voice.
No cloning via API
Voice cloning from arbitrary audio is dashboard-only by design.
External Voice Providers
/api/v1/voice-providersexternal_voices:readThe organization's provider connections (credentials are never returned).
/api/v1/external-voices?voiceProviderId={'{id}'}external_voices:readVoices available from that provider account, with voice_id values usable in POST /voices.
Saved Content
Lines, sound effects, and 3D models with full version history. Top-level fields always mirror the active version. Audio items carry audioUrl; models carry files[] (each with role, name, url, sizeBytes), model (the metadata), thumbnailUrl, and glbUrl.
/api/v1/content?contentType=&projectId=&characterId=lines:read | sfx:read | models:read | music:readUnfiltered calls return only the types the key can read. contentType is line, sfx, or model.
/api/v1/content/{'{id}'}read (by item type)/api/v1/contentwrite (by contentType)multipart/form-data: audio (WAV/MP3/OGG/FLAC/M4A/AAC/WMA, ≤50MB), name, contentType, projectId?, characterId?, textToScreen?, tags?, source?, durationMs?. Lines require project, character, and text.
/api/v1/content/{'{id}'}/versionswrite (by item type)multipart/form-data: audio + optional fields. The new version becomes active.
/api/v1/content/{'{id}'}write (by item type)| Field | Type | Description |
|---|---|---|
activeVersion | integer | Version number to make active (send this alone) |
name | string | New display name (1-100 characters; a 3D model name is its asset name: letters, digits, underscores, hyphens, starts with a letter, ≤ 48) |
tags | string[] | Replaces the tag list (≤ 20) |
Either pin a version (activeVersion) or edit details (name and/or tags); the project an item belongs to cannot be changed.
/api/v1/content/{'{id}'}write (by item type)Removes the record and every version's files. Cannot be undone. A model with a running job answers 409 job_in_progress.
/api/v1/content/{'{id}'}/refinemodels:write3D models only: { instructions, referenceImages?, budgetUsd? } starts a job that produces version n+1 from the active version's build script and .blend — see 3D Models. Returns 202 with the job, like /models/generate below.
Generation
Generation spends credits, charged up front and refunded automatically on failure. Results are uploaded to storage and returned as URLs.
/api/v1/ttstts:generate| Field | Type | Description |
|---|---|---|
characterId* | string | The character to speak as (internal or external voice) |
text* | string | What to say |
settings | object | Optional overrides: stability, similarityBoost, useSpeakerBoost, speed, model, pitch, volumeGain, exaggeration |
save | object | Optional { name, textToScreen? } — also save the result as a line, or { contentId, textToScreen? } — save it as the next active version of that line (same character). Requires lines:write |
/api/v1/sfx/generatesfx:write| Field | Type | Description |
|---|---|---|
text* | string | What the effect should sound like |
durationSeconds* | number | Length in seconds (provider max applies) |
promptInfluence | number 0-1 | Prompt adherence vs variety (default 0.3) |
loop | boolean | Seamless loop |
save | object | Optional { name, projectId?, tags? } — also save to Saved Content, or { contentId } — save it as the next active version of that item |
/api/v1/music/catalogmusic:read| Field | Type | Description |
|---|---|---|
— | string | No parameters. Returns the music models cheapest first: model, name, maxDurationSeconds, credits. |
/api/v1/music/generatemusic:write| Field | Type | Description |
|---|---|---|
prompt* | string | Genre, instruments, mood and tempo |
durationSeconds* | number | Length in seconds (model max applies). Charged per generation, so length does not affect the price. |
model | string | A model id from /music/catalog (omit for the cheapest) |
negativePrompt | string | Qualities to avoid, e.g. "vocals" |
seed | number | Reproduce a previous track |
expandPrompt | boolean | Expand a terse brief before generating |
save | object | Optional { name, projectId?, tags? } — also save to Saved Content, or { contentId } — save it as the next active version of that item |
Generate a line and save it in one call:
curl -s https://<host>/api/v1/tts \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"characterId": "<characterId>",
"text": "Welcome back, adventurer!",
"save": { "name": "Welcome Back" }
}'Response:
{
"success": true,
"result": {
"audioUrl": "https://<r2-public>/lines/<org>/<project>/<uuid>.mp3",
"audioKey": "lines/<org>/<project>/<uuid>.mp3",
"contentType": "audio/mpeg",
"characterId": "…",
"characterName": "Narrator",
"internalVoice": false,
"creditsCharged": 5,
"tokensCharged": 5,
"savedContent": { "id": "…", "name": "Welcome Back", ... }
}
}Live-endpoint TTS
The cached, live-endpoint-driven TTS API remains at GET /api/public/tts?liveEndpointId=… (scope tts:generate) — see Public TTS API.
3D models (asynchronous jobs)
Model generation takes minutes, so it is the one operation that runs as a job: submit, then poll (or long-poll) until the status is SUCCEEDED, FAILED, or CANCELLED. Credits are reserved at submit and settled to the actual cost on completion. Presets, tiers, and the model catalog are described in 3D Models.
/api/v1/models/catalogmodels:readModels (key, vision, estimatedTokens per tier — what a job typically settles to — and reserveTokens — what is held at submit), tiers with their caps and base reserve, presets with bones/sockets/clips, styles (flat / stylized / realistic with what each builds), targets, texture maps. The build agent (Anvil) chooses its own vendors; no request field selects one. LLM prices are in Tonetta credits only.
/api/v1/models/generatemodels:write| Field | Type | Description |
|---|---|---|
prompt* | string | The whole specification (≤ 2000 characters) — the build agent reads it and decides the asset name, the rig, the style, the weathering, the clips and the maps itself. Named franchises are refused with 422 prompt_rejected |
projectId | string | Optional project — its art direction then applies |
model | string | Catalog key from /models/catalog (omit for the default) |
preset | string | Rig preset to build to, a name from /models/catalog presets (pass ?projectId= to include the project’s own). Omit it and the agent chooses; name it when game code or animations already depend on that contract |
seedFirst | boolean | Import a generated mesh before the agent starts building (default true for a new model). false builds from primitives — cheaper, flatter on sculpted shapes |
referenceImages | string[] | Up to 4 URLs from POST /v1/models/references — images the agent looks at before it plans |
budgetUsd | number | Spend ceiling in USD of provider cost, capped at 50; a premium model needs more room than the default |
202 → { jobId, contentId, status: "queued", creditsReserved, model, tier, job }. Supports an Idempotency-Key header (a replay returns the original job with 200). Errors: 402 insufficient_tokens, 409 too_many_active_jobs, 422 prompt_rejected.
/api/v1/models/sessionsmodels:writeStart a conversation with the build agent instead of a fire-and-forget job: { message, contentId?, projectId?, preset?, referenceImages? }. The message is the brief and a new model is created, or contentId continues an existing model from its active version. Each message is one step: the agent builds, renders or researches, shows the result and asks where to go next; it proposes paid vendor calls with their price and waits for a yes unless the message asked for them. Answers 202 with{ session, job } — poll the job, then read the session. Each turn is billed to what it spent.
/api/v1/models/sessions/{'{contentId}'}models:readThe conversation: every message, what each turn did (tools, results), its renders, the version each export saved, cost, and runningJob while a turn runs. null when none exists.
/api/v1/models/sessions/{'{contentId}'}/messagesmodels:write{ message, approvedTools? } — the next step. 409 turn_in_progress while the last turn is still running. approvedTools lets a paid tool the agent proposed run this turn.
/api/v1/models/sessions/{'{contentId}'}/exportmodels:write{ message? } — an export turn: the agent saves what exists as a new version of the model.
/api/v1/models/jobs/{'{jobId}'}?wait={'{seconds}'}models:readThe job: status, stage, progress (iteration, the turns taken, against budgetUsd), previews[] with render URLs, creditsReserved / creditsCharged, and on success contentId + versionNumber. With wait (≤ 240 s) the call returns as soon as the job reaches a terminal status; the response adds done.
/api/v1/models/jobs?status=&projectId=&contentId=models:readstatus accepts a comma-separated list, e.g. QUEUED,RUNNING,FINISHING.
/api/v1/models/jobs/{'{jobId}'}/cancelmodels:writeA queued job is cancelled and refunded at once; a running job stops after its current step and is charged for the cost so far.
Submit and long-poll:
curl -s https://<host>/api/v1/models/generate \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{ "prompt": "twin-engine attack submarine, angular riveted hull, forward laser cannons, missile pods under the stub wings, salt-worn navy paint over bare steel" }'
curl -s "https://<host>/api/v1/models/jobs/<jobId>?wait=240" -H "Authorization: Bearer $API_KEY"Credit Costs
Amounts are in credits (1 credit = $0.01 of pack price). Responses name them with credits… fields (creditsReserved, creditsCharged, estimatedCredits, reserveCredits, creditCost); the older tokens… spellings are still sent as deprecated aliases with the same values, and the insufficient_tokens error code keeps its name.
| Operation | Cost (credits) |
|---|---|
| TTS — external voice | 5 |
| TTS — internal voice | 10 |
| SFX generation | per second (provider pricing) |
| Voice design / save (each) | 25 |
| 3D model generation — quick / detailed | 25 / 90 reserved, settled to actual LLM + compute cost |
| 3D model refine — quick / detailed | 20 / 70 reserved, settled to actual cost |
Model jobs are the only reservation-based charge: the flat amount is taken at submit, reconciled on completion (the difference is returned), refunded entirely on failure, and charged only for work done on cancel.