Model Context Protocol (MCP)

Connect AI assistants directly to TTS generation via the MCP standard.

Overview

MCP (Model Context Protocol) lets AI agents call TTS tools without custom HTTP integration. The remote HTTP server exposes 57 tools covering generation, voices, jobs, shares, library management, storage, account pricing, and personalization.

Two connection methods are available: a local stdio CLI for desktop AI apps, and a remote HTTP endpoint for server-side agents. The stdio proxy exposes 56 REST-backed tools, while remote MCP additionally exposes apply_mode as a composite convenience tool.

Connection Methods

Local stdio (recommended for desktop)

Add this to your MCP client config (Claude Desktop, Cursor, etc.):

MCP client config
{
  "mcpServers": {
    "aittsm": {
      "command": "npx",
      "args": ["@theproductivepixel/aittsm"],
      "env": { "AITTSM_API_KEY": "tts_YOUR_KEY" }
    }
  }
}

Set AITTSM_API_KEY to your API key. Optionally set AITTSM_BASE_URL to override the default API base URL.

Remote HTTP (server-to-server)

Send JSON-RPC messages directly to POST /api/v1/mcp. The endpoint is stateless and accepts either an API key or an OAuth 2.1 access token.

Remote HTTP
# Remote HTTP request. Use an API key (tts_...) or OAuth token (oauth_at_...).
curl -X POST https://aitts.theproductivepixel.com/api/v1/mcp \
  -H "Authorization: Bearer API_KEY_OR_OAUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Use an API key or OAuth token as the bearer credential in the curl example. All URLs below derive from BASE_URL. The fallback value is the production origin.

For OAuth 2.1, discover the protected resource at https://aitts.theproductivepixel.com/.well-known/oauth-protected-resource and the authorization server at https://aitts.theproductivepixel.com/.well-known/oauth-authorization-server.

Authentication failures include a WWW-Authenticate header. The same challenge is mirrored in MCP response metadata as _meta["mcp/www_authenticate"] so Apps clients can link the user to OAuth.

A valid OAuth token without the required scope returns 403 insufficient_scope. A missing or invalid bearer credential, including a wrong-audience OAuth token, returns 401.

OAuth client lifecycle

The authorization server is provider-neutral. Clients consume its discovered metadata and their resolved client record instead of inferring endpoints, redirect URIs, or policy from a provider name.

  1. Discover the protected resource and authorization server metadata from the two .well-known endpoints.
  2. Use a predefined client, an HTTPS CIMD client_id that points to its metadata document, or RFC 7591 registration at POST /api/v1/oauth/register.
  3. Start the authorization-code flow at /api/v1/oauth/authorize. The record controls redirect URIs, scopes, grants, client authentication, PKCE, and resource requirements.
  4. Exchange the code or rotate a refresh token at POST /api/v1/oauth/token. Revoke an access or refresh token family at POST /api/v1/oauth/revoke.

S256 is the only supported PKCE method. Public and CIMD clients, plus any client record with pkceRequired, must send it. Required-resource clients send the exact canonical resource. The legacy predefined GPT client is the transition exception and may omit PKCE and resource while its record allows it.

For CIMD, the client_idis the exact HTTPS metadata-document URL and the document's own client identifier must match it. Dynamic registration is never anonymous: generic or unverified lanes require an initial access token, while provider lanes are available only when their server-side trust configuration is enabled.

The registered authentication method controls both token and revoke requests. A private_key_jwt client publishes JWKS and sends an assertion with the exact endpoint audience, bounded iat/exp, matching iss/sub, and a one-time jti.

Available Tools (57)

Each tool requires a specific permission. API-key callers need the API-key permission, while OAuth callers need the matching OAuth scope. The names are the same. Tools are grouped by function:

Voice & Generation

API-key permission / OAuth scope: tts:generate, tts:status, voices:list

search_voicesget_voice_detailsget_voice_sample_urlgenerate_speechget_job_statusget_audio_link

Jobs

API-key permission / OAuth scope: jobs:read, jobs:write

list_jobsget_job_textupdate_job_metadatadelete_job_audio

Shares

API-key permission / OAuth scope: shares:read, shares:write

create_sharecreate_voice_sharelist_sharesget_shareupdate_sharerevoke_sharebulk_revoke_sharestoggle_share_permanentupdate_track_order

Access Codes & QR

API-key permission / OAuth scope: shares:read, shares:write

create_access_codeslist_access_codesupdate_access_codedelete_access_codeexport_access_codesget_qr_code

Library

API-key permission / OAuth scope: library:read, library:write

list_collectionsmanage_collectionlist_tagscreate_bookmarklist_bookmarksdelete_bookmarkmanage_bookmark_collection

Storage & Usage

API-key permission / OAuth scope: storage:read, storage:write, usage:read, pricing:estimate

get_storagelist_storage_itemsbulk_delete_storageget_usageestimate_costget_pricing

Personalization: Preferences & Modes

API-key permission / OAuth scope: preferences:read, preferences:write, modes:read, modes:write

get_preferencesset_preferenceslist_modesget_modepreview_modeapply_modecreate_modeupdate_modedelete_mode

Personalization: Projects

API-key permission / OAuth scope: projects:read, projects:write

list_projectsget_projectcreate_projectupdate_projectdelete_projectlist_project_itemsget_project_itemadd_project_itemupdate_project_itemremove_project_item

tools/list returns every descriptor with a title and explicit readOnlyHint, destructiveHint, and openWorldHint annotations. Each descriptor also includes securitySchemes at the top level and the identical _meta.securitySchemes mirror.

Patterns

Pagination

Paginated tools accept page_size (1–100, default 20) and page_token (opaque cursor). Personalization lists preserve the complete legacy envelope up to 100 raw records when both are omitted, omit next_page_token, and return PAGINATION_REQUIRED above that cap.

Delivery Mode

generate_speech supports two modes:

  • async (default): Returns job_id. Poll get_job_status until completed, then get_audio_link.
  • stream: Returns one-time stream_url for real-time audio. URL expires and is single-use. Durable artifact saved after stream completes.

Stream supports all 7 formats (wav, mp3, ogg_opus, pcm, mulaw, alaw, ogg_vorbis). Async supports wav, mp3, ogg_opus only.

Idempotency

Pass idempotency_key to generate_speech for safe retries. Same key + same body returns cached result. Same key + different body returns IDEMPOTENCY_KEY_REUSE (409). Key still processing returns REQUEST_IN_PROGRESS (409).

Rate Limiting

Tools use two buckets: read (higher limits, queries) and generate (lower limits, mutations). Limits are per-account. HTTP 429 with Retry-After header when exceeded.

Permissions

API-key permission / OAuth scopeTools
voices:listsearch_voices, get_voice_details
tts:generategenerate_speech, get_voice_sample_url
tts:statusget_job_status, get_audio_link
jobs:readlist_jobs, get_job_text
jobs:writedelete_job_audio, update_job_metadata
shares:readlist_shares, get_share, list_access_codes, export_access_codes
shares:writecreate_share, create_voice_share, update_share, revoke_share, bulk_revoke_shares, toggle_share_permanent, update_track_order, create_access_codes, update_access_code, delete_access_code, get_qr_code
library:readlist_collections, list_tags, list_bookmarks
library:writemanage_collection, create_bookmark, delete_bookmark, manage_bookmark_collection
storage:readget_storage, list_storage_items
storage:writebulk_delete_storage
pricing:estimateestimate_cost, get_pricing
usage:readget_usage
preferences:readget_preferences, preview_mode
preferences:writeset_preferences, apply_mode
modes:readlist_modes, get_mode
modes:writecreate_mode, update_mode, delete_mode
projects:readlist_projects, get_project, list_project_items, get_project_item
projects:writecreate_project, update_project, delete_project, add_project_item, update_project_item, remove_project_item

Examples

Initialize
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {},
    "clientInfo": { "name": "my-app", "version": "1.0.0" }
  }
}
List tools
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}
Generate speech
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "generate_speech",
    "arguments": {
      "text": "Hello from MCP!",
      "voice_id": "google:en-US-Chirp3HD-Charon"
    }
  }
}
Generate speech (stream)
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "generate_speech",
    "arguments": {
      "text": "Stream this audio in real time.",
      "voice_id": "google:en-US-Chirp3HD-Charon",
      "delivery_mode": "stream",
      "idempotency_key": "my-unique-key-123"
    }
  }
}
Search voices
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "search_voices",
    "arguments": { "q": "Leda", "language": "en-US", "gender": "female" }
  }
}
Get audio link
{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "get_audio_link",
    "arguments": { "job_id": "YOUR_JOB_ID" }
  }
}
// Result carries a fresh audio_url (signed_url is a deprecated alias) plus
// expires_at, expires_in, content_type, audio_bytes, and audio_endpoint.
OAuth discovery
# Discover the protected resource and authorization server.
curl https://aitts.theproductivepixel.com/.well-known/oauth-protected-resource
curl https://aitts.theproductivepixel.com/.well-known/oauth-authorization-server
CIMD client ID
# A CIMD client_id is the exact HTTPS metadata-document URL.
client_id=https://client.example/.well-known/oauth-client
Register an OAuth client
# RFC 7591 Dynamic Client Registration. Use the initial access
# token issued for the registration lane. Anonymous registration is not supported.
curl -X POST https://aitts.theproductivepixel.com/api/v1/oauth/register \
  -H "Authorization: Bearer INITIAL_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"redirect_uris":["https://client.example/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"],"scope":"voices:list tts:generate"}'
Exchange or refresh a token
# Authorization-code grant. Apply the client record's registered
# authentication method and send S256 code_verifier for PKCE-required clients.
# Required-resource clients also send resource=https://aitts.theproductivepixel.com.
curl -X POST https://aitts.theproductivepixel.com/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=authorization_code&client_id=CLIENT_ID&code=AUTH_CODE&redirect_uri=https%3A%2F%2Fclient.example%2Fcallback&code_verifier=CODE_VERIFIER&resource=https://aitts.theproductivepixel.com'

# Refresh-token grant.
curl -X POST https://aitts.theproductivepixel.com/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=refresh_token&client_id=CLIENT_ID&refresh_token=REFRESH_TOKEN&resource=https://aitts.theproductivepixel.com'
Revoke OAuth tokens
# RFC 7009 family revocation. Authenticate exactly as registered:
# public clients send client_id; confidential clients use HTTP Basic or
# client_secret_post; private_key_jwt clients send client_assertion_type and
# client_assertion. A live token revokes its family. Unknown, expired, or
# already-revoked tokens return the same empty 200 response after valid auth.
curl -X POST https://aitts.theproductivepixel.com/api/v1/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'client_id=CLIENT_ID&token=ACCESS_OR_REFRESH_TOKEN'

get_audio_link returns a fresh audio_url (the legacy signed_url is a deprecated alias) plus expires_at, expires_in, content_type, and audio_bytes.

Tool Reference

Complete parameter tables, response shapes, and error codes for every tool.

Voice & Generation

search_voicesvoices:list · read

Search available TTS voices with optional filters and free-text query.

ParamTypeReqDescription
qstring—Free-text fuzzy search across voice_id, family, name, language, and provider. Partial and typo-tolerant (e.g. "Leda"). Combines with the filters below.
languagestring—Language code (e.g. en-US)
providerstring—google, polly, or kokoro
model_typeenum: premium | ultra—Filter by model type
genderenum: male | female | neutral | unknown—Filter by gender
voice_idstring—Exact voice_id filter (case-insensitive)
Response:{ voices: ApiV1Voice[], count: number }
get_voice_detailsvoices:list · read

Get full capability details for a voice (streaming, formats, limits, speed, prompt).

ParamTypeReqDescription
voice_idstring✓Voice ID (provider:language-Family-Name)
modelstring—Model ID for ultra voices with model selection
Response:{ voice_id: string, provider: string, language: string, family: string, name: string, model_type: string, gender: string, characteristics: object, sample_url: string | null, capabilities: VoiceCapabilities, available_models?: string[], default_model?: string, model?: string, model_overrides?: Record<string, ModelOverride> }
Errors:
INVALID_VOICE_IDVOICE_NOT_FOUNDMODEL_SELECTION_NOT_AVAILABLEINVALID_MODEL
generate_speechtts:generate · generate

Generate TTS audio. Returns job_id for async or stream_url for real-time.

ParamTypeReqDescription
textstring (1–500000)✓Text to synthesize
voice_idstringsingle onlyVoice ID (provider:lang-Family-Name). Required for single-speaker, forbidden for multi-speaker.
delivery_modeenum: async | stream—Default: async. stream returns one-time URL.
model_typeenum: premium | ultra—Model type
modelstring—Specific model ID
speednumber (0.25–4)—Speaking rate multiplier
formatenum: text | ssml | markup—Input format
speaker_typeenum: single | multi—Speaker type
voice_id_speaker_1stringmulti onlySpeaker 1 voice ID. Required for multi-speaker only.
voice_id_speaker_2stringmulti onlySpeaker 2 voice ID. Required for multi-speaker only.
output_formatenum: wav | mp3 | ogg_opus | pcm | mulaw | alaw | ogg_vorbis—Async: wav/mp3/ogg_opus. Stream: all 7.
promptstring—Ultra model guidance prompt
webhook_urlstring (URL)—Completion webhook (enterprise)
metadataobject—Custom metadata
sample_rate_hertzinteger—Output sample rate
output_bitrate_kbpsinteger—Bitrate (async only)
languagestring—Language override
tagsstring[]—Library tags
collection_idstring—Collection assignment
idempotency_keystring (max 256)—Safe retry key (no line breaks)
Response:Async: { job_id, status, poll_url, audio_endpoint, chars_charged } Stream: { job_id, status, poll_url, audio_endpoint, stream_url?, transport_format, transport_mime_type, transport_sample_rate_hertz, chars_charged, cache? }
Errors:
VALIDATION_ERRORINVALID_VOICEPROVIDER_DISABLEDINSUFFICIENT_CREDITSSTORAGE_CAP_EXCEEDEDFORBIDDENINVALID_WEBHOOK_URLENTERPRISE_TIER_REQUIREDSTREAM_NOT_SUPPORTEDSPEED_OUT_OF_RANGESTREAM_BITRATE_NOT_SUPPORTEDSTREAM_FORMAT_MISMATCHMAINTENANCEIDEMPOTENCY_KEY_REUSEREQUEST_IN_PROGRESS
get_job_statustts:status · read

Get status and metadata of a TTS job.

ParamTypeReqDescription
job_idstring✓Job ID
Response:{ job_id, status, created_at?, progress_message?, elapsed_seconds?, provider?, model_requested?, model_effective?, output_format?, sample_rate_hertz?, output_bitrate_kbps?, duration_seconds?, estimated_duration_seconds?, prompt_bytes, chars_charged, audio_endpoint, audio_available, retention_tier?, retained_until?, voice_id?, model_type?, audio_bytes?, tags, collection_id?, source?, is_expired, audio_url?, audio_url_expires_at?, error?, metadata? }
Errors:
JOB_NOT_FOUND
get_audio_linktts:status · read

Get a fresh signed download URL plus audio metadata for completed audio. audio_url is canonical; signed_url is a deprecated alias.

ParamTypeReqDescription
job_idstring✓Job ID
Response:{ job_id, audio_url, signed_url, // deprecated alias of audio_url expires_at, expires_in, // seconds until expiry content_type, audio_bytes, audio_endpoint }
Errors:
JOB_NOT_FOUND
get_voice_sample_urltts:generate · generate

Get a short-lived sample-audio URL for a voice. Generation-class: when no sample exists yet, this triggers sample synthesis and returns a pending status until the audio is ready.

ParamTypeReqDescription
voice_idstring✓Voice ID (provider:language-Family-Name). The language is derived from the voice_id.
modelstring—Optional Gemini ultra sub-model id (honored only for Google ultra voices).
Response:Ready: { voice_id, sample_url, voices_url, // /voices deep-link to this voice expires_at, // <= 7 days (GCS signed-URL cap) expires_in, // seconds until expiry content_type } Pending: { status: 'pending', voices_url, // /voices deep-link to this voice retry_after // seconds to wait before retrying }
Errors:
VALIDATION_ERRORINVALID_VOICE

Jobs

get_job_textjobs:read · read

Retrieve the input text of a completed TTS job.

ParamTypeReqDescription
job_idstring✓Job ID
Response:{ text }
Errors:
JOB_NOT_FOUNDTEXT_UNAVAILABLE
list_jobsjobs:read · read

List TTS jobs with pagination and filters.

ParamTypeReqDescription
page_sizeinteger (1–100)—Default 20
page_tokenstring—Pagination cursor
sourceenum: api | ui—Filter by source
statusenum: completed | failed | pending | processing—Filter by status
Response:{ jobs: JobSummary[], next_page_token }
delete_job_audiojobs:write · generate

Delete stored audio for a completed job.

ParamTypeReqDescription
job_idstring✓Job ID
Response:{ shares_revoked, storage? }
Errors:
JOB_NOT_FOUNDJOB_IN_PROGRESS
update_job_metadatajobs:write · generate

Update tags and/or collection for a job.

ParamTypeReqDescription
job_idstring✓Job ID
tagsstring[]—Replace tags
collection_idstring | null—Set or clear collection
Response:{ job_id, tags, collection_id }
Errors:
JOB_NOT_FOUNDVALIDATION_ERROR

Shares

create_shareshares:write · generate

Create a shareable link. Provide job_ids (snapshot) or source_type+source_id (source-based).

ParamTypeReqDescription
job_idsstring[]—Job IDs for snapshot
source_typeenum: collection | tag—Source type
source_idstring—Source ID
share_modeenum: snapshot | live—Default: snapshot
auth_modeenum: none | password | access_code—Auth mode
passwordstring—Required if auth_mode=password
titlestring—Share title
allow_downloadboolean—Allow download
include_textboolean—Include text excerpts
show_voiceboolean—Show voice info
show_modelboolean—Show model info
show_providerboolean—Show provider
show_languageboolean—Show language
show_expiryboolean—Show expiry
show_track_metaboolean—Show track metadata
track_titlesRecord<string, string>—{ jobId: title }
track_orderstring[]—Custom track order
Response:{ code, url, item_count }
Errors:
AMBIGUOUS_SHARE_INPUTVALIDATION_ERRORINVALID_SHARE_SOURCEPASSWORD_REQUIREDINVALID_AUTH_MODE_PASSWORD_COMBO
create_voice_shareshares:write · generate

Create a shareable voice collection from caller-supplied voice references. The returned URL is built from the configured app origin, never the caller Origin header. Mirrors POST /api/v1/voice-shares.

ParamTypeReqDescription
voiceRefsVoiceRef[] (1–50)✓Voice references to share. Each ref is { id, language?, ultraModel? }: id is a public voice_id (provider:language-Family-Name) as returned by search_voices / GET /api/v1/voices, language is an optional BCP-47 code the voice supports, ultraModel is an optional Gemini sub-model id. Every id is canonicalized + language-captured internally and validated against the catalog.
Response:{ code, url }
Errors:
VALIDATION_ERRORINVALID_VOICE_REFINVALID_VOICE_LANGUAGEINVALID_ULTRA_MODEL
list_sharesshares:read · read

List active shares with pagination.

ParamTypeReqDescription
page_sizeinteger (1–100)—Default 20
page_tokenstring—Pagination cursor
Response:{ shares: ShareSummary[], next_page_token }
get_shareshares:read · read

Get full share details including job metadata.

ParamTypeReqDescription
codestring✓Share code
Response:{ code, title?, share_mode, auth_mode, source_type?, source_id?, created_at, expires_at?, revoked, views, permanent, allow_download, include_text, show_*, track_titles?, track_order?, item_count, jobs: [{ job_id, voice_id, model_type, created_at, audio_bytes?, output_format?, duration_seconds?, estimated_duration_seconds?, duration_source?, duration_confidence?, audio_sample_rate_hertz?, sample_rate_hertz?, output_bitrate_kbps? }] }
Errors:
SHARE_NOT_FOUND
update_shareshares:write · generate

Update share settings.

ParamTypeReqDescription
codestring✓Share code
titlestring—New title
auth_modeenum: none | password | access_code—Auth mode
passwordstring—Password
allow_downloadboolean—Allow download
share_modeenum: snapshot | live—Share mode
include_textboolean—Include text
show_voiceboolean—Show voice
show_modelboolean—Show model
show_providerboolean—Show provider
show_languageboolean—Show language
show_expiryboolean—Show expiry
show_track_metaboolean—Show track meta
track_titlesRecord<string, string>—Track titles
track_orderstring[] | null—Track order or null
source_typeenum: collection | tag—Source type
source_idstring—Source ID
Response:(Same shape as get_share)
Errors:
SHARE_NOT_FOUNDINVALID_SHARE_SOURCESOURCE_IMMUTABLEPASSWORD_REQUIREDINVALID_AUTH_MODE_PASSWORD_COMBO
revoke_shareshares:write · generate

Revoke a share, disabling access.

ParamTypeReqDescription
codestring✓Share code
Response:{ code, revoked: true }
Errors:
SHARE_NOT_FOUND
bulk_revoke_sharesshares:write · generate

Revoke multiple shares (max 100).

ParamTypeReqDescription
codesstring[] (1–100)✓Share codes
Response:{ revoked_count, skipped: string[] }
Errors:
VALIDATION_ERROR
toggle_share_permanentshares:write · generate

Toggle permanent status on a share.

ParamTypeReqDescription
codestring✓Share code
Response:{ code, permanent, expires_at? }
Errors:
SHARE_NOT_FOUND
update_track_ordershares:write · generate

Reorder tracks. Pass null to reset.

ParamTypeReqDescription
codestring✓Share code
track_orderstring[] | null✓Ordered job IDs or null
Response:{ code, track_order }
Errors:
SHARE_NOT_FOUNDVALIDATION_ERROR

Access Codes & QR

create_access_codesshares:write · generate

Create access codes for a share. Raw codes returned once only.

ParamTypeReqDescription
codestring✓Parent share code
countinteger (1–100)—Default 1
labelstring—Label or prefix
expires_atstring (ISO date)—Expiration
max_usesinteger (≥1)—Max uses per code
Response:count=1: { id, code, code_prefix, label?, active, created_at, expires_at?, max_uses? } count>1: [ { id, code }, ... ]
Errors:
SHARE_NOT_FOUNDVALIDATION_ERROR
list_access_codesshares:read · read

List access codes for a share (no raw codes).

ParamTypeReqDescription
codestring✓Share code
Response:[ { id, code_prefix, label?, active, created_at, expires_at?, max_uses?, uses, last_used_at? }, ... ]
Errors:
SHARE_NOT_FOUND
update_access_codeshares:write · generate

Update an access code.

ParamTypeReqDescription
codestring✓Share code
access_code_idstring✓Access code ID
labelstring—New label
activeboolean—Active status
expires_atstring | null—New expiry or null
max_usesinteger | null—New max uses or null
Response:{ id, code_prefix, label?, active, created_at, expires_at?, max_uses?, uses, last_used_at? }
Errors:
SHARE_NOT_FOUNDACCESS_CODE_NOT_FOUND
delete_access_codeshares:write · generate

Permanently delete an access code.

ParamTypeReqDescription
codestring✓Share code
access_code_idstring✓Access code ID
Response:{ deleted: true }
Errors:
SHARE_NOT_FOUNDACCESS_CODE_NOT_FOUND
export_access_codesshares:read · read

Export access codes in CSV-compatible format.

ParamTypeReqDescription
codestring✓Share code
Response:[ { id, code_prefix, label?, active, created_at, expires_at?, max_uses?, uses }, ... ]
Errors:
SHARE_NOT_FOUND
get_qr_codeshares:write · generate

Generate a QR code image for a share link.

ParamTypeReqDescription
codestring✓Share code
formatenum: svg | png✓Image format
presetenum: clean | branded—Visual preset
include_access_codeboolean—Embed code in URL
access_codestring—Code to embed
Response:{ data_uri, format }
Errors:
SHARE_NOT_FOUND

Library & Storage

list_collectionslibrary:read · read

List all audio collections.

Response:{ collections: [ { id, name, created_at, updated_at } ] }
manage_collectionlibrary:write · generate

Create, rename, or delete a collection.

ParamTypeReqDescription
actionenum: create | rename | delete✓Operation
namestring—Required for create/rename
collection_idstring—Required for rename/delete
Response:create/rename: { id, name, created_at, updated_at } delete: { deleted: true }
Errors:
VALIDATION_ERRORCOLLECTION_NOT_FOUND
list_tagslibrary:read · read

List all tags with usage counts.

Response:{ tags: [ { tag, count } ] }
create_bookmarklibrary:write · generate

Bookmark a shared audio link.

ParamTypeReqDescription
share_codestring✓Share code
Response:{ created: true }
Errors:
VALIDATION_ERRORALREADY_EXISTS
list_bookmarkslibrary:read · read

List bookmarks with pagination.

ParamTypeReqDescription
page_sizeinteger (1–100)—Default 20
page_tokenstring—Pagination cursor
Response:{ bookmarks: [ { id, share_code, title, personal_title?, collection_id?, created_at, last_opened_at?, effective_status } ], next_page_token }
delete_bookmarklibrary:write · generate

Delete a bookmark.

ParamTypeReqDescription
bookmark_idstring✓Bookmark ID (share code)
Response:{ deleted: true }
Errors:
BOOKMARK_NOT_FOUND
manage_bookmark_collectionlibrary:write · generate

Create, rename, or delete a bookmark collection.

ParamTypeReqDescription
actionenum: create | rename | delete✓Operation
namestring—Required for create/rename
collection_idstring—Required for rename/delete
Response:create/rename: { id, name, color?, created_at, updated_at } delete: { deleted: true }
Errors:
VALIDATION_ERRORBOOKMARK_NOT_FOUND
get_storagestorage:read · read

Get storage usage summary.

Response:{ used_bytes, cap_bytes, remaining_bytes, pending_reclaim_bytes, sync_status }
list_storage_itemsstorage:read · read

List stored audio items with pagination.

ParamTypeReqDescription
page_sizeinteger (1–100)—Default 20
page_tokenstring—Pagination cursor
Response:{ items: [ { job_id, status, audio_bytes?, output_format?, created_at?, storage_tier? } ], next_page_token }
bulk_delete_storagestorage:write · generate

Bulk delete stored audio (max 100 jobs).

ParamTypeReqDescription
job_idsstring[] (1–100)✓Job IDs to delete
Response:{ deleted_count, skipped_count, skipped: [ { job_id, reason } ] }
Errors:
VALIDATION_ERROR
estimate_costpricing:estimate · read

Estimate TTS cost without generating.

ParamTypeReqDescription
textstring (1–500000)✓Text to estimate
model_typeenum: premium | ultra—Model type
voice_idstring—Voice ID
output_formatenum: wav | mp3 | ogg_opus—Format
Response:{ estimated_cost, currency, chars_charged, model_type, provider, output_format }
Errors:
VALIDATION_ERRORENTERPRISE_TIER_REQUIRED
get_usageusage:read · read

Get API usage and credit balance.

Response:{ account_type, credits_balance?, balance: { amount, currency, formatted } }
get_pricingpricing:estimate · read

Get the account's current subscription plans and usage rates, in one currency.

Response:resolved: { pricing_available: true, currency, plans: [ { id, title, price, highlights } ], usage_rates: [ { provider, voice_class, rate_per_1k_bytes } ], pricing_url, guidance } setup required: { pricing_available: false, requires_account_pricing_setup: true, message, setup_url }

Personalization: Preferences & Modes

get_preferencespreferences:read · read

Read the user's saved TTS personalization preferences: the raw sparse overrides, the effective resolved config (system defaults, then the active mode if set, then overrides — later wins), a version for optimistic concurrency, and the active mode id/revision. Read-only.

Response:{ overrides, // sparse user overrides effective: { ttsDefaults, playlist, interaction }, version, updated_at, active_mode_id, // null when no/stale active mode mode_revision // null when no active mode }
set_preferencespreferences:write · generate

Persist a change to the user's saved preferences via an RFC 7396 merge-patch (set a field to null to reset it to the inherited default). Requires expected_version from a prior get_preferences call (optimistic concurrency).

ParamTypeReqDescription
updatesobject (RFC 7396 merge-patch)✓Merge-patch over the override doc. Each namespace is itself nullable (null resets the whole namespace): ttsDefaults { voiceId, ttsProvider (google|gemini|polly|kokoro), tier (premium|ultra), language, speed (0.25–4), outputFormat }, playlist { summaryFirst, maxTracks (1–100) }, interaction { responseStyle (concise|detailed|neutral) }, and activeModeId. Any leaf set to null resets it to inherited.
expected_versioninteger (≥0)✓Version from a prior get_preferences call (optimistic concurrency; a stale value returns PRECONDITION_FAILED).
Response:{ overrides, version, changed: string[] // top-level keys in the patch }
Errors:
PRECONDITION_FAILED
list_modesmodes:read · read

List personalization modes. Omitting pagination returns the complete legacy envelope up to 100 raw records and omits next_page_token; larger lists return PAGINATION_REQUIRED. Supplying either field enables stable pages. Read-only.

ParamTypeReqDescription
page_sizeinteger (1–100)—Explicit page size; defaults to 20 once pagination is selected.
page_tokenstring (≤2048 chars)—Opaque owner-bound token from the previous page; it cannot be reused by another account.
Response:{ modes: [ { id, title, kind, source, // system_default | user_created | user_fork version, enabled, category?, // system modes only description? // system modes only } ], next_page_token?: string | null }
Errors:
PAGINATION_REQUIRED
get_modemodes:read · read

Read full detail for one personalization mode by id (system or user-owned), including its config (the sparse preference overrides it applies) and, for system modes, agent guidance. Read-only.

ParamTypeReqDescription
mode_idstring✓Mode id (system or user-owned)
Response:System mode: { source: 'system_default', id, title, kind, version, enabled: true, category, description, config, // sparse overrides agentGuidance } User mode: { source: 'user_created' | 'user_fork', id, title, kind, version, enabled, sortKey, config, baseModeId?, baseModeVersion? }
Errors:
NOT_FOUND
preview_modepreferences:read · read

Preview the effective preferences that WOULD apply if this mode were active, WITHOUT changing anything. Resolves system defaults, then this mode's config, then the user's own overrides (later wins). Read-only.

ParamTypeReqDescription
mode_idstring✓Mode id to preview (must resolve to a mode visible to this user, else NOT_FOUND)
Response:{ effective: { ttsDefaults, playlist, interaction }, mode_id, mode_revision, version }
Errors:
NOT_FOUND
create_modemodes:write · generate

Create a new personalization mode owned by the user, either from scratch or forked from an existing system/user mode via baseModeId (deep-merging config over the base, input wins).

ParamTypeReqDescription
titlestring✓Human-readable display title
kindenum: photo_to_playlist | bedtime_stories | language_learning | news_curation | longread_to_audio | podcast_two_voice | accessibility_reader | custom—Mode kind; defaults to custom
configobject—Sparse preference overrides (ttsDefaults/playlist/interaction; activeModeId not allowed). Defaults to empty.
baseModeIdstring—System/user mode id to fork from, if any
enabledboolean—Whether the new mode is enabled
Response:{ mode: UserModeDoc, version }
Errors:
NOT_FOUND
update_modemodes:write · generate

Apply an RFC 7396 merge-patch to a user-owned mode (set a field to null to reset it to inherited). System modes are immutable and always rejected. Requires expected_version from a prior list_modes/get_mode call.

ParamTypeReqDescription
mode_idstring✓User-owned mode id
updatesobject (RFC 7396 merge-patch)✓Patch over the mode: title, enabled, sortKey (plain replacement, at most 192 UTF-8 bytes); config (nullable merge-patch over the mode config — leaves/namespaces set to null reset to inherited; activeModeId not allowed).
expected_versioninteger (≥1)✓Version from a prior list_modes/get_mode call (optimistic concurrency).
Response:{ mode: UserModeDoc, version }
Errors:
IMMUTABLE_MODENOT_FOUNDPRECONDITION_FAILED
delete_modemodes:write · generate

Permanently delete a user-owned personalization mode. System modes are immutable and always rejected. If it was the active mode, the stored selector is gracefully ignored on future reads (no automatic cleanup).

ParamTypeReqDescription
mode_idstring✓User-owned mode id
Response:{ id, deleted: true }
Errors:
IMMUTABLE_MODENOT_FOUND
apply_modepreferences:write · generate

Set this mode as the user's active personalization mode (persists activeModeId) and return the resulting effective preferences. Unlike preview_mode, this PERSISTS a change to the saved preferences document.

ParamTypeReqDescription
mode_idstring✓Mode id to activate (must resolve to a mode visible to this user, else NOT_FOUND)
Response:{ active_mode_id, version, effective: { ttsDefaults, playlist, interaction }, mode_revision }
Errors:
NOT_FOUNDPRECONDITION_FAILED

Personalization: Projects

list_projectsprojects:read · read

List projects. Omitting pagination returns the complete legacy envelope up to 100 raw records and omits next_page_token; larger lists return PAGINATION_REQUIRED. Supplying either field enables stable pages. Read-only.

ParamTypeReqDescription
page_sizeinteger (1–100)—Explicit page size; defaults to 20 once pagination is selected.
page_tokenstring (≤2048 chars)—Opaque owner-bound token from the previous page.
Response:{ projects: [ { id, title, kind, status, defaultModeId?, updatedAt, createdAt, version } ], next_page_token?: string | null }
Errors:
PAGINATION_REQUIRED
get_projectprojects:read · read

Get the full detail of a project: the project doc plus its itemCount and the next pending item (the resume-where-we-left-off pointer — the lowest-ordinal item still pending, or null if none). Read-only.

ParamTypeReqDescription
project_idstring✓Project id
Response:{ project: ProjectDoc, itemCount, nextPendingItem: { id, ordinal, ref, title? } | null }
Errors:
NOT_FOUND
create_projectprojects:write · generate

Create a new project owned by the user (an ongoing series/playlist with progress, e.g. a book, course, or podcast). A project never stores audio itself — items added later point at existing jobs or shares.

ParamTypeReqDescription
titlestring (1–200)✓Human-readable display title
kindenum: book | course | news_feed | podcast | series | custom—Project kind; defaults to custom
descriptionstring (max 2000)—Optional longer-form description
statusenum: active | archived | completed—Initial status; defaults to active
defaultModeIdstring—Phase-2 personalization mode id applied by default to items
metadataobject (≤30 keys)—Free-form agent scratch space
Response:{ project: ProjectDoc, version }
update_projectprojects:write · generate

Apply an RFC 7396 merge-patch to a user-owned project (set description/defaultModeId/metadata to null to reset to unset). Requires expected_version from a prior list_projects/get_project call.

ParamTypeReqDescription
project_idstring✓Project id
updatesobject (RFC 7396 merge-patch)✓Patch over the project: title/kind/status (plain replacements); description/defaultModeId/metadata accept null to reset to unset (a null metadata VALUE deletes that key).
expected_versioninteger (≥1)✓Version from a prior list_projects/get_project call (optimistic concurrency).
Response:{ project: ProjectDoc, version }
Errors:
NOT_FOUNDPRECONDITION_FAILED
delete_projectprojects:write · generate

Delete a project AND all its items (cascade). This is irreversible and cannot be undone.

ParamTypeReqDescription
project_idstring✓Project id
Response:{ id, deleted: true }
Errors:
NOT_FOUND
list_project_itemsprojects:read · read

List project items. Omitting pagination returns the complete legacy envelope up to 100 raw records and omits next_page_token; larger lists return PAGINATION_REQUIRED. Supplying either field enables stable pages. Read-only.

ParamTypeReqDescription
project_idstring✓Project id (a missing project yields an empty list)
page_sizeinteger (1–100)—Explicit page size; defaults to 20 once pagination is selected.
page_tokenstring (≤2048 chars)—Opaque owner- and project-bound token from the previous page.
Response:{ items: ProjectItemDoc[], // ordered by ordinal then id next_page_token?: string | null }
Errors:
PAGINATION_REQUIRED
get_project_itemprojects:read · read

Get full detail for a single project item by id. Read-only.

ParamTypeReqDescription
project_idstring✓Parent project id
item_idstring✓Project item id
Response:{ id, ownerUid, projectId, ordinal, title?, ref, // { type: 'job', jobId } | { type: 'share', shareCode } status, // pending | ready | failed | skipped metadata?, version, createdAt, updatedAt }
Errors:
NOT_FOUND
add_project_itemprojects:write · generate

Add a new item to a project: a pointer to an existing job or share (ref), with an optional explicit ordinal (otherwise auto-appended as max+1), title, initial status, and metadata.

ParamTypeReqDescription
project_idstring✓Parent project id
refobject✓Pointer to existing content: { type: 'job', jobId } or { type: 'share', shareCode }
ordinalinteger (≥0)—Sequence position; auto-appended as max+1 when omitted
titlestring (max 200)—Optional display title for this item
statusenum: pending | ready | failed | skipped—Initial status; defaults to pending
metadataobject (≤30 keys)—Free-form agent scratch space
Response:{ item: ProjectItemDoc, version }
Errors:
NOT_FOUND
update_project_itemprojects:write · generate

Apply an RFC 7396 merge-patch to a project item (ordinal/ref/status are plain replacements; title/metadata accept null to reset — ref is always a full replace, never merged). Requires expected_version from a prior list_project_items/get_project_item call.

ParamTypeReqDescription
project_idstring✓Parent project id
item_idstring✓Project item id
updatesobject (RFC 7396 merge-patch)✓Patch over the item: ordinal/ref/status (plain replacements; ref is a full replace, never merged); title/metadata accept null to reset to unset.
expected_versioninteger (≥1)✓Version from a prior list_project_items/get_project_item call (optimistic concurrency).
Response:{ item: ProjectItemDoc, version }
Errors:
NOT_FOUNDPRECONDITION_FAILED
remove_project_itemprojects:write · generate

Remove a single item from a project. This is irreversible and cannot be undone (no cascade — items have no sub-data).

ParamTypeReqDescription
project_idstring✓Parent project id
item_idstring✓Project item id
Response:{ id, deleted: true }
Errors:
NOT_FOUND

Error Codes

Tool errors are returned as text content with isError: true and a JSON payload: { error: "message", code: "ERROR_CODE", status: 400 }. Protocol-level errors (invalid JSON-RPC, unknown method) use standard JSON-RPC 2.0 error format.

Protocol Errors

CodeMeaning
-32700Parse error — malformed JSON body
-32600Invalid request — bad method or params
-32001Authentication failed: missing or invalid bearer credential
-32603Internal server error

Tool-Level Errors

CodeStatusDescription
VALIDATION_ERROR400Invalid input parameters
INVALID_VOICE400Voice ID not found or invalid format
PROVIDER_DISABLED400Provider unknown or disabled
INSUFFICIENT_CREDITS402Not enough credits for generation
STORAGE_CAP_EXCEEDED403Storage quota full
FORBIDDEN403Enterprise-only feature or ownership check failed
INVALID_WEBHOOK_URL400Webhook URL validation failed
ENTERPRISE_TIER_REQUIRED400Enterprise pricing without tier
STREAM_NOT_SUPPORTED400Voice/provider doesn't support streaming
SPEED_OUT_OF_RANGE400Speaking rate exceeds stream limit
STREAM_BITRATE_NOT_SUPPORTED400Bitrate selection in stream mode
STREAM_FORMAT_MISMATCH400Format not supported for stream transport
MAINTENANCE503API in maintenance mode
IDEMPOTENCY_KEY_REUSE409Key reused with different request body
REQUEST_IN_PROGRESS409Key still processing
JOB_NOT_FOUND404Job doesn't exist or not owned by caller
JOB_IN_PROGRESS409Cannot delete pending/processing job
TEXT_UNAVAILABLE410Job expired or text not stored
SHARE_NOT_FOUND404Share doesn't exist or not owned
AMBIGUOUS_SHARE_INPUT400Both job_ids and source provided
INVALID_SHARE_SOURCE400Invalid source configuration
PASSWORD_REQUIRED400auth_mode=password without password
INVALID_AUTH_MODE_PASSWORD_COMBO400auth_mode and password combination invalid
SOURCE_IMMUTABLE400Cannot change source on source-backed share
ACCESS_CODE_NOT_FOUND404Access code doesn't exist
COLLECTION_NOT_FOUND404Collection doesn't exist or not owned
BOOKMARK_NOT_FOUND404Bookmark or collection doesn't exist
ALREADY_EXISTS409Bookmark already exists for this share
FREE_TIER_LIMIT_EXCEEDED403Free tier limit reached (>1000 characters per request)
Back to Documentation

© 2026 AI TTS Microservice. All rights reserved.