Repository navigation
Expand file tree
/
Copy pathopenapi.json
More file actions
1 lines (1 loc) · 59.7 KB
/
Copy pathopenapi.json
File metadata and controls
1 lines (1 loc) · 59.7 KB
1
{"openapi":"3.1.1","info":{"title":"VoiceLabs API","version":"1.0.0","summary":"Generate speech, transcribe audio, and read your VoiceLabs data over HTTP.","description":"The VoiceLabs public API. Text-to-speech and transcription run on VoiceLabs' GPU servers; this surface is the HTTP way to reach them.\n\nAuthenticate with an API key in the `x-api-key` header, created in the developer console with the scopes it needs. Responses to an API-key request carry IETF draft-11 `RateLimit` headers, and every error is an RFC 9457 problem document whose `code` is a stable token to branch on.\n\nAn OAuth 2.1 access token is the other accepted credential, for software acting on behalf of OTHER people's VoiceLabs accounts — the same authorization server the VoiceLabs MCP server uses. Both carry the same scopes and are gated by the same check; the `RateLimit` headers describe a key's budget and are therefore absent on the OAuth lane.\n\nGeneration is asynchronous: `POST /v1/speech` returns a generation id immediately, and `GET /v1/generations/{generationId}` is polled for the finished audio. Writes accept an `Idempotency-Key` header so a retry after a lost response cannot generate twice.\n\n`POST /v1/audio/speech` is the OpenAI-compatible alternative: point any OpenAI SDK at `https://app.voicelabs.now/v1` as its base URL, pass a VoiceLabs API key, and it works unmodified. That one operation is synchronous (it returns the audio bytes) and answers refusals in OpenAI's `{\"error\": {...}}` envelope rather than a problem document, because that is what an OpenAI SDK reads an error message from. Its `code` member is the same stable token the problem documents carry.\n\nThis document is generated from the same schemas the server validates with, so it describes the running API rather than an account of it.","termsOfService":"https://voicelabs.now/terms","contact":{"name":"VoiceLabs support","url":"https://voicelabs.now/support"}},"servers":[{"url":"https://app.voicelabs.now","description":"Production"}],"tags":[{"name":"Voices & history","description":"Read the voices, captures, and generations on an account."},{"name":"Generation","description":"Create speech and transcribe audio."},{"name":"Voice cloning","description":"Create a cloned voice from a recording. Requires the `voice:clone` scope."}],"paths":{"/v1/voices":{"get":{"operationId":"listVoices","summary":"List voices","description":"List the voice profiles on this account — both cloned voices and built-in presets — with language, default engine, and usage counts. A new account has none until a voice is created.\n\nRequires the `voice:read` scope.","tags":["Voices & history"],"responses":{"200":{"description":"Success.","headers":{"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"language":{"type":"string"},"voice_type":{"type":"string"},"default_engine":{"anyOf":[{"type":"string"},{"type":"null"}]},"has_personality":{"type":"boolean"},"generation_count":{"type":"number"},"sample_count":{"type":"number"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","description","language","voice_type","default_engine","has_personality","generation_count","sample_count","created_at","updated_at"],"additionalProperties":false}},"total":{"type":"number"}},"required":["data","total"],"additionalProperties":false}}}},"400":{"description":"`invalid_request` — Invalid request","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["invalid_request"]}}}]}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["unauthorized"]}}}]}}}},"403":{"description":"`insufficient_scope` — Insufficient scope; `pro_plan_required` — Pro plan required","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["insufficient_scope","pro_plan_required"]}}}]}}}},"404":{"description":"`not_found` — Not found","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["not_found"]}}}]}}}},"429":{"description":"`rate_limit_exceeded` — Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["rate_limit_exceeded"]}}}]}}}},"500":{"description":"`internal_error` — Internal error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["internal_error"]}}}]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["upstream_error"]}}}]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["service_unavailable"]}}}]}}}}},"security":[{"apiKeyHeader":[]},{"bearerToken":[]},{"oauth2":["voice:read"]}]}},"/v1/captures":{"get":{"operationId":"listCaptures","summary":"List captures","description":"List this account's recent captures with their transcripts, most-recent first. Paginated with `limit` (1-200, default 50) and `offset`.\n\nRequires the `voice:read` scope.","tags":["Voices & history"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"offset","in":"query","required":false,"schema":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}}],"responses":{"200":{"description":"Success.","headers":{"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"source":{"type":"string"},"language":{"anyOf":[{"type":"string"},{"type":"null"}]},"duration_ms":{"anyOf":[{"type":"number"},{"type":"null"}]},"transcript_raw":{"type":"string"},"transcript_refined":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string"}},"required":["id","source","language","duration_ms","transcript_raw","transcript_refined","created_at"],"additionalProperties":false}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}},"required":["data","total","limit","offset"],"additionalProperties":false}}}},"400":{"description":"`invalid_request` — Invalid request","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["invalid_request"]}}}]}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["unauthorized"]}}}]}}}},"403":{"description":"`insufficient_scope` — Insufficient scope; `pro_plan_required` — Pro plan required","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["insufficient_scope","pro_plan_required"]}}}]}}}},"404":{"description":"`not_found` — Not found","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["not_found"]}}}]}}}},"429":{"description":"`rate_limit_exceeded` — Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["rate_limit_exceeded"]}}}]}}}},"500":{"description":"`internal_error` — Internal error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["internal_error"]}}}]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["upstream_error"]}}}]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["service_unavailable"]}}}]}}}}},"security":[{"apiKeyHeader":[]},{"bearerToken":[]},{"oauth2":["voice:read"]}]}},"/v1/generations/{generationId}":{"get":{"operationId":"getGeneration","summary":"Get a generation","description":"Look up a generation by id to poll its status (generating, completed, or failed). A failed generation carries a human-readable `error` and a `failure_kind`; both are null otherwise. Returns 404 for an id that does not belong to this account.\n\nRequires the `voice:read` scope.","tags":["Voices & history"],"parameters":[{"name":"generationId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"responses":{"200":{"description":"Success.","headers":{"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"profile":{"type":"string"},"has_audio":{"type":"boolean"},"audio_url":{"anyOf":[{"type":"string"},{"type":"null"}]},"error":{"anyOf":[{"type":"string"},{"type":"null"}]},"failure_kind":{"anyOf":[{"type":"string","enum":["runaway","gpu_out_of_memory","engine_restart","timed_out","cancelled","clone_reference","unknown"]},{"type":"null"}]}},"required":["id","status","profile","has_audio","audio_url","error","failure_kind"],"additionalProperties":false}}}},"400":{"description":"`invalid_request` — Invalid request","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["invalid_request"]}}}]}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["unauthorized"]}}}]}}}},"403":{"description":"`insufficient_scope` — Insufficient scope; `pro_plan_required` — Pro plan required","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["insufficient_scope","pro_plan_required"]}}}]}}}},"404":{"description":"`not_found` — Not found","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["not_found"]}}}]}}}},"429":{"description":"`rate_limit_exceeded` — Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["rate_limit_exceeded"]}}}]}}}},"500":{"description":"`internal_error` — Internal error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["internal_error"]}}}]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["upstream_error"]}}}]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["service_unavailable"]}}}]}}}}},"security":[{"apiKeyHeader":[]},{"bearerToken":[]},{"oauth2":["voice:read"]}]}},"/v1/speech":{"post":{"operationId":"createSpeech","summary":"Create speech","description":"Generate speech from text in one of this account's voice profiles. Returns immediately with a generation id and status \"generating\" — poll GET /v1/generations/{id} to retrieve the audio URL once it completes. Send an Idempotency-Key header to make a retried request safe: the same key with the same body replays the original response instead of generating a second time. `seed` and `temperature` pin the sampling: a seed makes a take reproducible, and a temperature is honoured only by the `chatterbox` and `chatterbox_turbo` engines — ask any other engine for one and the request is refused with `400 invalid_request` rather than generated at a setting that was quietly discarded. This endpoint does not choose the engine; the voice profile's own default engine serves the request.\n\nRequires the `voice:generate` scope.","tags":["Generation"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"An optional client-generated key that makes a retry safe. Replaying the same key with the same body returns the ORIGINAL response byte-for-byte instead of doing the work a second time; replaying it with a different body is refused with `idempotency_key_reused`.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":10000,"description":"The text to speak."},"voice_id":{"description":"Voice profile id.","type":"string","minLength":1,"maxLength":200},"voice_name":{"description":"Voice profile name.","type":"string","minLength":1,"maxLength":200},"language":{"description":"ISO language code. Defaults to the profile's own language.","type":"string","enum":["zh","en","ja","ko","de","fr","ru","pt","es","it","he","ar","da","el","fi","hi","ms","nl","no","pl","sv","sw","tr"]},"seed":{"description":"Pins the random seed so the same text in the same voice renders the same audio every time. Omit to inherit the voice profile's pinned seed, and then the engine's own randomness. Every engine accepts a seed and every engine VoiceLabs ships changes its output when the seed changes, so a pinned seed is genuinely reproducible and a different one genuinely re-rolls the take.","type":"integer","minimum":0,"maximum":9007199254740991},"temperature":{"description":"Sampling temperature, 0.05–2. Omit to inherit the voice profile's pinned temperature, and then the engine's own default. Only the `chatterbox` and `chatterbox_turbo` engines forward a temperature to the model: asking any other engine for one is refused with `400 invalid_request` rather than dropped, because a knob that silently does nothing is indistinguishable from the drift it was set to fix.","type":"number","minimum":0.05,"maximum":2}},"required":["text"],"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","headers":{"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"voice":{"type":"string"}},"required":["id","status","voice"],"additionalProperties":false}}}},"400":{"description":"`invalid_request` — Invalid request","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["invalid_request"]}}}]}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["unauthorized"]}}}]}}}},"403":{"description":"`feature_not_enabled` — Feature not enabled; `insufficient_scope` — Insufficient scope; `pro_plan_required` — Pro plan required","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["feature_not_enabled","insufficient_scope","pro_plan_required"]}}}]}}}},"404":{"description":"`not_found` — Not found","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["not_found"]}}}]}}}},"409":{"description":"`idempotency_key_in_progress` — Idempotency key in progress","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["idempotency_key_in_progress"]}}}]}}}},"422":{"description":"`idempotency_key_reused` — Idempotency key reused","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["idempotency_key_reused"]}}}]}}}},"429":{"description":"`key_ceiling_exceeded` — Key spend ceiling exceeded; `quota_exhausted` — Quota exhausted; `rate_limit_exceeded` — Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["key_ceiling_exceeded","quota_exhausted","rate_limit_exceeded"]}}}]}}}},"500":{"description":"`internal_error` — Internal error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["internal_error"]}}}]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["upstream_error"]}}}]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["service_unavailable"]}}}]}}}}},"security":[{"apiKeyHeader":[]},{"bearerToken":[]},{"oauth2":["voice:generate"]}]}},"/v1/audio/speech":{"post":{"operationId":"createOpenAiSpeech","summary":"Create speech (OpenAI-compatible)","description":"The OpenAI `POST /v1/audio/speech` contract, served by VoiceLabs. Point any OpenAI SDK at `https://app.voicelabs.now/v1` as its base URL and pass a VoiceLabs API key as the API key — no other change is needed. Unlike `POST /v1/speech` this endpoint is SYNCHRONOUS: it holds the response open until the audio is ready and returns the bytes, because that is the contract an OpenAI client is written against. Every parameter VoiceLabs cannot honour is refused with a 400 naming it rather than accepted and ignored. Two parameters OpenAI does not define are accepted as VoiceLabs extensions and forwarded: `seed`, which makes a take reproducible, and `temperature`, which only the `chatterbox` and `chatterbox_turbo` engines honour — every other engine refuses it with `400` rather than discarding it. An OpenAI SDK reaches them through its own escape hatch for non-OpenAI parameters (`extra_body` in the Python client). Errors on this endpoint use OpenAI's `{\"error\": {...}}` envelope instead of the RFC 9457 problem document the rest of /v1 returns, so an OpenAI SDK raises a typed exception with a readable message; the `code` member still carries this API's own stable error token.\n\nRequires the `voice:generate` scope.","tags":["Generation"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","enum":["qwen","qwen_custom_voice","luxtts","chatterbox","chatterbox_turbo","tada","kokoro","tts-1","tts-1-hd","gpt-4o-mini-tts","gpt-4o-mini-tts-2025-12-15"],"description":"A VoiceLabs TTS engine id, which selects the engine. OpenAI's own model names (tts-1, tts-1-hd, gpt-4o-mini-tts) are accepted so an unmodified integration keeps working, but they select nothing: the voice's own default engine is used."},"input":{"type":"string","minLength":1,"maxLength":10000,"description":"The text to speak. OpenAI caps this at 4096 characters; VoiceLabs accepts up to 10000, which is the engine's own limit."},"voice":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":200}},"required":["id"],"additionalProperties":false}],"description":"A VoiceLabs voice profile id or name — list them with GET /v1/voices. OpenAI's custom voice object, { \"id\": \"…\" }, is accepted for the same value. OpenAI's built-in voice names (alloy, echo, nova, …) are not VoiceLabs voices and are refused."},"response_format":{"default":"mp3","description":"The audio container. Defaults to mp3, matching OpenAI. OpenAI's aac, flac and pcm are not encoded by VoiceLabs and are refused rather than silently substituted.","type":"string","enum":["mp3","opus","wav"]},"speed":{"default":1,"description":"Playback speed. Only 1 is accepted: the engine route behind this endpoint has no speed control, and returning audio at the wrong speed would be worse than refusing.","type":"number","const":1},"stream_format":{"default":"audio","description":"Only \"audio\" (the OpenAI default) is supported — the whole file on one response. The \"sse\" event stream is not implemented.","type":"string","const":"audio"},"seed":{"description":"Pins the random seed so the same text in the same voice renders the same audio every time. Omit to inherit the voice profile's pinned seed, and then the engine's own randomness. Every engine accepts a seed and every engine VoiceLabs ships changes its output when the seed changes, so a pinned seed is genuinely reproducible and a different one genuinely re-rolls the take.","type":"integer","minimum":0,"maximum":9007199254740991},"temperature":{"description":"Sampling temperature, 0.05–2. Omit to inherit the voice profile's pinned temperature, and then the engine's own default. Only the `chatterbox` and `chatterbox_turbo` engines forward a temperature to the model: asking any other engine for one is refused with `400 invalid_request` rather than dropped, because a knob that silently does nothing is indistinguishable from the drift it was set to fix.","type":"number","minimum":0.05,"maximum":2}},"required":["model","input","voice"],"additionalProperties":false}}}},"responses":{"200":{"description":"The generated audio, in the requested container: `audio/mpeg` for mp3 (the default), `audio/ogg` for opus, `audio/wav` for wav. The response also carries `X-VoiceLabs-Generation-Id`, the id this generation has in the rest of the API.","headers":{"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"audio/*":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"`invalid_request` — Invalid request","content":{"application/json":{"schema":{"type":"object","description":"An OpenAI-compatible error envelope, so an OpenAI SDK raises a typed exception with a readable message. `code` carries this API's own stable error token, the same one the RFC 9457 problem documents on every other operation use — branch on it, not on `message`.","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable explanation."},"type":{"type":"string","description":"The OpenAI error family, derived from the HTTP status.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","api_error"]},"param":{"type":["string","null"],"description":"The request field at fault, when the failure names one."},"code":{"type":"string","description":"This API's stable error code — the field to branch on.","enum":["invalid_request"]}},"required":["message","type","param","code"]}},"required":["error"]}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/json":{"schema":{"type":"object","description":"An OpenAI-compatible error envelope, so an OpenAI SDK raises a typed exception with a readable message. `code` carries this API's own stable error token, the same one the RFC 9457 problem documents on every other operation use — branch on it, not on `message`.","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable explanation."},"type":{"type":"string","description":"The OpenAI error family, derived from the HTTP status.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","api_error"]},"param":{"type":["string","null"],"description":"The request field at fault, when the failure names one."},"code":{"type":"string","description":"This API's stable error code — the field to branch on.","enum":["unauthorized"]}},"required":["message","type","param","code"]}},"required":["error"]}}}},"403":{"description":"`feature_not_enabled` — Feature not enabled; `insufficient_scope` — Insufficient scope; `pro_plan_required` — Pro plan required","content":{"application/json":{"schema":{"type":"object","description":"An OpenAI-compatible error envelope, so an OpenAI SDK raises a typed exception with a readable message. `code` carries this API's own stable error token, the same one the RFC 9457 problem documents on every other operation use — branch on it, not on `message`.","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable explanation."},"type":{"type":"string","description":"The OpenAI error family, derived from the HTTP status.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","api_error"]},"param":{"type":["string","null"],"description":"The request field at fault, when the failure names one."},"code":{"type":"string","description":"This API's stable error code — the field to branch on.","enum":["feature_not_enabled","insufficient_scope","pro_plan_required"]}},"required":["message","type","param","code"]}},"required":["error"]}}}},"404":{"description":"`not_found` — Not found","content":{"application/json":{"schema":{"type":"object","description":"An OpenAI-compatible error envelope, so an OpenAI SDK raises a typed exception with a readable message. `code` carries this API's own stable error token, the same one the RFC 9457 problem documents on every other operation use — branch on it, not on `message`.","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable explanation."},"type":{"type":"string","description":"The OpenAI error family, derived from the HTTP status.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","api_error"]},"param":{"type":["string","null"],"description":"The request field at fault, when the failure names one."},"code":{"type":"string","description":"This API's stable error code — the field to branch on.","enum":["not_found"]}},"required":["message","type","param","code"]}},"required":["error"]}}}},"429":{"description":"`key_ceiling_exceeded` — Key spend ceiling exceeded; `quota_exhausted` — Quota exhausted; `rate_limit_exceeded` — Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","description":"An OpenAI-compatible error envelope, so an OpenAI SDK raises a typed exception with a readable message. `code` carries this API's own stable error token, the same one the RFC 9457 problem documents on every other operation use — branch on it, not on `message`.","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable explanation."},"type":{"type":"string","description":"The OpenAI error family, derived from the HTTP status.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","api_error"]},"param":{"type":["string","null"],"description":"The request field at fault, when the failure names one."},"code":{"type":"string","description":"This API's stable error code — the field to branch on.","enum":["key_ceiling_exceeded","quota_exhausted","rate_limit_exceeded"]}},"required":["message","type","param","code"]}},"required":["error"]}}}},"500":{"description":"`internal_error` — Internal error","content":{"application/json":{"schema":{"type":"object","description":"An OpenAI-compatible error envelope, so an OpenAI SDK raises a typed exception with a readable message. `code` carries this API's own stable error token, the same one the RFC 9457 problem documents on every other operation use — branch on it, not on `message`.","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable explanation."},"type":{"type":"string","description":"The OpenAI error family, derived from the HTTP status.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","api_error"]},"param":{"type":["string","null"],"description":"The request field at fault, when the failure names one."},"code":{"type":"string","description":"This API's stable error code — the field to branch on.","enum":["internal_error"]}},"required":["message","type","param","code"]}},"required":["error"]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/json":{"schema":{"type":"object","description":"An OpenAI-compatible error envelope, so an OpenAI SDK raises a typed exception with a readable message. `code` carries this API's own stable error token, the same one the RFC 9457 problem documents on every other operation use — branch on it, not on `message`.","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable explanation."},"type":{"type":"string","description":"The OpenAI error family, derived from the HTTP status.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","api_error"]},"param":{"type":["string","null"],"description":"The request field at fault, when the failure names one."},"code":{"type":"string","description":"This API's stable error code — the field to branch on.","enum":["upstream_error"]}},"required":["message","type","param","code"]}},"required":["error"]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","description":"An OpenAI-compatible error envelope, so an OpenAI SDK raises a typed exception with a readable message. `code` carries this API's own stable error token, the same one the RFC 9457 problem documents on every other operation use — branch on it, not on `message`.","properties":{"error":{"type":"object","properties":{"message":{"type":"string","description":"A human-readable explanation."},"type":{"type":"string","description":"The OpenAI error family, derived from the HTTP status.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","api_error"]},"param":{"type":["string","null"],"description":"The request field at fault, when the failure names one."},"code":{"type":"string","description":"This API's stable error code — the field to branch on.","enum":["service_unavailable"]}},"required":["message","type","param","code"]}},"required":["error"]}}}}},"security":[{"apiKeyHeader":[]},{"bearerToken":[]},{"oauth2":["voice:generate"]}]}},"/v1/transcriptions":{"post":{"operationId":"createTranscription","summary":"Create a transcription","description":"Transcribe a base64-encoded audio clip to text. Returns the transcript along with the id of the capture it created, which stays retrievable through GET /v1/captures. Send an Idempotency-Key header to make a retried request safe.\n\nRequires the `voice:generate` scope.","tags":["Generation"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"An optional client-generated key that makes a retry safe. Replaying the same key with the same body returns the ORIGINAL response byte-for-byte instead of doing the work a second time; replaying it with a different body is refused with `idempotency_key_reused`.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"audio":{"type":"string","minLength":1,"maxLength":10485760,"format":"base64","contentEncoding":"base64","pattern":"^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$","description":"The audio clip, base64-encoded (up to 10 MiB of base64 text)."},"language":{"description":"ISO language code. Auto-detected when omitted.","type":"string","enum":["zh","en","ja","ko","de","fr","ru","pt","es","it","he","ar","da","el","fi","hi","ms","nl","no","pl","sv","sw","tr"]}},"required":["audio"],"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","headers":{"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"language":{"anyOf":[{"type":"string"},{"type":"null"}]},"duration_ms":{"anyOf":[{"type":"number"},{"type":"null"}]}},"required":["id","text","language","duration_ms"],"additionalProperties":false}}}},"400":{"description":"`invalid_request` — Invalid request","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["invalid_request"]}}}]}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["unauthorized"]}}}]}}}},"403":{"description":"`feature_not_enabled` — Feature not enabled; `insufficient_scope` — Insufficient scope; `pro_plan_required` — Pro plan required","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["feature_not_enabled","insufficient_scope","pro_plan_required"]}}}]}}}},"404":{"description":"`not_found` — Not found","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["not_found"]}}}]}}}},"409":{"description":"`idempotency_key_in_progress` — Idempotency key in progress","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["idempotency_key_in_progress"]}}}]}}}},"422":{"description":"`idempotency_key_reused` — Idempotency key reused","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["idempotency_key_reused"]}}}]}}}},"429":{"description":"`quota_exhausted` — Quota exhausted; `rate_limit_exceeded` — Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["quota_exhausted","rate_limit_exceeded"]}}}]}}}},"500":{"description":"`internal_error` — Internal error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["internal_error"]}}}]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["upstream_error"]}}}]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["service_unavailable"]}}}]}}}}},"security":[{"apiKeyHeader":[]},{"bearerToken":[]},{"oauth2":["voice:generate"]}]}},"/v1/voices/default":{"post":{"operationId":"ensureDefaultVoice","summary":"Ensure the default voice","description":"Make sure this account has VoiceLabs' default built-in voice (\"Heart\", the Kokoro preset `af_heart`), creating it only if it is missing, and return it. Safe to call on every start: a second call returns the same voice with `created: false` and creates nothing. Use it when GET /v1/voices comes back empty, then pass the returned id as `voice_id` (POST /v1/speech) or `voice` (POST /v1/audio/speech). It adds a built-in voice only; it never clones anyone's voice.\n\nRequires the `voice:generate` scope.","tags":["Generation"],"responses":{"200":{"description":"Success.","headers":{"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"created":{"type":"boolean"},"voice_type":{"type":"string"},"engine":{"anyOf":[{"type":"string"},{"type":"null"}]},"language":{"type":"string"}},"required":["id","name","created","voice_type","engine","language"],"additionalProperties":false}}}},"400":{"description":"`invalid_request` — Invalid request","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["invalid_request"]}}}]}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["unauthorized"]}}}]}}}},"403":{"description":"`feature_not_enabled` — Feature not enabled; `insufficient_scope` — Insufficient scope; `pro_plan_required` — Pro plan required","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["feature_not_enabled","insufficient_scope","pro_plan_required"]}}}]}}}},"404":{"description":"`not_found` — Not found","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["not_found"]}}}]}}}},"429":{"description":"`quota_exhausted` — Quota exhausted; `rate_limit_exceeded` — Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["quota_exhausted","rate_limit_exceeded"]}}}]}}}},"500":{"description":"`internal_error` — Internal error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["internal_error"]}}}]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["upstream_error"]}}}]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["service_unavailable"]}}}]}}}}},"security":[{"apiKeyHeader":[]},{"bearerToken":[]},{"oauth2":["voice:generate"]}]}},"/v1/voices/clone":{"post":{"operationId":"cloneVoice","summary":"Clone a voice","description":"Create a cloned voice on this account from a recording: a name, the language, the exact text spoken, and the audio either inline as base64 (`audio`) or as an https:// URL (`audio_url`). The recording must be 2–30 seconds of clear speech. Returns the new profile's id, name, voice type and engine; use the id as `voice_id` on POST /v1/speech. A duplicate name is refused, so send an Idempotency-Key header to make a retry safe. On the free plan the account keeps one saved cloned voice; Pro is unlimited. Only clone a voice whose owner has agreed to it.\n\nRequires the `voice:clone` scope.","tags":["Voice cloning"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"An optional client-generated key that makes a retry safe. Replaying the same key with the same body returns the ORIGINAL response byte-for-byte instead of doing the work a second time; replaying it with a different body is refused with `idempotency_key_reused`.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"The name for the new cloned voice. Must not already exist on the account; a duplicate is refused with `invalid_request`."},"language":{"description":"ISO language code of the recording and the voice. Defaults to \"en\".","type":"string","enum":["zh","en","ja","ko","de","fr","ru","pt","es","it","he","ar","da","el","fi","hi","ms","nl","no","pl","sv","sw","tr"]},"reference_text":{"type":"string","minLength":1,"maxLength":1000,"description":"The exact words spoken in the recording (up to 1000 characters). The engine aligns the clone against this transcript."},"audio":{"description":"The recording inline, base64-encoded (WAV, or mp3/m4a/ogg/flac/aac/webm/opus; up to 10 MiB decoded). 2–30 seconds of clear speech. Give exactly one of `audio` or `audio_url`.","type":"string","minLength":1,"maxLength":13981016},"audio_url":{"description":"An https:// URL the recording can be downloaded from — a public host, no redirects, up to 10 MiB. Give exactly one of `audio` or `audio_url`.","type":"string","minLength":1,"maxLength":2048}},"required":["name","reference_text"],"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","headers":{"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"voice_type":{"type":"string"},"engine":{"anyOf":[{"type":"string"},{"type":"null"}]},"language":{"type":"string"},"sample_id":{"type":"string"}},"required":["id","name","voice_type","engine","language","sample_id"],"additionalProperties":false}}}},"400":{"description":"`invalid_request` — Invalid request","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["invalid_request"]}}}]}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["unauthorized"]}}}]}}}},"403":{"description":"`feature_not_enabled` — Feature not enabled; `insufficient_scope` — Insufficient scope; `pro_plan_required` — Pro plan required","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["feature_not_enabled","insufficient_scope","pro_plan_required"]}}}]}}}},"404":{"description":"`not_found` — Not found","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["not_found"]}}}]}}}},"409":{"description":"`idempotency_key_in_progress` — Idempotency key in progress","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["idempotency_key_in_progress"]}}}]}}}},"422":{"description":"`idempotency_key_reused` — Idempotency key reused","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["idempotency_key_reused"]}}}]}}}},"429":{"description":"`rate_limit_exceeded` — Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"RateLimit":{"description":"This credential's current state, as an RFC 8941 structured field: `\"default\";r=<requests remaining>;t=<seconds until reset>`. Absent when the credential has no rate limit configured.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"The standing policy: `\"default\";q=<requests per window>;w=<window seconds>`. Defaults to 600 requests per hour, counting POST, PUT, PATCH and DELETE only: GET reads never draw on it and are bounded separately by a short \"read-burst\" burst policy (300 per 60 seconds).","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["rate_limit_exceeded"]}}}]}}}},"500":{"description":"`internal_error` — Internal error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["internal_error"]}}}]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["upstream_error"]}}}]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["service_unavailable"]}}}]}}}}},"security":[{"apiKeyHeader":[]},{"bearerToken":[]},{"oauth2":["voice:clone"]}]}},"/v1/audio/{generationId}":{"get":{"operationId":"getGenerationAudio","summary":"Download generated audio","description":"Download the audio of a completed generation. This URL is not called directly — it is the `audio_url` returned by GET /v1/generations/{generationId}, already signed and ready to follow. The signature grants access to this one generation for about 15 minutes, so no API key is sent (and none is accepted): the link can be handed straight to a media player, a download, or an <audio> tag that cannot attach an Authorization header. Links expire — poll the generation again to mint a fresh one rather than storing this URL.\n\nAuthenticated by the signature in the URL, not by an API key.","tags":["Voices & history"],"parameters":[{"name":"generationId","in":"path","required":true,"description":"The generation whose audio to download.","schema":{"type":"string","minLength":1}},{"name":"sig","in":"query","required":true,"description":"The capability signature. Supplied by `audio_url`; do not construct it.","schema":{"type":"string"}}],"responses":{"200":{"description":"The generated audio, streamed through from the generation service.","content":{"audio/*":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"`unauthorized` — Unauthorized","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["unauthorized"]}}}]}}}},"404":{"description":"`not_found` — Not found","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["not_found"]}}}]}}}},"502":{"description":"`upstream_error` — Upstream error","content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["upstream_error"]}}}]}}}},"503":{"description":"`service_unavailable` — Service unavailable","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","properties":{"code":{"enum":["service_unavailable"]}}}]}}}}},"security":[]}}},"components":{"securitySchemes":{"apiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key","description":"A VoiceLabs API key, sent in the `x-api-key` header. Keys begin with `vl_sandbox_`. This is the simplest way to authenticate and is what most callers should use.\n\nCreate one in the developer console at https://voicelabs.now/connections, on the **API keys** tab, choosing the scopes it carries. A key does exactly what its scopes allow: one without `voice:generate` is answered `403 insufficient_scope` on the generation operations and cannot spend the account's allowance. The secret is shown once, at creation; revoking a key takes effect on its very next request."},"bearerToken":{"type":"http","scheme":"bearer","description":"The same API key, sent as `Authorization: Bearer <key>` for clients whose HTTP layer only supports bearer auth. An OAuth 2.1 access token is also accepted here — the two are told apart by shape, never by guessing."},"oauth2":{"type":"oauth2","description":"OAuth 2.1 (authorization code with PKCE and dynamic client registration) — the same authorization server the VoiceLabs MCP server uses. Use this when an application acts on behalf of ITS OWN users, so each of them consents and can revoke independently. Use an API key when the application acts on behalf of the account that issued the key.","flows":{"authorizationCode":{"authorizationUrl":"https://app.voicelabs.now/api/auth/oauth2/authorize","tokenUrl":"https://app.voicelabs.now/api/auth/oauth2/token","refreshUrl":"https://app.voicelabs.now/api/auth/oauth2/token","scopes":{"voice:clone":"Create a cloned voice from a recording you provide","voice:generate":"Generate speech, transcribe audio, and add built-in (preset) voices to your account on your behalf. Cannot clone a real person's voice.","voice:read":"Read your voices, profiles, and past generations."}}}}},"schemas":{"Problem":{"type":"object","description":"An RFC 9457 problem document. `code` is an extension member and is the field to branch on: it is a stable enum token, whereas `detail` is human-readable prose that may be reworded, and `status` alone cannot distinguish two problems that share it.","properties":{"type":{"type":"string","format":"uri","description":"A URI identifying the problem type, under https://voicelabs.now/errors/."},"title":{"type":"string","description":"A short, stable summary of the problem type."},"status":{"type":"integer","description":"The HTTP status code, repeated in the body."},"detail":{"type":"string","description":"A human-readable explanation of this occurrence."},"instance":{"type":"string","description":"The request path this problem occurred on."},"code":{"type":"string","description":"The stable machine-readable error code. Branch on this.","enum":["feature_not_enabled","idempotency_key_in_progress","idempotency_key_reused","insufficient_scope","internal_error","invalid_request","key_ceiling_exceeded","method_not_allowed","not_found","pro_plan_required","quota_exhausted","rate_limit_exceeded","service_unavailable","unauthorized","upstream_error"]},"required_scope":{"type":"string","description":"On `insufficient_scope`: the scope the credential was missing."},"settings_url":{"type":"string","format":"uri","description":"On quota and entitlement problems: where the account owner resolves it. Present only on problems a human can actually fix from a settings page."}},"required":["type","title","status","detail","code"]}}},"externalDocs":{"description":"VoiceLabs developer documentation","url":"https://voicelabs.now/developers"}}