{"openapi":"3.1.0","info":{"title":"SiangMuan API","description":"\n<img src=\"/branding/siangmuan-logo.png\" alt=\"SiangMuan — Lao text to speech\" width=\"280\">\n\n**SiangMuan (ສຽງມ່ວນ) — Lao text to speech with voice cloning.** Send Lao text and get natural speech back as\nWAV, MP3, OGG or FLAC.\nNumbers, dates, times, prices and abbreviations are read out correctly.\n\n### Getting started\n1. Create a free account in the [web app](/app#/register) and open **API Keys**.\n2. Create a key, then click **Authorize** (top right) and paste it into **APIKey**.\n3. Open `POST /tts` → **Try it out** → **Execute**.\n\n### Authentication\nSend your key with every request: `Authorization: Bearer sd_…` (or `X-API-Key: sd_…`).\nIf you are logged in to the web app in this browser, *Try it out* works without a key.\n**Keep keys on your server** — never in public web pages or mobile apps.\n\n### Limits\nLimits depend on your plan (characters per month, per request, requests per minute) — see `GET /plans`.\nSpeech responses include `X-Quota-Remaining`: the characters you have left this month.\n\n### Errors\nErrors return `{\"detail\": \"…\"}`:\n`401` missing or invalid key · `403` not included in your plan / not allowed ·\n`404` unknown voice · `409` already exists · `413` text too long for your plan ·\n`422` invalid input · `429` rate limit or monthly quota reached.\n\n📘 Step-by-step guide with code in cURL, Python, Node.js and PHP: **[API documentation](/app#/api)**\n","version":"1.1.0"},"paths":{"/health":{"get":{"tags":["System"],"summary":"Server status","description":"Is the server up and the model loaded? Public — use it for monitoring.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/voices":{"get":{"tags":["Voices"],"summary":"List voices","description":"Built-in voices plus the voices you cloned. Use `name` as `voice` in speech requests.","operationId":"voices_voices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceList"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"APIKey":[]},{"XAPIKey":[]}]},"post":{"tags":["Voices"],"summary":"Clone a voice","description":"Create a voice from a recording of **one speaker**: 15–25 s of clear speech is best (minimum 6 s,\nonly the first 30 s are used). **Each account can have one cloned voice**: to change it, send a new\nrecording with the same `name` and `overwrite=true`, or delete it first. Send as `multipart/form-data`. Silences are removed and the level is\nnormalised; the voice is usable right away as `\"voice\": \"<name>\"`. Check `warnings` for quality hints.\nOnly clone your own voice or a voice you have permission to use.","operationId":"voice_create_voices_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_voice_create_voices_post"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceCreated"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKey":[]},{"XAPIKey":[]}]}},"/voices/{name}":{"delete":{"tags":["Voices"],"summary":"Delete a cloned voice","description":"Delete one of your cloned voices. Administrators can delete any voice; a built-in voice is moved to\n`voices/.trash/` (restorable). The last remaining voice cannot be deleted.","operationId":"voice_delete_voices__name__delete","security":[{"APIKey":[]},{"XAPIKey":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/voices/{name}/reference":{"get":{"tags":["Voices"],"summary":"Download a voice's reference recording","description":"The cleaned reference recording the voice is cloned from.","operationId":"voice_reference_voices__name__reference_get","security":[{"APIKey":[]},{"XAPIKey":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Cleaned reference recording (WAV)","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tts":{"post":{"tags":["Speech"],"summary":"Text to speech (audio file)","description":"Convert Lao text to speech and return **the audio file** directly — best for saving or streaming\nto a player. Long texts take a while (about 1–2 s of work per second of audio): use a client\ntimeout of at least 120–300 s. Behind a proxy such as Cloudflare, requests that take longer than\n~100 s fail with HTTP 524; use `POST /tts/jobs` for long texts.","operationId":"tts_tts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TTSRequest"},"examples":{"simple":{"summary":"Simple (WAV)","value":{"text":"ສະບາຍດີ, ຍິນດີຕ້ອນຮັບ"}},"mp3":{"summary":"MP3, numbers and a bit faster","value":{"text":"ລາຄາ 15,000 ກີບ. ໂທຫາເບີ 020 5555 1234.","format":"mp3","speed":1.1}},"voice":{"summary":"With your cloned voice","value":{"text":"ສະບາຍດີ","voice":"somphone"}},"verify":{"summary":"Verified pronunciation (plans with verify)","value":{"text":"ລາວມີ 17 ແຂວງ ແລະ 1 ນະຄອນຫຼວງ.","verify":true,"candidates":3}}}}},"required":true},"responses":{"200":{"description":"The audio file (`Content-Type` matches the requested `format`)","headers":{"X-Audio-Duration":{"description":"Seconds of audio (before any speed change)","schema":{"type":"number"}},"X-Processing-Time":{"description":"Seconds the server worked on it","schema":{"type":"number"}},"X-Quota-Remaining":{"description":"Characters left this month (accounts only)","schema":{"type":"integer"}}},"content":{"audio/wav":{"schema":{"type":"string","format":"binary"}},"audio/mpeg":{"schema":{"type":"string","format":"binary"}},"audio/ogg":{"schema":{"type":"string","format":"binary"}},"audio/flac":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Text longer than your plan allows per request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit (requests per minute) or monthly character quota reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKey":[]},{"XAPIKey":[]}]}},"/tts/json":{"post":{"tags":["Speech"],"summary":"Text to speech (JSON with base64 audio)","description":"Same as `POST /tts`, but returns JSON: the audio as base64, the exact text that was spoken\n(useful for subtitles) and per-sentence details.","operationId":"tts_json_tts_json_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TTSRequest"},"examples":{"simple":{"summary":"Simple (WAV)","value":{"text":"ສະບາຍດີ, ຍິນດີຕ້ອນຮັບ"}},"mp3":{"summary":"MP3, numbers and a bit faster","value":{"text":"ລາຄາ 15,000 ກີບ. ໂທຫາເບີ 020 5555 1234.","format":"mp3","speed":1.1}},"voice":{"summary":"With your cloned voice","value":{"text":"ສະບາຍດີ","voice":"somphone"}},"verify":{"summary":"Verified pronunciation (plans with verify)","value":{"text":"ລາວມີ 17 ແຂວງ ແລະ 1 ນະຄອນຫຼວງ.","verify":true,"candidates":3}}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TTSJsonResponse"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Text longer than your plan allows per request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit (requests per minute) or monthly character quota reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKey":[]},{"XAPIKey":[]}]}},"/tts/jobs":{"post":{"tags":["Speech"],"summary":"Start a speech job (for long texts)","description":"Start generating speech in the background and return a `job_id` immediately. Poll\n`GET /tts/jobs/{job_id}` every 1–2 s; when `status` is `done`, download the audio from `audio_url`.\nUse this for long texts: it never runs into proxy or client timeouts. Results are kept for 15 minutes.","operationId":"tts_job_create_tts_jobs_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TTSRequest"},"examples":{"simple":{"summary":"Simple (WAV)","value":{"text":"ສະບາຍດີ, ຍິນດີຕ້ອນຮັບ"}},"mp3":{"summary":"MP3, numbers and a bit faster","value":{"text":"ລາຄາ 15,000 ກີບ. ໂທຫາເບີ 020 5555 1234.","format":"mp3","speed":1.1}},"voice":{"summary":"With your cloned voice","value":{"text":"ສະບາຍດີ","voice":"somphone"}},"verify":{"summary":"Verified pronunciation (plans with verify)","value":{"text":"ລາວມີ 17 ແຂວງ ແລະ 1 ນະຄອນຫຼວງ.","verify":true,"candidates":3}}}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TTSJob"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Text longer than your plan allows per request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit (requests per minute) or monthly character quota reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKey":[]},{"XAPIKey":[]}]}},"/tts/jobs/{job_id}":{"get":{"tags":["Speech"],"summary":"Speech job status","description":"Progress of a job (`done` of `total` sentences). When `status` is `done` the response also contains\nthe spoken text and per-sentence details, and `audio_url`. On `error`, `error` says why.","operationId":"tts_job_status_tts_jobs__job_id__get","security":[{"APIKey":[]},{"XAPIKey":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TTSJob"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tts/jobs/{job_id}/audio":{"get":{"tags":["Speech"],"summary":"Download a job's audio","description":"The finished audio file of a job.","operationId":"tts_job_audio_tts_jobs__job_id__audio_get","security":[{"APIKey":[]},{"XAPIKey":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"The audio file (`Content-Type` matches the requested `format`)","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}},"audio/mpeg":{"schema":{"type":"string","format":"binary"}},"audio/ogg":{"schema":{"type":"string","format":"binary"}},"audio/flac":{"schema":{"type":"string","format":"binary"}}},"headers":{"X-Audio-Duration":{"description":"Seconds of audio (before any speed change)","schema":{"type":"number"}},"X-Processing-Time":{"description":"Seconds the server worked on it","schema":{"type":"number"}},"X-Quota-Remaining":{"description":"Characters left this month (accounts only)","schema":{"type":"integer"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/normalize":{"post":{"tags":["Speech"],"summary":"Preview the spoken text","description":"Show exactly what will be spoken (numbers, dates and abbreviations written out as Lao words) and how\nthe text is split into sentences. Instant, no audio, does not count towards your quota.","operationId":"normalize_text_normalize_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NormalizeResponse"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKey":[]},{"XAPIKey":[]}]}},"/lexicon":{"get":{"tags":["Pronunciation"],"summary":"List pronunciations","description":"Custom pronunciations as `{\"word\": \"spoken as\"}`, applied to every request.","operationId":"lexicon_list_lexicon_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Lexicon List Lexicon Get"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"APIKey":[]},{"XAPIKey":[]}]},"put":{"tags":["Pronunciation"],"summary":"Add or change a pronunciation","description":"Add or replace how a word is spoken (e.g. a brand or person name). Admin accounts only.","operationId":"lexicon_put_lexicon_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LexiconEntry"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LexiconUpdated"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKey":[]},{"XAPIKey":[]}]}},"/lexicon/{word}":{"delete":{"tags":["Pronunciation"],"summary":"Remove a pronunciation","description":"Admin accounts only.","operationId":"lexicon_delete_lexicon__word__delete","security":[{"APIKey":[]},{"XAPIKey":[]}],"parameters":[{"name":"word","in":"path","required":true,"schema":{"type":"string","title":"Word"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/register":{"post":{"tags":["Account (web app)"],"summary":"Create an account","description":"Create an account on the **Free** plan and log in (sets the `laotts_session` cookie).","operationId":"register_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit (requests per minute) or monthly character quota reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/login":{"post":{"tags":["Account (web app)"],"summary":"Log in","description":"Log in with email and password (sets the `laotts_session` cookie, valid 30 days).\nAfter 10 wrong passwords for an email, logins are blocked for 15 minutes.","operationId":"login_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit (requests per minute) or monthly character quota reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/logout":{"post":{"tags":["Account (web app)"],"summary":"Log out","operationId":"logout_auth_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}}}}},"/auth/change-password":{"post":{"tags":["Account (web app)"],"summary":"Change your password","description":"Change your password. Your other logged-in sessions are logged out.","operationId":"change_password_auth_change_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit (requests per minute) or monthly character quota reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/me":{"get":{"tags":["Account (web app)"],"summary":"Current user","description":"The logged-in user, their plan and this month's usage. Requires a web login (cookie).","operationId":"me_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/account/keys":{"get":{"tags":["Account (web app)"],"summary":"List API keys","description":"Your active keys (only the prefix is shown). Requires a web login (cookie).","operationId":"list_keys_account_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyList"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Account (web app)"],"summary":"Create an API key","description":"Create an API key for a third-party app. The full key is returned **only once** — store it safely.\nRequires a web login (API keys cannot create keys).","operationId":"create_key_account_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyCreated"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/account/keys/{key_id}":{"delete":{"tags":["Account (web app)"],"summary":"Revoke an API key","description":"Revoke a key immediately; apps using it get `401` from then on.","operationId":"revoke_key_account_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/account/usage":{"get":{"tags":["Account (web app)"],"summary":"Usage history","description":"Daily usage for the last 30 days. Requires a web login (cookie).","operationId":"usage_account_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/plans":{"get":{"tags":["System"],"summary":"Available plans","description":"Public plans and their limits. No API key needed.","operationId":"plans_plans_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanList"}}}}}}},"/admin/stats":{"get":{"tags":["Admin"],"summary":"Overview numbers","operationId":"stats_admin_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/admin/users":{"get":{"tags":["Admin"],"summary":"List users","description":"Newest first. Includes this month's usage per user.","operationId":"list_users_admin_users_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"Search name or email","default":"","title":"Q"},"description":"Search name or email"},{"name":"plan","in":"query","required":false,"schema":{"type":"string","maxLength":32,"description":"Filter by package code","default":"","title":"Plan"},"description":"Filter by package code"},{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(|active|disabled|admin)$","default":"","title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPage"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{user_id}":{"patch":{"tags":["Admin"],"summary":"Update a user","description":"Change name, email, package (and until when), active status, admin rights or the top-up balance.\nDisabled users cannot log in or use their keys.","operationId":"update_user_admin_users__user_id__patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Admin"],"summary":"Delete a user","description":"Delete an account: sessions, API keys, usage and cloned voices are removed and the email is freed.\nPayments are kept for accounting (shown as a deleted user).","operationId":"delete_user_admin_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{user_id}/keys":{"get":{"tags":["Admin"],"summary":"A user's API keys","operationId":"user_keys_admin_users__user_id__keys_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserKey"},"title":"Response User Keys Admin Users  User Id  Keys Get"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{user_id}/keys/{key_id}":{"delete":{"tags":["Admin"],"summary":"Revoke a user's API key","operationId":"revoke_user_key_admin_users__user_id__keys__key_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{user_id}/reset-password":{"post":{"tags":["Admin"],"summary":"Reset a user's password","description":"Set a new password (or generate a temporary one) and log the user out everywhere.\nThe password is returned once — pass it to the user securely; they can change it under Plan & Usage.","operationId":"reset_password_admin_users__user_id__reset_password_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetResult"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/plans":{"get":{"tags":["Admin"],"summary":"List all packages","description":"All packages, including private and inactive ones, with how many users are on each.","operationId":"list_plans_admin_plans_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AdminPlan"},"type":"array","title":"Response List Plans Admin Plans Get"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Admin"],"summary":"Create a package","description":"Create a package with custom limits — e.g. a private package with a custom character (token) quota\nfor one customer (`is_public: false`).","operationId":"create_plan_admin_plans_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPlan"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/plans/{plan_id}":{"patch":{"tags":["Admin"],"summary":"Update a package","description":"Change a package's limits. Applies immediately to every user on it.","operationId":"update_plan_admin_plans__plan_id__patch","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"integer","title":"Plan Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPlan"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Admin"],"summary":"Delete a package","description":"Only packages with no users can be deleted (move the users first, or deactivate the package).","operationId":"delete_plan_admin_plans__plan_id__delete","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"integer","title":"Plan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/packages":{"get":{"tags":["Billing"],"summary":"Top-up packages","description":"Character top-ups you can buy with LaoQR. Public.","operationId":"topup_packages_billing_packages_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupList"}}}}}}},"/billing/orders":{"get":{"tags":["Billing"],"summary":"Your orders","operationId":"my_orders_billing_orders_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/OrderInfo"},"type":"array","title":"Response My Orders Billing Orders Get"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Billing"],"summary":"Buy a top-up (create LaoQR)","description":"Create an order and a LaoQR code to pay it. Scan the QR with any Lao banking app; the characters\nare added to your balance once the payment is confirmed.","operationId":"create_order_billing_orders_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderInfo"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit (requests per minute) or monthly character quota reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/orders/{order_id}":{"get":{"tags":["Billing"],"summary":"Order status","description":"Poll this while the QR is shown; `status` becomes `paid` when the payment is confirmed.","operationId":"my_order_billing_orders__order_id__get","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"integer","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderInfo"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/orders/{order_id}/cancel":{"post":{"tags":["Billing"],"summary":"Cancel an unpaid order","operationId":"cancel_order_billing_orders__order_id__cancel_post","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"integer","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/orders":{"get":{"tags":["Admin"],"summary":"List payment orders","operationId":"admin_orders_admin_orders_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(|pending|paid|expired|cancelled)$","default":"","title":"Status"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"Email or transaction ID","default":"","title":"Q"},"description":"Email or transaction ID"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminOrderPage"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/orders/{order_id}/mark-paid":{"post":{"tags":["Admin"],"summary":"Confirm a payment manually","description":"After checking the money arrived in the bank account, mark the order paid: its characters are added\nto the user's balance. Safe to click twice (it only applies once).","operationId":"admin_mark_paid_admin_orders__order_id__mark_paid_post","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"integer","title":"Order Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkPaidBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{user_id}/payments":{"post":{"tags":["Admin"],"summary":"Record an offline payment","description":"For customers who paid outside the app (cash, bank transfer, a QR outside the app): records a paid order\nand adds its characters to the user's balance immediately. It appears on the Payments page and in the\ncustomer's order history.","operationId":"admin_record_payment_admin_users__user_id__payments_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfflinePaymentBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminOrder"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/payment-events":{"get":{"tags":["Admin"],"summary":"Recent webhook calls","description":"What the bank sent to the webhook — use this to implement automatic confirmation.","operationId":"admin_payment_events_admin_payment_events_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"},"title":"Response Admin Payment Events Admin Payment Events Get"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/topups":{"get":{"tags":["Admin"],"summary":"List top-up packages","operationId":"admin_topups_admin_topups_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TopupAdmin"},"type":"array","title":"Response Admin Topups Admin Topups Get"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Admin"],"summary":"Create a top-up package","operationId":"admin_create_topup_admin_topups_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupAdmin"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/topups/{topup_id}":{"patch":{"tags":["Admin"],"summary":"Update a top-up package","description":"New prices apply to new orders; existing orders keep the price they were created with.","operationId":"admin_update_topup_admin_topups__topup_id__patch","parameters":[{"name":"topup_id","in":"path","required":true,"schema":{"type":"integer","title":"Topup Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupAdmin"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Admin"],"summary":"Delete a top-up package","description":"Packages that were already bought cannot be deleted (deactivate them instead) — orders keep a record.","operationId":"admin_delete_topup_admin_topups__topup_id__delete","parameters":[{"name":"topup_id","in":"path","required":true,"schema":{"type":"integer","title":"Topup Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing, invalid or revoked API key (or not logged in)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Not allowed for your account or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AdminOrder":{"properties":{"id":{"type":"integer","title":"Id"},"package":{"type":"string","title":"Package"},"characters":{"type":"integer","title":"Characters"},"amount_lak":{"type":"integer","title":"Amount Lak"},"status":{"type":"string","title":"Status","description":"pending · paid · expired · cancelled"},"transaction_id":{"type":"string","title":"Transaction Id"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"paid_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paid At"},"channel":{"type":"string","title":"Channel","description":"laoqr (paid in the app) · cash · bank · qr · other (recorded by an admin)","default":"laoqr"},"qr_string":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Qr String"},"qr_image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Qr Image","description":"QR code image as a data: URL"},"user_id":{"type":"integer","title":"User Id"},"user_email":{"type":"string","title":"User Email"},"user_name":{"type":"string","title":"User Name"},"confirmed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmed By"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"webhook_calls":{"type":"integer","title":"Webhook Calls"}},"type":"object","required":["id","package","characters","amount_lak","status","transaction_id","created_at","expires_at","paid_at","user_id","user_email","user_name","confirmed_by","note","webhook_calls"],"title":"AdminOrder"},"AdminOrderPage":{"properties":{"orders":{"items":{"$ref":"#/components/schemas/AdminOrder"},"type":"array","title":"Orders"},"total":{"type":"integer","title":"Total"},"pending":{"type":"integer","title":"Pending"},"paid_amount_lak":{"type":"integer","title":"Paid Amount Lak","description":"Total of paid orders (all time)"}},"type":"object","required":["orders","total","pending","paid_amount_lak"],"title":"AdminOrderPage"},"AdminPlan":{"properties":{"code":{"type":"string","title":"Code","examples":["free"]},"name":{"type":"string","title":"Name","examples":["Free"]},"price_usd_month":{"type":"number","title":"Price Usd Month","examples":[0]},"monthly_chars":{"type":"integer","title":"Monthly Chars","description":"Characters per calendar month (UTC)","examples":[20000]},"max_chars_per_request":{"type":"integer","title":"Max Chars Per Request","examples":[1000]},"requests_per_minute":{"type":"integer","title":"Requests Per Minute","examples":[10]},"max_api_keys":{"type":"integer","title":"Max Api Keys","examples":[3]},"max_custom_voices":{"type":"integer","title":"Max Custom Voices","description":"1 = can clone one voice, 0 = no voice cloning","examples":[1]},"allow_verify":{"type":"boolean","title":"Allow Verify","description":"Whether `verify: true` may be used","examples":[false]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","examples":["Get started with natural Lao speech."]},"id":{"type":"integer","title":"Id"},"is_active":{"type":"boolean","title":"Is Active"},"is_public":{"type":"boolean","title":"Is Public"},"users":{"type":"integer","title":"Users","description":"Accounts on this package"}},"type":"object","required":["code","name","price_usd_month","monthly_chars","max_chars_per_request","requests_per_minute","max_api_keys","max_custom_voices","allow_verify","id","is_active","is_public","users"],"title":"AdminPlan"},"AdminUser":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"type":"string","title":"Email"},"full_name":{"type":"string","title":"Full Name"},"plan":{"type":"string","title":"Plan","description":"Package code"},"plan_name":{"type":"string","title":"Plan Name"},"plan_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Expires At","description":"Package valid until (UTC); afterwards Free"},"is_active":{"type":"boolean","title":"Is Active"},"is_admin":{"type":"boolean","title":"Is Admin"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_login_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Login At"},"month_characters":{"type":"integer","title":"Month Characters"},"month_requests":{"type":"integer","title":"Month Requests"},"monthly_chars":{"type":"integer","title":"Monthly Chars","description":"The package's monthly character quota"},"credit_chars":{"type":"integer","title":"Credit Chars","description":"Bought top-up characters left"},"api_keys":{"type":"integer","title":"Api Keys"}},"type":"object","required":["id","email","full_name","plan","plan_name","is_active","is_admin","created_at","last_login_at","month_characters","month_requests","monthly_chars","credit_chars","api_keys"],"title":"AdminUser"},"Body_voice_create_voices_post":{"properties":{"audio":{"type":"string","contentMediaType":"application/octet-stream","title":"Audio","description":"Recording of the speaker (any common format)"},"name":{"type":"string","title":"Name","description":"Voice ID used in requests, e.g. 'somphone'"},"label":{"type":"string","title":"Label","description":"Display name (Lao allowed)","default":""},"overwrite":{"type":"boolean","title":"Overwrite","description":"Replace an existing web-created voice","default":false}},"type":"object","required":["audio","name"],"title":"Body_voice_create_voices_post"},"ChangePasswordBody":{"properties":{"current_password":{"type":"string","maxLength":128,"title":"Current Password"},"new_password":{"type":"string","maxLength":128,"minLength":8,"title":"New Password","description":"At least 8 characters"}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordBody"},"CheckoutBody":{"properties":{"package":{"type":"string","title":"Package","description":"Top-up package code","examples":["standard"]}},"type":"object","required":["package"],"title":"CheckoutBody"},"Chunk":{"properties":{"text":{"type":"string","title":"Text","description":"One sentence/phrase, as spoken"},"seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Seconds","description":"Length of this part of the audio"},"tries":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tries","description":"Takes generated for this part"},"cer":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cer","description":"Character error rate from the verifier (with `verify`)"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning","description":"Present if the part may still sound wrong"}},"type":"object","required":["text"],"title":"Chunk"},"DayUsage":{"properties":{"date":{"type":"string","title":"Date","examples":["2026-10-08"]},"characters":{"type":"integer","title":"Characters"},"requests":{"type":"integer","title":"Requests"},"audio_seconds":{"type":"number","title":"Audio Seconds"}},"type":"object","required":["date","characters","requests","audio_seconds"],"title":"DayUsage"},"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","examples":["login or API key required"]}},"type":"object","required":["detail"],"title":"ErrorResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status","examples":["ok"]},"model_loaded":{"type":"boolean","title":"Model Loaded"},"device":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device","examples":["mps"]},"max_chars":{"type":"integer","title":"Max Chars","description":"Server-wide maximum characters per request","examples":[5000]},"auth_enabled":{"type":"boolean","title":"Auth Enabled","description":"Whether an API key / login is required"}},"type":"object","required":["status","model_loaded","device","max_chars","auth_enabled"],"title":"HealthResponse"},"KeyBody":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","default":"Default","examples":["My website"]}},"type":"object","title":"KeyBody"},"KeyCreated":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name","examples":["My website"]},"prefix":{"type":"string","title":"Prefix","description":"First characters of the key, to recognise it","examples":["sd_mYGBgGw1t"]},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"key":{"type":"string","title":"Key","description":"The full API key. Shown only in this response — store it safely.","examples":["sd_mYGBgGw1tVUODWr-7xsxxzXa4yNB8DCSmo6HPX4XUAk"]}},"type":"object","required":["id","name","prefix","created_at","last_used_at","key"],"title":"KeyCreated"},"KeyInfo":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name","examples":["My website"]},"prefix":{"type":"string","title":"Prefix","description":"First characters of the key, to recognise it","examples":["sd_mYGBgGw1t"]},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"}},"type":"object","required":["id","name","prefix","created_at","last_used_at"],"title":"KeyInfo"},"KeyList":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/KeyInfo"},"type":"array","title":"Keys"},"max_api_keys":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Api Keys","description":"null = unlimited (administrators)"}},"type":"object","required":["keys","max_api_keys"],"title":"KeyList"},"LexiconEntry":{"properties":{"word":{"type":"string","minLength":1,"title":"Word","description":"The word as written in texts","examples":["BCEL"]},"pronunciation":{"type":"string","minLength":1,"title":"Pronunciation","description":"Lao respelling to speak instead","examples":["ບີຊີອີແອວ"]}},"type":"object","required":["word","pronunciation"],"title":"LexiconEntry"},"LexiconUpdated":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true},"entries":{"type":"integer","title":"Entries"}},"type":"object","required":["entries"],"title":"LexiconUpdated"},"LoginBody":{"properties":{"email":{"type":"string","maxLength":254,"title":"Email","examples":["somphone@example.la"]},"password":{"type":"string","maxLength":128,"title":"Password","examples":["my-secret-pass"]}},"type":"object","required":["email","password"],"title":"LoginBody"},"MarkPaidBody":{"properties":{"note":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Note","examples":["Checked in the BCEL One app, ref 123456"]}},"type":"object","title":"MarkPaidBody"},"MeResponse":{"properties":{"user":{"$ref":"#/components/schemas/UserInfo"},"plan":{"$ref":"#/components/schemas/PlanInfo"},"usage":{"$ref":"#/components/schemas/UsageSummary"}},"type":"object","required":["user","plan","usage"],"title":"MeResponse"},"NormalizeResponse":{"properties":{"normalized_text":{"type":"string","title":"Normalized Text","examples":["ລາວມີ ສິບເຈັດ ແຂວງ ແລະ ໜຶ່ງ ນະຄອນຫຼວງ."]},"chunks":{"items":{"type":"string"},"type":"array","title":"Chunks"}},"type":"object","required":["normalized_text","chunks"],"title":"NormalizeResponse"},"OfflinePaymentBody":{"properties":{"topup":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topup","description":"Top-up package code; omit for a custom amount","examples":["standard"]},"characters":{"type":"integer","maximum":10000000000.0,"minimum":0.0,"title":"Characters","description":"Characters to add to the balance (0 is allowed)"},"amount_lak":{"type":"integer","maximum":10000000000.0,"minimum":0.0,"title":"Amount Lak","description":"Amount received, in LAK"},"method":{"type":"string","enum":["cash","bank","qr","other"],"title":"Method","description":"How the customer paid","default":"bank"},"reference":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"title":"Reference","description":"Bank or receipt reference"},"note":{"anyOf":[{"type":"string","maxLength":150},{"type":"null"}],"title":"Note"}},"type":"object","required":["characters","amount_lak"],"title":"OfflinePaymentBody"},"Ok":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true}},"type":"object","title":"Ok"},"OrderInfo":{"properties":{"id":{"type":"integer","title":"Id"},"package":{"type":"string","title":"Package"},"characters":{"type":"integer","title":"Characters"},"amount_lak":{"type":"integer","title":"Amount Lak"},"status":{"type":"string","title":"Status","description":"pending · paid · expired · cancelled"},"transaction_id":{"type":"string","title":"Transaction Id"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"paid_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paid At"},"channel":{"type":"string","title":"Channel","description":"laoqr (paid in the app) · cash · bank · qr · other (recorded by an admin)","default":"laoqr"},"qr_string":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Qr String"},"qr_image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Qr Image","description":"QR code image as a data: URL"}},"type":"object","required":["id","package","characters","amount_lak","status","transaction_id","created_at","expires_at","paid_at"],"title":"OrderInfo"},"PlanBody":{"properties":{"code":{"type":"string","title":"Code","description":"Short ID: lowercase a–z, 0–9, - or _","examples":["pro"]},"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","examples":["Pro"]},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description"},"price_usd_month":{"type":"number","maximum":100000.0,"minimum":0.0,"title":"Price Usd Month","default":0},"monthly_chars":{"type":"integer","maximum":1000000000.0,"minimum":0.0,"title":"Monthly Chars","description":"Characters (tokens) per month"},"max_chars_per_request":{"type":"integer","maximum":5000.0,"minimum":1.0,"title":"Max Chars Per Request"},"requests_per_minute":{"type":"integer","maximum":10000.0,"minimum":1.0,"title":"Requests Per Minute"},"max_api_keys":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Max Api Keys"},"max_custom_voices":{"type":"integer","maximum":1.0,"minimum":0.0,"title":"Max Custom Voices","description":"1 = users can clone one voice, 0 = no cloning"},"allow_verify":{"type":"boolean","title":"Allow Verify","default":false},"is_public":{"type":"boolean","title":"Is Public","description":"Listed on GET /plans; false = custom package for chosen users","default":true},"is_active":{"type":"boolean","title":"Is Active","default":true}},"type":"object","required":["code","name","monthly_chars","max_chars_per_request","requests_per_minute","max_api_keys","max_custom_voices"],"title":"PlanBody"},"PlanInfo":{"properties":{"code":{"type":"string","title":"Code","examples":["free"]},"name":{"type":"string","title":"Name","examples":["Free"]},"price_usd_month":{"type":"number","title":"Price Usd Month","examples":[0]},"monthly_chars":{"type":"integer","title":"Monthly Chars","description":"Characters per calendar month (UTC)","examples":[20000]},"max_chars_per_request":{"type":"integer","title":"Max Chars Per Request","examples":[1000]},"requests_per_minute":{"type":"integer","title":"Requests Per Minute","examples":[10]},"max_api_keys":{"type":"integer","title":"Max Api Keys","examples":[3]},"max_custom_voices":{"type":"integer","title":"Max Custom Voices","description":"1 = can clone one voice, 0 = no voice cloning","examples":[1]},"allow_verify":{"type":"boolean","title":"Allow Verify","description":"Whether `verify: true` may be used","examples":[false]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","examples":["Get started with natural Lao speech."]}},"type":"object","required":["code","name","price_usd_month","monthly_chars","max_chars_per_request","requests_per_minute","max_api_keys","max_custom_voices","allow_verify"],"title":"PlanInfo"},"PlanList":{"properties":{"plans":{"items":{"$ref":"#/components/schemas/PlanInfo"},"type":"array","title":"Plans"}},"type":"object","required":["plans"],"title":"PlanList"},"PlanUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description"},"price_usd_month":{"anyOf":[{"type":"number","maximum":100000.0,"minimum":0.0},{"type":"null"}],"title":"Price Usd Month"},"monthly_chars":{"anyOf":[{"type":"integer","maximum":1000000000.0,"minimum":0.0},{"type":"null"}],"title":"Monthly Chars"},"max_chars_per_request":{"anyOf":[{"type":"integer","maximum":5000.0,"minimum":1.0},{"type":"null"}],"title":"Max Chars Per Request"},"requests_per_minute":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Requests Per Minute"},"max_api_keys":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Max Api Keys"},"max_custom_voices":{"anyOf":[{"type":"integer","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Max Custom Voices","description":"1 = users can clone one voice, 0 = no cloning"},"allow_verify":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Allow Verify"},"is_public":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"PlanUpdate"},"RegisterBody":{"properties":{"full_name":{"type":"string","maxLength":100,"minLength":1,"title":"Full Name","examples":["ສົມພອນ ວົງສາ"]},"email":{"type":"string","maxLength":254,"title":"Email","examples":["somphone@example.la"]},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password","description":"At least 8 characters","examples":["my-secret-pass"]}},"type":"object","required":["full_name","email","password"],"title":"RegisterBody"},"ResetBody":{"properties":{"password":{"anyOf":[{"type":"string","maxLength":128,"minLength":8},{"type":"null"}],"title":"Password","description":"New password; omit to generate a temporary one"}},"type":"object","title":"ResetBody"},"ResetResult":{"properties":{"password":{"type":"string","title":"Password","description":"The new password — shown only once; give it to the user securely"},"generated":{"type":"boolean","title":"Generated"},"sessions_revoked":{"type":"integer","title":"Sessions Revoked"}},"type":"object","required":["password","generated","sessions_revoked"],"title":"ResetResult"},"Stats":{"properties":{"users":{"type":"integer","title":"Users"},"active_users":{"type":"integer","title":"Active Users"},"admins":{"type":"integer","title":"Admins"},"month_characters":{"type":"integer","title":"Month Characters"},"month_requests":{"type":"integer","title":"Month Requests"},"month_active_users":{"type":"integer","title":"Month Active Users","description":"Accounts that generated speech this month"},"by_plan":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Plan"}},"type":"object","required":["users","active_users","admins","month_characters","month_requests","month_active_users","by_plan"],"title":"Stats"},"TTSJob":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","enum":["queued","running","done","error"],"title":"Status"},"done":{"type":"integer","title":"Done","description":"Sentences finished","default":0},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Sentences in the text (known once the job starts)"},"queue_position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Queue Position","description":"Jobs ahead of this one (while queued)"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the job failed (status `error`)"},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code","description":"HTTP status the error corresponds to"},"audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Url","description":"Download the audio here (status `done`)"},"mime_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime Type"},"sample_rate":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sample Rate"},"duration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration"},"normalized_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Normalized Text"},"chunks":{"anyOf":[{"items":{"$ref":"#/components/schemas/Chunk"},"type":"array"},{"type":"null"}],"title":"Chunks"},"processing_time":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Processing Time"},"quota_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Remaining"}},"type":"object","required":["job_id","status"],"title":"TTSJob"},"TTSJsonResponse":{"properties":{"audio_base64":{"type":"string","title":"Audio Base64","description":"The audio file, base64-encoded"},"mime_type":{"type":"string","title":"Mime Type","examples":["audio/wav"]},"sample_rate":{"type":"integer","title":"Sample Rate","examples":[48000]},"duration":{"type":"number","title":"Duration","description":"Seconds of audio (before any speed change)","examples":[2.04]},"normalized_text":{"type":"string","title":"Normalized Text","description":"Exactly what was spoken","examples":["ລາຄາ ສິບຫ້າພັນ ກີບ"]},"chunks":{"items":{"$ref":"#/components/schemas/Chunk"},"type":"array","title":"Chunks"},"processing_time":{"type":"number","title":"Processing Time","description":"Seconds the server worked on it","examples":[3.12]},"quota_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Remaining","description":"Characters left this month (accounts only)","examples":[18734]}},"type":"object","required":["audio_base64","mime_type","sample_rate","duration","normalized_text","chunks","processing_time"],"title":"TTSJsonResponse"},"TTSRequest":{"properties":{"text":{"type":"string","maxLength":5000,"minLength":1,"title":"Text","description":"Lao text to speak. Numbers, dates, times, prices and abbreviations are expanded automatically. Your plan may allow fewer characters per request.","examples":["ສະບາຍດີ, ຍິນດີຕ້ອນຮັບ"]},"voice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice","description":"Voice ID from `GET /voices`; the default voice if omitted","examples":["jenny"]},"format":{"type":"string","enum":["wav","mp3","ogg","flac"],"title":"Format","description":"Audio format","default":"wav"},"seed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Seed","description":"A fixed seed gives identical audio for identical text (good for caching); `null` = random variation","default":1234},"verify":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Verify","description":"Check every sentence with a Lao speech recogniser and keep the best take. 2–3× slower, most accurate. Not on the Free plan."},"candidates":{"anyOf":[{"type":"integer","maximum":8.0,"minimum":1.0},{"type":"null"}],"title":"Candidates","description":"Maximum takes per sentence when `verify` is true (default 3)"},"cfg_value":{"anyOf":[{"type":"number","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Cfg Value","description":"Advanced: how closely the model follows the text (voice default if omitted)"},"inference_timesteps":{"anyOf":[{"type":"integer","maximum":50.0,"minimum":4.0},{"type":"null"}],"title":"Inference Timesteps","description":"Advanced: quality/speed trade-off (voice default if omitted)"},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Playback speed, pitch preserved","default":1.0}},"type":"object","required":["text"],"title":"TTSRequest"},"TextRequest":{"properties":{"text":{"type":"string","maxLength":5000,"minLength":1,"title":"Text","examples":["ລາວມີ 17 ແຂວງ ແລະ 1 ນະຄອນຫຼວງ."]}},"type":"object","required":["text"],"title":"TextRequest"},"TopupAdmin":{"properties":{"code":{"type":"string","title":"Code","examples":["standard"]},"name":{"type":"string","title":"Name","examples":["Standard"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"characters":{"type":"integer","title":"Characters","examples":[200000]},"price_lak":{"type":"integer","title":"Price Lak","description":"Price in Lao kip","examples":[180000]},"id":{"type":"integer","title":"Id"},"is_active":{"type":"boolean","title":"Is Active"},"sort_order":{"type":"integer","title":"Sort Order"},"orders_paid":{"type":"integer","title":"Orders Paid"}},"type":"object","required":["code","name","characters","price_lak","id","is_active","sort_order","orders_paid"],"title":"TopupAdmin"},"TopupBody":{"properties":{"code":{"type":"string","title":"Code","examples":["business"]},"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description"},"characters":{"type":"integer","maximum":10000000000.0,"minimum":1.0,"title":"Characters"},"price_lak":{"type":"integer","maximum":10000000000.0,"minimum":1.0,"title":"Price Lak"},"is_active":{"type":"boolean","title":"Is Active","default":true},"sort_order":{"type":"integer","title":"Sort Order","default":0}},"type":"object","required":["code","name","characters","price_lak"],"title":"TopupBody"},"TopupInfo":{"properties":{"code":{"type":"string","title":"Code","examples":["standard"]},"name":{"type":"string","title":"Name","examples":["Standard"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"characters":{"type":"integer","title":"Characters","examples":[200000]},"price_lak":{"type":"integer","title":"Price Lak","description":"Price in Lao kip","examples":[180000]}},"type":"object","required":["code","name","characters","price_lak"],"title":"TopupInfo"},"TopupList":{"properties":{"packages":{"items":{"$ref":"#/components/schemas/TopupInfo"},"type":"array","title":"Packages"},"payments_enabled":{"type":"boolean","title":"Payments Enabled","description":"False until the server has a LaoQR account configured"}},"type":"object","required":["packages","payments_enabled"],"title":"TopupList"},"TopupUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description"},"characters":{"anyOf":[{"type":"integer","maximum":10000000000.0,"minimum":1.0},{"type":"null"}],"title":"Characters"},"price_lak":{"anyOf":[{"type":"integer","maximum":10000000000.0,"minimum":1.0},{"type":"null"}],"title":"Price Lak"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"sort_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sort Order"}},"type":"object","title":"TopupUpdate"},"UsageResponse":{"properties":{"month_characters":{"type":"integer","title":"Month Characters","description":"Characters used since the 1st of this month (UTC)"},"month_requests":{"type":"integer","title":"Month Requests"},"plan_remaining":{"type":"integer","title":"Plan Remaining","description":"Characters left in this month's plan quota"},"credit_chars":{"type":"integer","title":"Credit Chars","description":"Bought top-up characters (never expire; used after the plan quota)"},"remaining_characters":{"type":"integer","title":"Remaining Characters","description":"plan_remaining + credit_chars"},"period_start":{"type":"string","title":"Period Start","examples":["2026-10-01"]},"days":{"items":{"$ref":"#/components/schemas/DayUsage"},"type":"array","title":"Days","description":"Last 30 days, oldest first"}},"type":"object","required":["month_characters","month_requests","plan_remaining","credit_chars","remaining_characters","period_start","days"],"title":"UsageResponse"},"UsageSummary":{"properties":{"month_characters":{"type":"integer","title":"Month Characters","description":"Characters used since the 1st of this month (UTC)"},"month_requests":{"type":"integer","title":"Month Requests"},"plan_remaining":{"type":"integer","title":"Plan Remaining","description":"Characters left in this month's plan quota"},"credit_chars":{"type":"integer","title":"Credit Chars","description":"Bought top-up characters (never expire; used after the plan quota)"},"remaining_characters":{"type":"integer","title":"Remaining Characters","description":"plan_remaining + credit_chars"},"period_start":{"type":"string","title":"Period Start","examples":["2026-10-01"]}},"type":"object","required":["month_characters","month_requests","plan_remaining","credit_chars","remaining_characters","period_start"],"title":"UsageSummary"},"UserInfo":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"type":"string","title":"Email"},"full_name":{"type":"string","title":"Full Name"},"is_admin":{"type":"boolean","title":"Is Admin"},"plan_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Expires At","description":"The package is valid until then (UTC); afterwards Free"}},"type":"object","required":["id","email","full_name","is_admin"],"title":"UserInfo"},"UserKey":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"prefix":{"type":"string","title":"Prefix"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"}},"type":"object","required":["id","name","prefix","created_at","last_used_at"],"title":"UserKey"},"UserPage":{"properties":{"users":{"items":{"$ref":"#/components/schemas/AdminUser"},"type":"array","title":"Users"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["users","total"],"title":"UserPage"},"UserUpdate":{"properties":{"full_name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Full Name"},"email":{"anyOf":[{"type":"string","maxLength":254},{"type":"null"}],"title":"Email"},"plan":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan","description":"Package code to move the user to","examples":["free"]},"plan_expires_at":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Plan Expires At","description":"Last day of the package (inclusive); null = no end. Afterwards the user is moved back to Free."},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"is_admin":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Admin"},"add_credits":{"anyOf":[{"type":"integer","maximum":1000000000.0,"minimum":-1000000000.0},{"type":"null"}],"title":"Add Credits","description":"Add (or with a negative number remove) top-up characters"}},"type":"object","title":"UserUpdate"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VoiceCreated":{"properties":{"name":{"type":"string","title":"Name","examples":["somphone"]},"label":{"type":"string","title":"Label","examples":["ສົມພອນ"]},"speech_seconds":{"type":"number","title":"Speech Seconds","description":"Seconds of speech kept after removing silences","examples":[18.4]},"raw_seconds":{"type":"number","title":"Raw Seconds","description":"Length of the uploaded recording","examples":[24.1]},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings","description":"Hints about noise, clipping or length","examples":[[]]}},"type":"object","required":["name","label","speech_seconds","raw_seconds","warnings"],"title":"VoiceCreated"},"VoiceInfo":{"properties":{"name":{"type":"string","title":"Name","description":"Voice ID to use in requests","examples":["jenny"]},"label":{"type":"string","title":"Label","description":"Display name","examples":["Jenny"]},"custom":{"type":"boolean","title":"Custom","description":"True for voices cloned with `POST /voices`"},"mine":{"type":"boolean","title":"Mine","description":"True for built-in voices and voices you cloned","default":true},"owner_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Owner Id","description":"Account that cloned it (shown to administrators)"},"fine_tuned":{"type":"boolean","title":"Fine Tuned"},"settings":{"additionalProperties":true,"type":"object","title":"Settings"}},"type":"object","required":["name","label","custom","fine_tuned","settings"],"title":"VoiceInfo"},"VoiceList":{"properties":{"default":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default","examples":["jenny"]},"voices":{"items":{"$ref":"#/components/schemas/VoiceInfo"},"type":"array","title":"Voices"}},"type":"object","required":["default","voices"],"title":"VoiceList"},"WebhookEvent":{"properties":{"id":{"type":"integer","title":"Id"},"order_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order Id"},"transaction_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transaction Id"},"ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip"},"headers":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Headers"},"payload":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payload"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["id","order_id","transaction_id","ip","headers","payload","created_at"],"title":"WebhookEvent"}},"securitySchemes":{"APIKey":{"type":"http","description":"Your API key (`sd_…`), sent as `Authorization: Bearer sd_…`. Create one on the API Keys page.","scheme":"bearer"},"XAPIKey":{"type":"apiKey","description":"Alternative: send the API key in the `X-API-Key` header.","in":"header","name":"X-API-Key"}}},"tags":[{"name":"Speech","description":"Turn Lao text into audio. Characters sent here count towards your quota."},{"name":"Voices","description":"Built-in and cloned voices. Cloned voices are private to your account."},{"name":"Pronunciation","description":"The shared pronunciation list. Changing it needs an admin account."},{"name":"System","description":"Public status and plan information — no API key needed."},{"name":"Account (web app)","description":"Used by the web app with a login cookie. Third-party apps only need an API key."},{"name":"Billing","description":"Buy character top-ups with LaoQR (web login). Bought characters never expire and are used after the monthly plan quota."},{"name":"Admin","description":"Manage users, packages and payments. Administrator web login only."}]}