Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: ditto.audio Models API
description: Cloud API behind ditto.audio. The reader (reader.ditto.audio) and the voice studio (studio.ditto.audio) are separate apps sharing this one backend — see /docs/reader and /docs/inference for each app’s public surface. Third-party integrations want /docs/public (rendered for humans at https://ditto.audio/docs).
version: '1.0'
contact: {}
servers: []
tags:
- name: Models
description: Voice-model catalog — browse, download & upload custom voices
paths:
/v1/models:
get:
operationId: VoiceModelsController_list
parameters:
- name: q
required: false
in: query
description: free-text search over name/description/tags
schema:
type: string
- name: engine
required: false
in: query
schema:
type: string
enum:
- piper
- xtts
- kokoro
- pocket
- rvc
- system
- morpho
- name: language
required: false
in: query
schema:
example: en
type: string
- name: gender
required: false
in: query
description: 'Only voices that sound like this: female, male, neutral (comma-separated for any of several).'
schema:
example: female
type: string
- name: accent
required: false
in: query
description: Only voices with one of these accents (comma-separated keys of GET /v1/models/taxonomy). An accent includes every accent under it (Latin American includes Mexican), stated or meant by the voice's language tag (`es-MX`).
schema:
example: es-latin-american
type: string
- name: age
required: false
in: query
description: 'Only voices that sound this old: young, middle-aged, older (comma-separated).'
schema:
example: young
type: string
- name: style
required: false
in: query
description: 'Only voices with ALL of these styles (comma-separated taxonomy keys): each style narrows.'
schema:
example: warm,deep
type: string
- name: use
required: false
in: query
description: Only voices suited to one of these uses (comma-separated taxonomy keys).
schema:
example: narration
type: string
- name: tag
required: false
in: query
description: single keyword filter
schema:
type: string
- name: featuredOnly
required: false
in: query
schema:
default: false
type: boolean
- name: sortBy
required: false
in: query
schema:
default: popular
type: string
enum:
- newest
- popular
- name
- name: page
required: false
in: query
schema:
default: 1
type: number
- name: limit
required: false
in: query
schema:
maximum: 100
default: 24
type: number
- name: locale
required: false
in: query
description: 'The language of the site page asking (one of the site''s languages: en, de, es, fr, ...; anything else is English). A catalogue voice''s description comes back translated when a current translation exists, else in English; each item says which in `descriptionLanguage`. Names are never translated.'
schema:
example: de
type: string
responses:
'200':
description: ''
summary: Browse the published voice-model catalog
tags:
- Models
/v1/models/catalog:
get:
description: 'Every voice GET /v1/models lists (its default order, all pages at once), in the language asked for, and `version`: a hash of what it lists, download counts aside. A client that keeps a copy sends the version it holds as `have` and gets `{ version, unchanged: true }` while that copy is current, else the catalogue. Never cached by a shared cache: every answer is current.'
operationId: VoiceModelsController_catalog
parameters:
- name: locale
required: false
in: query
description: 'A site language code (en, de, ja, ...): descriptions come back in it when translated.'
schema:
example: de
type: string
- name: have
required: false
in: query
description: The `version` of the copy the client holds.
schema:
example: 3f6c0a9e2b71d48c5e10
type: string
responses:
'200':
description: ''
summary: The whole published catalogue in one answer, with the version to keep a copy by
tags:
- Models
/v1/models/languages:
get:
description: Counts what GET /v1/models lists with the same filters (`q`, `engine`, `gender`, `age`, `accent`, `style`, `use`, `tag`, `featuredOnly`), grouped by primary language (en-US and en-GB are both English), largest first; `language` itself is never applied to its own counts. Each `code` is the `language` to pass to GET /v1/models to list those voices.
operationId: VoiceModelsController_languages
parameters:
- name: q
required: false
in: query
description: free-text search over name/description/tags
schema:
type: string
- name: engine
required: false
in: query
schema:
type: string
enum:
- piper
- xtts
- kokoro
- pocket
- rvc
- system
- morpho
- name: language
required: false
in: query
schema:
example: en
type: string
- name: gender
required: false
in: query
description: 'Only voices that sound like this: female, male, neutral (comma-separated for any of several).'
schema:
example: female
type: string
- name: accent
required: false
in: query
description: Only voices with one of these accents (comma-separated keys of GET /v1/models/taxonomy). An accent includes every accent under it (Latin American includes Mexican), stated or meant by the voice's language tag (`es-MX`).
schema:
example: es-latin-american
type: string
- name: age
required: false
in: query
description: 'Only voices that sound this old: young, middle-aged, older (comma-separated).'
schema:
example: young
type: string
- name: style
required: false
in: query
description: 'Only voices with ALL of these styles (comma-separated taxonomy keys): each style narrows.'
schema:
example: warm,deep
type: string
- name: use
required: false
in: query
description: Only voices suited to one of these uses (comma-separated taxonomy keys).
schema:
example: narration
type: string
- name: tag
required: false
in: query
description: single keyword filter
schema:
type: string
- name: featuredOnly
required: false
in: query
schema:
default: false
type: boolean
- name: sortBy
required: false
in: query
schema:
default: popular
type: string
enum:
- newest
- popular
- name
- name: page
required: false
in: query
schema:
default: 1
type: number
- name: limit
required: false
in: query
schema:
maximum: 100
default: 24
type: number
- name: locale
required: false
in: query
description: 'The language of the site page asking (one of the site''s languages: en, de, es, fr, ...; anything else is English). A catalogue voice''s description comes back translated when a current translation exists, else in English; each item says which in `descriptionLanguage`. Names are never translated.'
schema:
example: de
type: string
responses:
'200':
description: ''
summary: The languages the catalogue has voices recorded in, with how many
tags:
- Models
/v1/models/taxonomy:
get:
description: The controlled vocabulary of every voice's `accent`, `gender`, `age`, `styles` and `useCases`, with display labels. A filter on an accent includes every accent under it. Keys are stable; labels may be reworded.
operationId: VoiceModelsController_taxonomy
parameters: []
responses:
'200':
description: ''
summary: 'The voice taxonomy: languages, accents (a hierarchy per language), genders…'
tags:
- Models
/v1/models/mine:
get:
operationId: VoiceModelsController_mine
parameters: []
responses:
'200':
description: ''
security:
- bearer: []
summary: Your models — per-user refs joined to catalog metadata (for the reader's Models…
tags:
- Models
/v1/models/premium/usage:
get:
operationId: VoiceModelsController_premiumUsage
parameters: []
responses:
'200':
description: ''
security:
- bearer: []
summary: 'Your premium-voice quota: used / limit / claimed voices (drives upgrade prompts)'
tags:
- Models
/v1/models/support/rvc:
get:
description: 'Stable route in front of the catalog model that carries the bytes (RVC_SUPPORT_MODEL_SLUG). Free: no login needed.'
operationId: VoiceModelsController_rvcSupport
parameters: []
responses:
'200':
description: ''
security:
- bearer: []
summary: The RVC engine's support files (ContentVec, FCPE), presigned — what the desktop…
tags:
- Models
/v1/models/support/pocket:
get:
description: 'Stable route in front of the catalog model that carries the bytes (POCKET_SUPPORT_MODEL_SLUG). Free: no login needed.'
operationId: VoiceModelsController_pocketSupport
parameters: []
responses:
'200':
description: ''
security:
- bearer: []
summary: The Pocket engine's shared ONNX bundle, presigned — what the desktop app…
tags:
- Models
/v1/models/support/pocket-voices:
get:
description: 'What a desktop plugin fetches when its Pocket voices are asked for (TTS Synth bought on its own has no app). Free: no login needed.'
operationId: VoiceModelsController_pocketVoices
parameters: []
responses:
'200':
description: ''
summary: 'Pocket''s stock voices, presigned: one reference clip per voice, named for it'
tags:
- Models
/v1/models/support/morpho-tones:
get:
description: 'What Ditto Morpho (and the app''s Morph page) fetches when its window opens: a plugin bought on its own has no app. Free: no login needed. 503 while no tone model is published.'
operationId: VoiceModelsController_morphoTones
parameters: []
responses:
'200':
description: ''
summary: 'Ditto Morpho''s stock tone models, presigned: one folder per model…'
tags:
- Models
/v1/models/{slug}:
get:
operationId: VoiceModelsController_detail
parameters:
- name: slug
required: true
in: path
schema:
type: string
- name: locale
required: false
in: query
description: 'The site page''s language: a current translation of the description, else English (`descriptionLanguage` says which).'
schema:
type: string
responses:
'404':
description: Not found / not published
summary: Get a published model with its file list (no URLs)
tags:
- Models
/v1/models/{slug}/samples/{sample}:
get:
description: The address every catalog `samples[].url` points at. Supports byte ranges and ETag revalidation; a request carrying the current `?v=` is cacheable for a year.
operationId: VoiceModelsController_sample
parameters:
- name: slug
required: true
in: path
schema:
type: string
- name: sample
required: true
in: path
schema:
type: string
responses:
'404':
description: No such public voice or clip
summary: Stream a pre-rendered preview clip
tags:
- Models
/v1/models/{slug}/download:
get:
operationId: VoiceModelsController_download
parameters:
- name: slug
required: true
in: path
schema:
type: string
responses:
'429':
description: 'More new voices from this network than its allowance: at most 2, 5, 10 new voices a minute, in 10 minutes and a day per client address (2x for a Ditto subscriber, whose session is read from its bearer token). A voice this address already downloaded in the last 24 hours is free, and an engine (a base bundle voices are built on, such as xtts-v2, kokoro-v1-0 or a pocket-<code>-v1 language model; the clone encoders; a tone model) is never counted. The body says `code: DOWNLOAD_LIMIT` and `retryAfterSec`; wait the `Retry-After` header (whole seconds) and ask again.'
headers:
Retry-After:
description: Seconds until this network may download a new voice again.
schema:
type: integer
security:
- bearer: []
summary: Get presigned download URLs for every file.
tags:
- Models
/v1/models/{slug}/files/{fileId}/download:
get:
operationId: VoiceModelsController_downloadFile
parameters:
- name: slug
required: true
in: path
schema:
type: string
- name: fileId
required: true
in: path
schema:
type: string
responses:
'429':
description: 'More new voices from this network than its allowance: at most 2, 5, 10 new voices a minute, in 10 minutes and a day per client address (2x for a Ditto subscriber, whose session is read from its bearer token). A voice this address already downloaded in the last 24 hours is free, and an engine (a base bundle voices are built on, such as xtts-v2, kokoro-v1-0 or a pocket-<code>-v1 language model; the clone encoders; a tone model) is never counted. The body says `code: DOWNLOAD_LIMIT` and `retryAfterSec`; wait the `Retry-After` header (whole seconds) and ask again.'
headers:
Retry-After:
description: Seconds until this network may download a new voice again.
schema:
type: integer
security:
- bearer: []
summary: Get a presigned URL for one file.
tags:
- Models
/v1/models/custom:
post:
operationId: CustomVoicesController_create
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateCustomVoiceDto'
responses:
'201':
description: ''
security:
- bearer: []
summary: Create a custom XTTS voice and reserve latent upload URLs
tags:
- Models
/v1/models/custom/{slug}/confirm:
post:
operationId: CustomVoicesController_confirm
parameters:
- name: slug
required: true
in: path
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ConfirmCustomVoiceDto'
responses:
'201':
description: ''
security:
- bearer: []
summary: Finalize a custom voice after its latents are uploaded
tags:
- Models
/v1/models/custom/by-source/{sourceHash}:
get:
description: Returns the file manifest (names + content hashes) of the conversion the CALLER already made of this exact model, so the client can link the stored files instead of converting and uploading again. Never answers from another account's voice. 200 with {found:false} when unknown.
operationId: CustomVoicesController_bySource
parameters:
- name: sourceHash
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: Look up YOUR earlier pth→onnx conversion of a source model.pth, by its sha-256
tags:
- Models
/v1/models/custom/{slug}/download:
get:
operationId: CustomVoicesController_download
parameters:
- name: slug
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: 'Owner manifest: presigned latents + base voice slug/version'
tags:
- Models
/v1/models/custom/{slug}:
delete:
operationId: CustomVoicesController_remove
parameters:
- name: slug
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: Delete a custom voice (blobs + row + ref)
tags:
- Models
/v1/custom-voices/{id}:
get:
operationId: CustomVoicePagesController_detail
parameters:
- name: id
required: true
in: path
schema:
type: string
responses:
'404':
description: No such voice, or not yours
security:
- bearer: []
summary: 'One of your custom voices: metadata, files and preview clips'
tags:
- Models
patch:
description: 'Every field is optional: one left out stays, a null clears it. Accent, styles and use cases are words of the voice taxonomy (GET /v1/models/taxonomy: keys, labels or aliases), stored as keys; a word that names nothing is a 400 that says which. Every voice list filters your voices by what you say here.'
operationId: CustomVoicePagesController_update
parameters:
- name: id
required: true
in: path
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateCustomVoiceDto'
responses:
'404':
description: No such voice, or not yours
security:
- bearer: []
summary: 'Describe one of your custom voices: name, description, language, accent…'
tags:
- Models
delete:
operationId: CustomVoicePagesController_remove
parameters:
- name: id
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: Delete one of your custom voices (row, account ref, private files)
tags:
- Models
/v1/custom-voices/{id}/samples/{sample}:
get:
operationId: CustomVoicePagesController_sample
parameters:
- name: id
required: true
in: path
schema:
type: string
- name: sample
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: Stream one of your custom voice's preview clips (owner-only)
tags:
- Models
put:
description: 'Custom voices are never rendered server-side: the page synthesizes the canonical snippet in the browser and uploads the WAV here (field "file"), once. From then on it streams from GET :id/samples/:sample on every device. `sample` is intro, passage or cozy; the body must be PCM WAV of at most 3 minutes.'
operationId: CustomVoicePagesController_savePreview
parameters:
- name: id
required: true
in: path
schema:
type: string
- name: sample
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: Save a preview clip you rendered on your own device (owner-only)
tags:
- Models
/v1/custom-voices/{id}/download:
get:
operationId: CustomVoicePagesController_download
parameters:
- name: id
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: 'Owner manifest: presigned files + the base voice it overlays'
tags:
- Models
/v1/models/imported:
get:
operationId: ImportedVoicesController_list
parameters: []
responses:
'200':
description: ''
security:
- bearer: []
summary: List the user's URL-imported voices
tags:
- Models
post:
description: Stores only the source URL, the resolved file list and provenance. Re-posting an existing sourceUrl refreshes that record instead of creating a duplicate.
operationId: ImportedVoicesController_create
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateImportedVoiceDto'
responses:
'201':
description: ''
security:
- bearer: []
summary: Register a voice imported from a URL
tags:
- Models
/v1/models/imported/{id}:
get:
operationId: ImportedVoicesController_get
parameters:
- name: id
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: One imported voice
tags:
- Models
patch:
operationId: ImportedVoicesController_update
parameters:
- name: id
required: true
in: path
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateImportedVoiceDto'
responses:
'200':
description: ''
security:
- bearer: []
summary: Rename, or refresh the cached file list after URLs expire
tags:
- Models
delete:
operationId: ImportedVoicesController_remove
parameters:
- name: id
required: true
in: path
schema:
type: string
responses:
'200':
description: ''
security:
- bearer: []
summary: Remove an imported voice (soft delete)
tags:
- Models
components:
schemas:
ConfirmCustomVoiceDto:
type: object
properties:
files:
description: the uploaded files (s3Key + sha256)
type: array
items:
$ref: '#/components/schemas/ConfirmUploadDto'
required:
- files
ImportedVoiceFileDto:
type: object
properties:
fileName:
type: string
example: model.pth
role:
type: string
example: model
description: model | index | config | sidecar | other
sizeBytes:
type: number
example: 55019812
url:
type: string
description: Direct byte URL (may expire; sourceUrl is the fallback).
md5:
type: string
description: Upstream md5, when the source published one.
insideArchive:
type: object
description: 'Set when the file is a member of a zip: { archiveUrl, memberPath }.'
required:
- fileName
- role
UpdateCustomVoiceDto:
type: object
properties:
name:
type: string
example: Grandma, bedtime
description:
type:
- object
- 'null'
language:
type:
- object
- 'null'
example: es-MX
description: The language the voice was recorded in (a BCP 47 tag).
accent:
type:
- object
- 'null'
example: es-rioplatense
gender:
type:
- string
- 'null'
enum:
- female
- male
- neutral
- null
age:
type:
- string
- 'null'
enum:
- young
- middle-aged
- older
- null
styles:
type: array
items:
type: string
useCases:
type: array
items:
type: string
CreateCustomVoiceDto:
type: object
properties:
name:
type: string
example: My Cloned Narrator
engine:
type: string
enum:
- piper
- xtts
- kokoro
- pocket
- rvc
- system
- morpho
description: xtts (default) or rvc. RVC voices persist model.onnx + model.pth.
sourceHash:
type: string
description: sha-256 of the source model.pth (for an in-browser pth→onnx voice). Stored so a future client can skip re-converting via GET /models/custom/by-source/:hash.
baseSlug:
type: string
example: xtts-v2
description: base XTTS voice to overlay (omit for a full self-contained bundle)
files:
description: the files to upload (full-bundle mode)
type: array
items:
$ref: '#/components/schemas/CustomVoiceFileDto'
language:
type: string
example: en
sampleRate:
type: number
example: 24000
description:
type: string
example: My grandmother reading bedtime stories
gender:
type: string
enum:
- female
- male
- neutral
age:
type: string
enum:
- young
- middle-aged
- older
accent:
type: string
example: en-scottish
styles:
example:
- warm
type: array
items:
type: string
useCases:
example:
- narration
type: array
items:
type: string
required:
- name
UpdateImportedVoiceDto:
type: object
properties:
name:
type: string
example: My better name
files:
description: Refreshed file list after a re-resolve (URLs expire).
type: array
items:
type: string
CreateImportedVoiceDto:
type: object
properties:
sourceUrl:
type: string
example: https://voice-models.com/model/9Pk
name:
type: string
example: Mencho Wey 200 (Mexico)
engine:
type: string
example: rvc
default: rvc
files:
type: array
items:
$ref: '#/components/schemas/ImportedVoiceFileDto'
sourceMeta:
type: object
description: 'Provenance: page title/author/license/pageUrl.'
via:
description: Locator chain that resolved the URL.
type: array
items:
type: string
sampleRate:
type: number
example: 40000
encoderBaseSlug:
type: string
description: Catalog slug whose HuBERT/RMVPE encoders this import overlays.
hasIndex:
type: boolean
description: Whether a FAISS/npy retrieval index came with the model.
required:
- sourceUrl
- name
CustomVoiceFileDto:
type: object
properties:
fileName:
type: string
example: xtts_gpt.onnx
role:
type: string
enum:
- model
- config
- vocab
- speakers
- index
- sample
- archive
- other
contentHash:
type: string
description: sha-256 of the file — drives dedup (skip re-upload if present)
sizeBytes:
type: number
required:
- fileName
- contentHash
ConfirmUploadDto:
type: object
properties:
s3Key:
type: string
description: s3Key returned by the presign call
sha256:
type: string
description: hex sha-256 for integrity
required:
- s3Key
securitySchemes:
bearer:
scheme: bearer
bearerFormat: ditto_sk_live_… API key, ditto_oat_… OAuth access token, or a session JWT
type: http
description: An API key (`ditto_sk_live_…`, made at https://ditto.audio/account/api-keys) authenticates /v1/audio/* and /v1/reader/*; an OAuth connection's one-hour access token (`ditto_oat_…`) holds the audio:read and audio:speech scopes only. Account routes (/v1/api-keys, /v1/usage, /v1/subscription) take the signed-in session's token (a JWT) only.