{"openapi":"3.1.0","info":{"title":"Paxa Labs API","version":"1.0.0-beta","description":"Text to speech for Thai, English, and Mandarin Chinese, Thai-first multilingual transcription, OCR over Thai documents and other languages, and translation into Thai from any language. Document Extraction pulls typed fields from documents against a schema you write. Authenticate with an API key from the dashboard. Send it as 'Authorization: Bearer pxa_...' or in the x-api-key header. Authorization wins when both are present. Costs are charged in credits before inference and refunded automatically when inference fails. Every error is an RFC 9457 problem+json body whose title field is a stable machine-readable code. Report a wrong output or an API problem to POST /v1/feedback with the request's x-request-id. /v1/tts/live and /v1/stt/live are WebSockets, which OpenAPI does not describe; the docs at https://paxalabs.com/docs do."},"externalDocs":{"url":"https://paxalabs.com/docs"},"servers":[{"url":"https://api.paxalabs.com"}],"tags":[{"name":"TTS","description":"Speech synthesis."},{"name":"Designed voices","description":"Design, save, and manage the account's own voices."},{"name":"Translation","description":"Translation into Thai."},{"name":"OCR","description":"Document text recognition and document extraction."},{"name":"STT","description":"Speech to text."},{"name":"Catalog","description":"Models and voices. Public."},{"name":"Account","description":"Key, balance, plan, and feedback."},{"name":"System","description":"Health and diagnostics."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key"}}},"paths":{"/v1/tts":{"post":{"operationId":"createSpeech","summary":"Synthesize speech","description":"Converts text to speech and returns the audio bytes. Cost is deterministic from text length, and the request is charged before synthesis. When synthesis fails, the charge is refunded automatically and the response is 502, or 422 unspeakable_text for text with nothing the model can voice.","tags":["TTS"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"idempotency-key","in":"header","required":false,"description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","schema":{"minLength":1,"maxLength":200,"pattern":"^[!-~]+$","description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"minLength":1,"description":"The text to synthesize, in Thai, English, Mandarin Chinese (Simplified script), or any mix of them. Cost is 10 credits per 1000 characters, billed per character and rounded up to the next hundredth of a credit, minimum 0.1 credit per request. At most 5,000 characters; longer text answers 400 text_too_long, and GET /v1/models reports each model's own ceiling as max_chars. On Paxa TTS Flash v2, every voice reads tags. A bracketed direction such as [whispering] sets the delivery from where it stands. A sound such as [laughs] plays where it is written, [pause] or [pause 300ms] adds silence, at most 500 ms for an exact length, and <emph> and <whisper> mark a span. A tag that model refuses, such as [british accent] or [sniffs], answers 400 tag_invalid. On a Paxa TTS Flash (paxa-tts-flash-v1) voice whose emotion_tags list is not empty, a bracketed tag such as [happy] or [sad:0.5] sets the delivery from where it stands. On those voices a tag outside the list, or a weight that is not a number, answers 400 tag_invalid. Every other v1 voice skips the tags it knows. Tag characters bill like the rest of the text.","examples":["สวัสดีครับ ยินดีต้อนรับสู่ Paxa Labs"],"type":"string"},"voice":{"minLength":1,"maxLength":100,"description":"The voice id, the stable identifier GET /v1/voices lists, for example \"khanomkrok\". Case is ignored, and only the id is matched. A designed voice has an id starting \"voice_\", and GET /v1/voices/designed lists the account's. It speaks only on Paxa TTS Flash v2 (paxa-tts-flash-v2) and only for the account that saved it; on another model or account, or once deleted, it answers 400 unknown_voice. Speech in a designed voice bills at 1.25x the model's per-character rate, or 1.5x with timestamps.","examples":["khanomkrok"],"type":"string"},"model":{"minLength":1,"maxLength":100,"description":"TTS model id, for example paxa-tts-flash-v2. GET /v1/models lists the served catalog.","examples":["paxa-tts-flash-v2"],"type":"string"},"format":{"default":"mp3","description":"Audio format. mp3 is compressed and the default. opus is Ogg Opus, 48 kbps on Paxa TTS Flash and 64 on Paxa TTS Flash v2, the smallest, for streaming and web playback. wav is uncompressed 16-bit PCM under a RIFF header, the largest, for audio that feeds further processing. pcm is those samples with no header, 16-bit little-endian mono, for voice agents and phone systems that take raw samples. Both are 24 kHz unless sample_rate says otherwise. mulaw and alaw are G.711 mu-law and A-law with no header, one byte per sample at 8 kHz, for phone lines. Paxa TTS Flash v2 serves them, and Paxa TTS Flash answers unsupported_format.","anyOf":[{"type":"string","const":"mp3"},{"type":"string","const":"opus"},{"type":"string","const":"wav"},{"type":"string","const":"pcm"},{"type":"string","const":"mulaw"},{"type":"string","const":"alaw"}]},"sample_rate":{"description":"Sample rate of pcm and wav, in Hz. Paxa TTS Flash v2 serves 8000, 16000, 22050, 24000, 32000, 44100, 48000, and Paxa TTS Flash serves 24000 only. For any other format the value may only restate that format's own rate, which is 8000 for mulaw and alaw. A rate the model doesn't serve answers unsupported_format. A request costs the same at every rate.","examples":[16000],"anyOf":[{"type":"number","const":8000},{"type":"number","const":16000},{"type":"number","const":22050},{"type":"number","const":24000},{"type":"number","const":32000},{"type":"number","const":44100},{"type":"number","const":48000}]},"stream":{"default":false,"description":"When true the audio arrives as a chunked stream while it is synthesized, lowering time to first byte. When false the response is a complete body with a content-length.","type":"boolean"},"timestamps":{"description":"Adds speech timing to the response: \"word\" times every word, \"sentence\" every sentence, \"utterance\" every continuous run of speech. When set, the response is application/json with base64 audio and timing spans, or application/x-ndjson events when stream is true, and the request bills at 1.25x the model's per-character rate.","anyOf":[{"type":"string","const":"word"},{"type":"string","const":"sentence"},{"type":"string","const":"utterance"}]},"speed":{"default":1,"minimum":0.5,"maximum":1.5,"description":"Speaking rate, as a multiplier on the voice's own design speed. It runs from 0.5 to 1.5, and 1 is the voice as designed. Rate changes how the audio sounds. It does not change how many characters were sent. A request costs the same at every rate. Values round to the nearest 0.05. Timing spans are measured against the audio that was produced. They already carry the rate.","examples":[1.1],"type":"number"},"language":{"default":"auto","description":"Sets how codes, digits, and symbols are read. Words keep the language of their script, and a number inside Thai or Chinese text keeps that text's language. Elsewhere, \"th\" reads them in Thai (AB2039 with Thai letter names and digits) and \"en\" in English. \"zh\" reads codes in English, a number next to Chinese characters in Mandarin, and other digits and symbols in Thai. \"auto\", the default, takes the language most of the text is written in, counting English only in words of three or more letters, and Thai on a tie. Under \"auto\", a bare number, AB2039, or 250 ml reads as Thai, and ISO9001 or 100 USD as English. Text with no Thai, Chinese, digits, or counted English word reads as English when a two-letter word has a lowercase letter, such as Hi or I'm OK. Pin \"th\" or \"en\" to choose how a short number or code reads. A request costs the same in every language. Every value but \"auto\" is a code GET /v1/voices reports as a voice's language. POST /v1/audio/speech ignores this field and reads every request as \"auto\".","examples":["en"],"anyOf":[{"type":"string","const":"auto"},{"type":"string","const":"th"},{"type":"string","const":"en"},{"type":"string","const":"zh"}]},"temperature":{"default":0.5,"minimum":0,"maximum":2,"description":"Sets how much the pacing, pauses, breaths, and melody of a reading vary from one request to the next. It runs from 0 to 2, and 0.5 is the default. They vary least at 0 and most at 2. Paxa TTS Flash v2 reads it. Any other model accepts only a value that rounds to 0.5, and any other answers validation. A request costs the same at every temperature. Values round to the nearest 0.1. POST /v1/audio/speech and the start frame of WS /v1/tts/live read it too.","examples":[0.5],"type":"number"}},"required":["text","voice","model"]}}}},"responses":{"200":{"description":"The synthesized speech as binary audio, in the format and sample rate the request chose.","headers":{"x-request-id":{"description":"Identifies the request for support and log correlation. Sent on every response, errors included, except the cached catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.","schema":{"type":"string"}},"x-credits-charged":{"description":"Credits charged for this request, exact to a hundredth. An idempotent replay reports the original charge. Sent on audio responses and on the OpenAI-compatible transcription endpoint, whose body follows OpenAI's shape and carries no usage object. POST /v1/translate, /v1/ocr, /v1/extract, /v1/stt, and /v1/voices/design report the charge in the usage object of the body instead.","schema":{"type":"string"}},"x-voice-id":{"description":"The id of the voice that spoke. When the request named the voice by an accepted alias, such as an OpenAI voice name, it is the Paxa voice the alias maps to. Sent on audio responses.","schema":{"type":"string"}},"x-ratelimit-limit":{"description":"Requests per minute the plan allows.","schema":{"type":"string"}},"x-ratelimit-remaining":{"description":"Requests left in the current minute window.","schema":{"type":"string"}},"x-ratelimit-reset":{"description":"Unix time in seconds when the current window resets.","schema":{"type":"string"}}},"content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}},"audio/ogg":{"schema":{"type":"string","format":"binary"}},"audio/wav":{"schema":{"type":"string","format":"binary"}},"audio/pcm;rate=24000;channels=1":{"schema":{"type":"string","format":"binary"}},"audio/basic":{"schema":{"type":"string","format":"binary"}},"audio/x-alaw-basic":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"description":"Speech with timing, returned when timestamps is set and stream is false.","type":"object","properties":{"audio":{"description":"The complete audio file in the requested container, base64-encoded.","type":"string"},"timing":{"type":"object","properties":{"granularity":{"description":"The granularity that was requested.","anyOf":[{"type":"string","const":"word"},{"type":"string","const":"sentence"},{"type":"string","const":"utterance"}]},"spans":{"description":"Spans in text order, together covering the spoken input.","type":"array","items":{"type":"object","properties":{"text":{"description":"The spoken input text this span covers. An emotion tag takes no span.","type":"string"},"start":{"description":"Where the span begins in the audio, in seconds.","type":"number"},"end":{"description":"Where the span ends in the audio, in seconds.","type":"number"}},"required":["text","start","end"]}}},"required":["granularity","spans"]}},"required":["audio","timing"]}},"application/x-ndjson":{"schema":{"type":"string","description":"One JSON event per line, arriving as audio is synthesized. Each chunk carries the spans its own audio completed. The stream ends with a done event. A mid-stream failure ends it with an error event and refunds the charge automatically."}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`unknown_model`: The model field does not name a served model.\n\n`unsupported_format`: The model does not serve this format, or this format at the requested sample rate.\n\n`unknown_voice`: The voice id names no voice on the model's roster and no designed voice this account holds.\n\n`text_too_long`: The text exceeds the model's per-request character ceiling.\n\n`tag_invalid`: The text holds a tag the model refuses. On Paxa TTS Flash v2, the bracket asks for what the model does not do, such as an accent, singing, or a sound outside the supported list. It can also hold no letters or carry a weight that is not a number. On a Paxa TTS Flash voice that takes emotion tags, the bracketed word is outside the tag list, or its weight is not a number. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","unknown_model","unsupported_format","unknown_voice","text_too_long","tag_invalid"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"402":{"description":"`insufficient_credits`: The account does not have enough credits for this request. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["insufficient_credits"]},"status":{"type":"integer","const":402}}}}}},"403":{"description":"`key_limit`: This API key reached its spending cap. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["key_limit"]},"status":{"type":"integer","const":403}}}}}},"409":{"description":"`idempotency_in_flight`: Another request with this Idempotency-Key is in flight right now.\n\n`idempotency_refunded`: The original request under this Idempotency-Key failed and was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_in_flight","idempotency_refunded"]},"status":{"type":"integer","const":409}}}}}},"422":{"description":"`idempotency_mismatch`: This Idempotency-Key was already used for a different request.\n\n`unspeakable_text`: The text contains nothing the model can voice: only tags, emoji, punctuation, or a script it does not speak. A text of tags alone is refused before the charge; any other is charged and refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_mismatch","unspeakable_text"]},"status":{"type":"integer","const":422}}}}}},"429":{"description":"`rate_limited`: Requests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.\n\n`concurrency_limited`: The account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["rate_limited","concurrency_limited"]},"status":{"type":"integer","const":429}}}}}},"500":{"description":"`internal`: Request state was inconsistent on the server.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["internal"]},"status":{"type":"integer","const":500}}}}}},"502":{"description":"`provider_error`: Model inference failed after the request was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_error"]},"status":{"type":"integer","const":502}}}}}},"503":{"description":"`provider_unavailable`: The model behind this endpoint is not available right now. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_unavailable"]},"status":{"type":"integer","const":503}}}}}}}}},"/v1/audio/speech":{"post":{"operationId":"createSpeechOpenAiCompatible","summary":"Synthesize speech (OpenAI-compatible)","description":"Converts text to speech from the OpenAI request shape. Point an OpenAI SDK at this server's base URL, authenticate with a Paxa API key, and call audio.speech.create. Behavior, pricing, and error codes match POST /v1/tts. Unrecognized OpenAI fields are ignored; response_format takes the containers POST /v1/tts serves. This alias always returns raw audio. To receive timing spans or pin the language, call POST /v1/tts.","tags":["TTS"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"idempotency-key","in":"header","required":false,"description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","schema":{"minLength":1,"maxLength":200,"pattern":"^[!-~]+$","description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"minLength":1,"maxLength":100,"description":"TTS model id, for example paxa-tts-flash-v2. OpenAI's tts-1, tts-1-hd, gpt-4o-mini-tts, gpt-4o-mini-tts-2025-03-20, gpt-4o-mini-tts-2025-12-15 are accepted and run on paxa-tts-flash-v2.","type":"string"},"input":{"minLength":1,"description":"The text to synthesize. Same limits, pricing, and tags as POST /v1/tts.","type":"string"},"voice":{"minLength":1,"maxLength":100,"description":"The voice id from GET /v1/voices, for example \"khanomkrok\", or a designed voice id starting \"voice_\", resolved and billed as on POST /v1/tts. Case is ignored. OpenAI's voice names, such as alloy, are accepted and map to Paxa voices, and the x-voice-id response header names the voice that spoke.","type":"string"},"response_format":{"default":"mp3","description":"Audio container, as format on POST /v1/tts. Other OpenAI formats are not served and answer 400.","anyOf":[{"type":"string","const":"mp3"},{"type":"string","const":"opus"},{"type":"string","const":"wav"},{"type":"string","const":"pcm"},{"type":"string","const":"mulaw"},{"type":"string","const":"alaw"}]},"sample_rate":{"description":"Sample rate of pcm and wav, in Hz. Paxa TTS Flash v2 serves 8000, 16000, 22050, 24000, 32000, 44100, 48000, and Paxa TTS Flash serves 24000 only. A Paxa extension to OpenAI's body. On this endpoint, formats other than pcm and wav ignore it, and any value must still be one of the listed rates. A rate the model doesn't serve answers unsupported_format. A request costs the same at every rate.","examples":[16000],"anyOf":[{"type":"number","const":8000},{"type":"number","const":16000},{"type":"number","const":22050},{"type":"number","const":24000},{"type":"number","const":32000},{"type":"number","const":44100},{"type":"number","const":48000}]},"stream":{"default":true,"description":"Streams the audio as it is synthesized, matching OpenAI delivery. Defaults to true. Set false to receive a complete body with a content-length.","type":"boolean"},"stream_format":{"description":"The OpenAI field of the same name. The response is always the raw audio bytes under the audio content type of response_format, \"sse\" included. Read the content-type header before parsing events.","anyOf":[{"type":"string","const":"audio"},{"type":"string","const":"sse"}]},"speed":{"default":1,"minimum":0.5,"maximum":1.5,"description":"Speaking rate, as a multiplier on the voice's own design speed. It runs from 0.5 to 1.5, and 1 is the voice as designed. Rate changes how the audio sounds. It does not change how many characters were sent. A request costs the same at every rate. Values round to the nearest 0.05. The OpenAI field of the same name. A value outside that range answers 400.","examples":[1.1],"type":"number"},"temperature":{"default":0.5,"minimum":0,"maximum":2,"description":"Sets how much the pacing, pauses, breaths, and melody of a reading vary from one request to the next. It runs from 0 to 2, and 0.5 is the default. They vary least at 0 and most at 2. Paxa TTS Flash v2 reads it. Any other model accepts only a value that rounds to 0.5, and any other answers validation. A request costs the same at every temperature. Values round to the nearest 0.1. A Paxa extension to OpenAI's body, read as on POST /v1/tts.","examples":[0.5],"type":"number"}},"required":["model","input","voice"]}}}},"responses":{"200":{"description":"The synthesized speech as binary audio, in the format and sample rate the request chose.","headers":{"x-request-id":{"description":"Identifies the request for support and log correlation. Sent on every response, errors included, except the cached catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.","schema":{"type":"string"}},"x-credits-charged":{"description":"Credits charged for this request, exact to a hundredth. An idempotent replay reports the original charge. Sent on audio responses and on the OpenAI-compatible transcription endpoint, whose body follows OpenAI's shape and carries no usage object. POST /v1/translate, /v1/ocr, /v1/extract, /v1/stt, and /v1/voices/design report the charge in the usage object of the body instead.","schema":{"type":"string"}},"x-voice-id":{"description":"The id of the voice that spoke. When the request named the voice by an accepted alias, such as an OpenAI voice name, it is the Paxa voice the alias maps to. Sent on audio responses.","schema":{"type":"string"}},"x-ratelimit-limit":{"description":"Requests per minute the plan allows.","schema":{"type":"string"}},"x-ratelimit-remaining":{"description":"Requests left in the current minute window.","schema":{"type":"string"}},"x-ratelimit-reset":{"description":"Unix time in seconds when the current window resets.","schema":{"type":"string"}}},"content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}},"audio/ogg":{"schema":{"type":"string","format":"binary"}},"audio/wav":{"schema":{"type":"string","format":"binary"}},"audio/pcm;rate=24000;channels=1":{"schema":{"type":"string","format":"binary"}},"audio/basic":{"schema":{"type":"string","format":"binary"}},"audio/x-alaw-basic":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`unknown_model`: The model field does not name a served model.\n\n`unsupported_format`: The model does not serve this format, or this format at the requested sample rate.\n\n`unknown_voice`: The voice id names no voice on the model's roster and no designed voice this account holds.\n\n`text_too_long`: The text exceeds the model's per-request character ceiling.\n\n`tag_invalid`: The text holds a tag the model refuses. On Paxa TTS Flash v2, the bracket asks for what the model does not do, such as an accent, singing, or a sound outside the supported list. It can also hold no letters or carry a weight that is not a number. On a Paxa TTS Flash voice that takes emotion tags, the bracketed word is outside the tag list, or its weight is not a number. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","unknown_model","unsupported_format","unknown_voice","text_too_long","tag_invalid"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"402":{"description":"`insufficient_credits`: The account does not have enough credits for this request. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["insufficient_credits"]},"status":{"type":"integer","const":402}}}}}},"403":{"description":"`key_limit`: This API key reached its spending cap. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["key_limit"]},"status":{"type":"integer","const":403}}}}}},"409":{"description":"`idempotency_in_flight`: Another request with this Idempotency-Key is in flight right now.\n\n`idempotency_refunded`: The original request under this Idempotency-Key failed and was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_in_flight","idempotency_refunded"]},"status":{"type":"integer","const":409}}}}}},"422":{"description":"`idempotency_mismatch`: This Idempotency-Key was already used for a different request.\n\n`unspeakable_text`: The text contains nothing the model can voice: only tags, emoji, punctuation, or a script it does not speak. A text of tags alone is refused before the charge; any other is charged and refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_mismatch","unspeakable_text"]},"status":{"type":"integer","const":422}}}}}},"429":{"description":"`rate_limited`: Requests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.\n\n`concurrency_limited`: The account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["rate_limited","concurrency_limited"]},"status":{"type":"integer","const":429}}}}}},"500":{"description":"`internal`: Request state was inconsistent on the server.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["internal"]},"status":{"type":"integer","const":500}}}}}},"502":{"description":"`provider_error`: Model inference failed after the request was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_error"]},"status":{"type":"integer","const":502}}}}}},"503":{"description":"`provider_unavailable`: The model behind this endpoint is not available right now. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_unavailable"]},"status":{"type":"integer","const":503}}}}}}}}},"/v1/voices/design":{"post":{"operationId":"designVoice","summary":"Design a voice","description":"Designs a voice from a written description, or refines a draft with an instruction, and returns the voice's words, its example line spoken as MP3, and a draft. Each call costs 5 credits, charged before the work and refunded automatically when no voice comes back: 502 provider_error, or 422 content_blocked when the description is declined. A client that disconnects after the charge keeps it. Idempotency-Key is refused with 400 validation, because each call designs a new voice. Nothing is saved until the draft goes to POST /v1/voices/designed.","tags":["Designed voices"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Send description alone to design a voice, or draft with instruction to refine one. Any other combination answers 400 validation.","type":"object","properties":{"description":{"minLength":1,"maxLength":1000,"description":"Who the voice is and how it sounds, in English, Thai, or Chinese, up to 1,000 characters: age, gender, pitch, pace, manner, and the language it speaks. The voice speaks the language this names, else the language this is written in, and its name, role, and description come back in the language this is written in.","examples":["A calm Bangkok pharmacist in her thirties who explains dosages slowly and kindly, speaking Thai"],"type":"string"},"draft":{"minLength":1,"maxLength":4096,"description":"A draft an earlier design response returned, to refine that voice. Send it with instruction and without description.","type":"string"},"instruction":{"minLength":1,"maxLength":500,"description":"What to change about the draft's voice, up to 500 characters, such as \"a little lower and slower\". The rest of the voice stays as it was.","type":"string"}},"required":[]}}}},"responses":{"200":{"description":"A designed voice, ready to refine or save.","headers":{"x-request-id":{"description":"Identifies the request for support and log correlation. Sent on every response, errors included, except the cached catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.","schema":{"type":"string"}},"x-ratelimit-limit":{"description":"Requests per minute the plan allows.","schema":{"type":"string"}},"x-ratelimit-remaining":{"description":"Requests left in the current minute window.","schema":{"type":"string"}},"x-ratelimit-reset":{"description":"Unix time in seconds when the current window resets.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"description":"A designed voice, ready to refine or save.","type":"object","properties":{"draft":{"description":"The voice, sealed. Send it back with an instruction to refine the voice, or to POST /v1/voices/designed to save it. It opens only for this account.","type":"string"},"voice":{"description":"The designed voice's words.","type":"object","properties":{"name":{"description":"A name for the voice, which saving keeps unless you send another.","type":"string"},"role":{"description":"Who the voice is, in a few words.","type":"string"},"description":{"description":"How the voice sounds, in one sentence.","type":"string"},"gender":{"description":"Voice gender, \"f\" or \"m\".","anyOf":[{"type":"string","const":"f"},{"type":"string","const":"m"}]},"language":{"description":"The one language the voice speaks: \"th\" (Thai), \"en\" (English), or \"zh\" (Mandarin Chinese, Simplified script).","anyOf":[{"type":"string","const":"th"},{"type":"string","const":"en"},{"type":"string","const":"zh"}]},"line":{"description":"The example line audio speaks, up to 120 characters in the voice's language.","type":"string"},"model":{"description":"The model the voice speaks on, paxa-tts-flash-v2.","type":"string"}},"required":["name","role","description","gender","language","line","model"]},"audio":{"description":"The example line spoken in the voice, as base64 MP3.","type":"string"},"usage":{"description":"What the request was billed for.","type":"object","properties":{"credits":{"description":"What this turn cost, 5 credits.","type":"number"}},"required":["credits"]}},"required":["draft","voice","audio","usage"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`draft_invalid`: The draft field holds no draft this account received from POST /v1/voices/design. It is altered or cut short, belongs to another account, or no longer opens after a platform update. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","draft_invalid"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"402":{"description":"`insufficient_credits`: The account does not have enough credits for this request. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["insufficient_credits"]},"status":{"type":"integer","const":402}}}}}},"403":{"description":"`key_limit`: This API key reached its spending cap. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["key_limit"]},"status":{"type":"integer","const":403}}}}}},"422":{"description":"`content_blocked`: The upstream safety system declined to process this content. The charge was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["content_blocked"]},"status":{"type":"integer","const":422}}}}}},"429":{"description":"`rate_limited`: Requests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.\n\n`concurrency_limited`: The account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["rate_limited","concurrency_limited"]},"status":{"type":"integer","const":429}}}}}},"500":{"description":"`internal`: Request state was inconsistent on the server.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["internal"]},"status":{"type":"integer","const":500}}}}}},"502":{"description":"`provider_error`: Model inference failed after the request was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_error"]},"status":{"type":"integer","const":502}}}}}},"503":{"description":"`provider_unavailable`: The model behind this endpoint is not available right now. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_unavailable"]},"status":{"type":"integer","const":503}}}}}}}}},"/v1/voices/designed":{"get":{"operationId":"listDesignedVoices","summary":"List designed voices","description":"Returns the account's designed voices, oldest first, with the slots in use and the plan's limit. Listing costs nothing and takes no rate-limit token.","tags":["Designed voices"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"responses":{"200":{"description":"The account's designed voices.","headers":{},"content":{"application/json":{"schema":{"description":"The account's designed voices.","type":"object","properties":{"voices":{"description":"The account's designed voices, oldest first.","type":"array","items":{"description":"A designed voice the account saved.","type":"object","properties":{"id":{"description":"The voice id, starting \"voice_\". Send it as voice with model paxa-tts-flash-v2 on POST /v1/tts, POST /v1/audio/speech, or the /v1/tts/live socket.","type":"string"},"name":{"description":"Display name, which PATCH changes. The voice field does not accept it.","type":"string"},"role":{"description":"Who the voice is, in a few words.","type":"string"},"description":{"description":"How the voice sounds, in one sentence.","type":"string"},"gender":{"description":"Voice gender, \"f\" or \"m\".","anyOf":[{"type":"string","const":"f"},{"type":"string","const":"m"}]},"language":{"description":"The one language the voice speaks: \"th\" (Thai), \"en\" (English), or \"zh\" (Mandarin Chinese, Simplified script).","anyOf":[{"type":"string","const":"th"},{"type":"string","const":"en"},{"type":"string","const":"zh"}]},"model":{"description":"The model the voice speaks on.","type":"string"},"created_at":{"description":"Save time, ISO 8601.","type":"string"}},"required":["id","name","role","description","gender","language","model","created_at"]}},"slots":{"description":"The account's voice slots.","type":"object","properties":{"used":{"description":"Designed voices the account holds.","type":"integer"},"limit":{"description":"Designed voices the plan allows. After a downgrade, used can exceed it: every saved voice keeps speaking, and saving waits until used is below limit.","type":"integer"}},"required":["used","limit"]}},"required":["voices","slots"]}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}}}},"post":{"operationId":"saveDesignedVoice","summary":"Save a designed voice","description":"Saves a draft from POST /v1/voices/design as one of the account's voices and returns it, with the id the voice field accepts. Saving one draft again returns the same voice and takes no slot. A retry is safe. A full account answers 403 voice_slots_full. Saving costs nothing and takes no rate-limit token.","tags":["Designed voices"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"draft":{"minLength":1,"maxLength":4096,"description":"The draft a design response returned. Saving one draft again returns the voice it made and takes no slot.","type":"string"},"name":{"minLength":1,"maxLength":60,"description":"A name for the voice, up to 60 characters. Omitted, the voice keeps the name the design response gave it. A catalog voice's name answers 400 voice_name_taken.","type":"string"}},"required":["draft"]}}}},"responses":{"200":{"description":"A designed voice the account saved.","headers":{},"content":{"application/json":{"schema":{"description":"A designed voice the account saved.","type":"object","properties":{"id":{"description":"The voice id, starting \"voice_\". Send it as voice with model paxa-tts-flash-v2 on POST /v1/tts, POST /v1/audio/speech, or the /v1/tts/live socket.","type":"string"},"name":{"description":"Display name, which PATCH changes. The voice field does not accept it.","type":"string"},"role":{"description":"Who the voice is, in a few words.","type":"string"},"description":{"description":"How the voice sounds, in one sentence.","type":"string"},"gender":{"description":"Voice gender, \"f\" or \"m\".","anyOf":[{"type":"string","const":"f"},{"type":"string","const":"m"}]},"language":{"description":"The one language the voice speaks: \"th\" (Thai), \"en\" (English), or \"zh\" (Mandarin Chinese, Simplified script).","anyOf":[{"type":"string","const":"th"},{"type":"string","const":"en"},{"type":"string","const":"zh"}]},"model":{"description":"The model the voice speaks on.","type":"string"},"created_at":{"description":"Save time, ISO 8601.","type":"string"}},"required":["id","name","role","description","gender","language","model","created_at"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`draft_invalid`: The draft field holds no draft this account received from POST /v1/voices/design. It is altered or cut short, belongs to another account, or no longer opens after a platform update. Nothing was charged.\n\n`voice_name_taken`: The name is a catalog voice's name, which no designed voice can take. Nothing was saved.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","draft_invalid","voice_name_taken"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"403":{"description":"`voice_slots_full`: The account holds as many designed voices as its plan allows. Nothing was saved.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["voice_slots_full"]},"status":{"type":"integer","const":403}}}}}}}}},"/v1/voices/designed/{id}":{"patch":{"operationId":"renameDesignedVoice","summary":"Rename a designed voice","description":"Changes a designed voice's name and returns the voice. The id stays the same, and the voice sounds the same. Renaming costs nothing and takes no rate-limit token.","tags":["Designed voices"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The designed voice's id, starting \"voice_\".","schema":{"description":"The designed voice's id, starting \"voice_\".","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"minLength":1,"maxLength":60,"description":"The new name, up to 60 characters. A catalog voice's name answers 400 voice_name_taken.","type":"string"}},"required":["name"]}}}},"responses":{"200":{"description":"A designed voice the account saved.","headers":{},"content":{"application/json":{"schema":{"description":"A designed voice the account saved.","type":"object","properties":{"id":{"description":"The voice id, starting \"voice_\". Send it as voice with model paxa-tts-flash-v2 on POST /v1/tts, POST /v1/audio/speech, or the /v1/tts/live socket.","type":"string"},"name":{"description":"Display name, which PATCH changes. The voice field does not accept it.","type":"string"},"role":{"description":"Who the voice is, in a few words.","type":"string"},"description":{"description":"How the voice sounds, in one sentence.","type":"string"},"gender":{"description":"Voice gender, \"f\" or \"m\".","anyOf":[{"type":"string","const":"f"},{"type":"string","const":"m"}]},"language":{"description":"The one language the voice speaks: \"th\" (Thai), \"en\" (English), or \"zh\" (Mandarin Chinese, Simplified script).","anyOf":[{"type":"string","const":"th"},{"type":"string","const":"en"},{"type":"string","const":"zh"}]},"model":{"description":"The model the voice speaks on.","type":"string"},"created_at":{"description":"Save time, ISO 8601.","type":"string"}},"required":["id","name","role","description","gender","language","model","created_at"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`unknown_voice`: The voice id names no voice on the model's roster and no designed voice this account holds.\n\n`voice_name_taken`: The name is a catalog voice's name, which no designed voice can take. Nothing was saved.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","unknown_voice","voice_name_taken"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}}}},"delete":{"operationId":"deleteDesignedVoice","summary":"Delete a designed voice","description":"Deletes a designed voice and frees its slot. A deleted voice cannot be restored, and its id is never reused. Deleting costs nothing and takes no rate-limit token.","tags":["Designed voices"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The designed voice's id, starting \"voice_\".","schema":{"description":"The designed voice's id, starting \"voice_\".","type":"string"}}],"responses":{"200":{"description":"The voice is deleted. New requests in it answer 400 unknown_voice, and a live connection already open in it keeps it until the connection closes.","headers":{},"content":{"application/json":{"schema":{"description":"The voice is deleted. New requests in it answer 400 unknown_voice, and a live connection already open in it keeps it until the connection closes.","type":"object","properties":{"id":{"description":"The deleted voice's id.","type":"string"}},"required":["id"]}}}},"400":{"description":"`unknown_voice`: The voice id names no voice on the model's roster and no designed voice this account holds.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unknown_voice"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}}}}},"/v1/stt":{"post":{"operationId":"createTranscription","summary":"Transcribe a recording","description":"Transcribes a recording of Thai speech, or of other languages, including speech that switches language mid-sentence, and returns the transcript verbatim, with word timestamps and speaker turns on request. Cost is deterministic from the recording's length, which is read from the file before the charge. When transcription fails after the charge, it is refunded automatically and the response is 502, or 422 content_blocked when the content is declined.","tags":["STT"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"idempotency-key","in":"header","required":false,"description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","schema":{"minLength":1,"maxLength":200,"pattern":"^[!-~]+$","description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"audio":{"minLength":1,"maxLength":41000000,"description":"The recording to transcribe, as the base64 encoding of an MP3, WAV, FLAC, Ogg (Opus or Vorbis), M4A, AAC, or WebM file. The format and the length are read from the file itself. No declared field is consulted. Cost is 500 credits per hour of audio, 8.33 per minute, billed by exact length with a minimum of 0.1 credits per request, charged before transcription and refunded automatically when transcription fails. A recording may run up to 60 minutes, and longer answers 400 audio_too_long. The decoded file may be up to 26,214,400 bytes, and larger answers 413 audio_too_large. A file that cannot be read as one of these formats, or whose length cannot be read from its container, answers 400 audio_invalid. A recording with no speech is delivered as an empty transcript and billed for its length. GET /v1/models reports the ceilings as max_duration_seconds and max_bytes.","type":"string"},"model":{"minLength":1,"maxLength":100,"description":"Transcription model id, for example paxa-stt-lite-v1-preview. GET /v1/models lists the served catalog.","examples":["paxa-stt-lite-v1-preview"],"type":"string"},"language":{"pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","maxLength":16,"description":"The language the speech is expected to be in, as a BCP 47 tag such as \"th\" or \"en\". The hint filters nothing. Speech in any other language is still transcribed as spoken, and Thai that switches into English mid-sentence needs no setting. Omitted, the model detects the language itself. Set the tag when detection has picked the wrong language.","examples":["th"],"type":"string"},"timestamps":{"type":"string","const":"word","description":"Adds a words array to the response, one span per word with its start and end in seconds on the recording's own clock. Thai words are segmented for you. Latin words and numerals arrive whole. A punctuation mark joins the word before it, or the word after it when it opens. The span shape matches the speech API's timing spans. Captions round-trip between the two. Costs nothing extra. Omitted, the response carries the transcript alone."},"diarization":{"default":false,"description":"true adds a segments array to the response, one entry per speaker turn with its speaker number, text, and start and end in seconds. Speakers are numbered from 0 in order of first appearance. Nobody is identified. With word timestamps on as well, every word span names its speaker too. Costs nothing extra. A diarized recording is read in one pass, which is slower to return and holds the recording under 9 minutes. One that long or longer answers 400 diarization_audio_too_long, uncharged. The same recording transcribes normally with this field off. GET /v1/models reports the ceiling as diarization_limit_seconds.","type":"boolean"},"style":{"default":"verbatim","description":"How faithfully the transcript follows the speech. \"verbatim\", the default, writes exactly what was said: fillers, false starts, and self-corrections included. \"clean\" drops fillers and merges broken phrases into one, for a transcript that is easier to read. It is a readability choice. A clean transcript departs further from what was said, and our own measurement found it less exact. Any other value answers 400 validation.","anyOf":[{"type":"string","const":"verbatim"},{"type":"string","const":"clean"}]},"convention":{"default":"spoken","description":"How the transcript writes what was said. \"spoken\", the default, writes numbers as they were said (สามสิบเปอร์เซ็นต์) and a word said twice as two words, which is what a verbatim transcript, a subtitle, or a language model reading the text needs. \"written\" keeps display conventions: digits and units (30%), the repetition mark ๆ, for dictated text that will be read on a page. The word timestamps are the same under both.","anyOf":[{"type":"string","const":"spoken"},{"type":"string","const":"written"}]},"vocabulary":{"maxItems":50,"description":"Terms the speech is likely to contain that the model should recognize as written: product names, people, places, jargon, in Thai or any other language. Up to 50 terms of 50 characters each, and a longer list or term answers 400 validation. The terms bias recognition and are never inserted; a term that was not said does not appear. Costs nothing extra and combines with every other option.","examples":[["Paxa Labs","กรุงเทพฯ","quarterly review"]],"type":"array","items":{"minLength":1,"maxLength":50,"type":"string"}},"subtitles":{"description":"Renders the transcript as a subtitle file and returns it in the response's subtitles field. \"srt\" writes SubRip, \"vtt\" writes WebVTT. Cues break at a speaker change, at sentence punctuation, and at a pause, then at the line budget, over at most two lines. Every break falls on a word boundary. That is the whole difference on Thai. Thai writes no spaces, and a subtitle cut by character count splits words down the middle. The word spans say where a word ends. Word timing is read for you and timestamps need not be set; setting it as well returns the words array beside the file. Costs nothing extra. A recording with no speech renders an empty SubRip file, or a WebVTT file carrying its signature alone.","anyOf":[{"type":"string","const":"srt"},{"type":"string","const":"vtt"}]},"subtitle_line_chars":{"minimum":10,"maximum":120,"description":"Columns per subtitle line, 10 to 120. A Chinese, Japanese, or Korean character or full-width punctuation mark takes two columns. Thai vowels and tone marks sit above and below their consonant and take none. A line of the same figure fills about the same box in every script. Omitted, a line holds 60 columns, or 32 (16 Chinese characters) when most of the transcript's letters are Chinese, Japanese, or Korean. Ignored when subtitles is not set.","type":"integer"}},"required":["audio","model"]}}}},"responses":{"200":{"description":"The transcribed recording.","headers":{"x-request-id":{"description":"Identifies the request for support and log correlation. Sent on every response, errors included, except the cached catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.","schema":{"type":"string"}},"x-ratelimit-limit":{"description":"Requests per minute the plan allows.","schema":{"type":"string"}},"x-ratelimit-remaining":{"description":"Requests left in the current minute window.","schema":{"type":"string"}},"x-ratelimit-reset":{"description":"Unix time in seconds when the current window resets.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"description":"The transcribed recording.","type":"object","properties":{"text":{"description":"The transcript, verbatim in the language spoken, with no labels, timestamps, or sound annotations. Empty when the recording carried no speech.","type":"string"},"words":{"description":"One span per recognized word, in spoken order, each with its text and its start and end in seconds. Present when the request set timestamps to \"word\"; empty for a recording with no speech. Each span names its speaker when the request set diarization to true.","type":"array","items":{"type":"object","properties":{"text":{"description":"The spoken input text this span covers. An emotion tag takes no span.","type":"string"},"start":{"description":"Where the span begins in the audio, in seconds.","type":"number"},"end":{"description":"Where the span ends in the audio, in seconds.","type":"number"},"speaker":{"minimum":0,"description":"The speaker, numbered from 0 in order of first appearance in the recording. Nobody is identified.","type":"integer"}},"required":["text","start","end"]}},"segments":{"description":"One entry per speaker turn, in spoken order. Present when the request set diarization to true; empty for a recording with no speech.","type":"array","items":{"type":"object","properties":{"speaker":{"minimum":0,"description":"The speaker, numbered from 0 in order of first appearance in the recording. Nobody is identified.","type":"integer"},"text":{"description":"The turn's text, exactly as the transcript renders it. Consecutive phrases of one speaker are one turn.","type":"string"},"start":{"description":"Where the turn begins in the recording, in seconds.","type":"number"},"end":{"description":"Where the turn ends in the recording, in seconds.","type":"number"}},"required":["speaker","text","start","end"]}},"subtitles":{"description":"The subtitle file in the format the request asked for, ready to write to disk. Present when the request set subtitles.","type":"string"},"usage":{"description":"What the request was billed for.","type":"object","properties":{"seconds":{"description":"Length of the recording this request was billed for, in seconds, exact to a thousandth.","type":"number"},"credits":{"description":"What this delivery cost, in credits, exact to a hundredth. An idempotent replay reports the ORIGINAL request's charge, since that one charge is what paid for this delivery too; your balance moves only once.","type":"number"}},"required":["seconds","credits"]}},"required":["text","usage"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`unknown_model`: The model field does not name a served model.\n\n`audio_invalid`: The audio field could not be read as an MP3, WAV, FLAC, Ogg, M4A, AAC, or WebM recording, or its length could not be read from the container. A damaged or truncated file answers this code. Nothing was charged.\n\n`audio_too_long`: The recording is longer than the model's per-request ceiling. Nothing was charged.\n\n`diarization_audio_too_long`: The recording reaches the length at which speaker attribution stops, which is well below the transcription ceiling. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","unknown_model","audio_invalid","audio_too_long","diarization_audio_too_long"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"402":{"description":"`insufficient_credits`: The account does not have enough credits for this request. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["insufficient_credits"]},"status":{"type":"integer","const":402}}}}}},"403":{"description":"`key_limit`: This API key reached its spending cap. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["key_limit"]},"status":{"type":"integer","const":403}}}}}},"409":{"description":"`idempotency_in_flight`: Another request with this Idempotency-Key is in flight right now.\n\n`idempotency_refunded`: The original request under this Idempotency-Key failed and was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_in_flight","idempotency_refunded"]},"status":{"type":"integer","const":409}}}}}},"413":{"description":"`audio_too_large`: The decoded recording exceeds the model's size ceiling. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["audio_too_large"]},"status":{"type":"integer","const":413}}}}}},"422":{"description":"`idempotency_mismatch`: This Idempotency-Key was already used for a different request.\n\n`content_blocked`: The upstream safety system declined to process this content. The charge was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_mismatch","content_blocked"]},"status":{"type":"integer","const":422}}}}}},"429":{"description":"`rate_limited`: Requests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.\n\n`concurrency_limited`: The account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["rate_limited","concurrency_limited"]},"status":{"type":"integer","const":429}}}}}},"500":{"description":"`internal`: Request state was inconsistent on the server.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["internal"]},"status":{"type":"integer","const":500}}}}}},"502":{"description":"`provider_error`: Model inference failed after the request was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_error"]},"status":{"type":"integer","const":502}}}}}},"503":{"description":"`provider_unavailable`: The model behind this endpoint is not available right now. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_unavailable"]},"status":{"type":"integer","const":503}}}}}}}}},"/v1/audio/transcriptions":{"post":{"operationId":"createTranscriptionOpenAiCompatible","summary":"Transcribe a recording (OpenAI-compatible)","description":"Transcribes a recording from the OpenAI request shape. Point an OpenAI SDK at this server's base URL, authenticate with a Paxa API key, and call audio.transcriptions.create. The recording is sent as a multipart file part, and nothing is base64 encoded. Pricing, ceilings, refusals, and refunds match POST /v1/stt. The response_format field sets the body to json (the default), verbose_json, text, srt, or vtt, and timestamp_granularities picks the segments and words verbose_json carries. Unrecognized OpenAI fields are ignored. The transcript conventions, styles, speaker turns, and the vocabulary are extensions of POST /v1/stt and are not read here. A recording long enough to run past a proxy's idle limit should go to POST /v1/stt, whose JSON body is kept alive while it works.","tags":["STT"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"idempotency-key","in":"header","required":false,"description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","schema":{"minLength":1,"maxLength":200,"pattern":"^[!-~]+$","description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The recording, as a file part. MP3, WAV (PCM), FLAC, Ogg (Opus or Vorbis), M4A, AAC (ADTS), or WebM; the format and the length are read from the bytes. The part's filename and content type are ignored. Same ceilings, pricing, and refusals as the audio field on POST /v1/stt: up to 60 minutes and 26,214,400 bytes."},"model":{"minLength":1,"maxLength":100,"description":"Transcription model id, for example paxa-stt-lite-v1-preview. OpenAI's whisper-1, gpt-4o-transcribe, gpt-4o-mini-transcribe, gpt-4o-mini-transcribe-2025-03-20, gpt-4o-mini-transcribe-2025-12-15 are accepted and run on paxa-stt-lite-v1-preview.","examples":["paxa-stt-lite-v1-preview"],"type":"string"},"language":{"pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","maxLength":16,"description":"The same field OpenAI defines. A BCP 47 tag the recording is expected to be in, such as \"th\". A hint that filters nothing. Omitted, the model detects the language itself.","examples":["th"],"type":"string"},"response_format":{"default":"json","description":"What the response body carries. \"json\" (the default) answers application/json with the transcript in a text field. \"verbose_json\" answers OpenAI's verbose shape: the duration, the text, and the segments and words that timestamp_granularities names. \"text\" answers text/plain with the transcript alone. \"srt\" and \"vtt\" answer the subtitle file, cut the same way POST /v1/stt cuts it, at the default line length. Every format is billed the same, by the recording's length.","anyOf":[{"type":"string","const":"json"},{"type":"string","const":"verbose_json"},{"type":"string","const":"text"},{"type":"string","const":"srt"},{"type":"string","const":"vtt"}]},"timestamp_granularities":{"description":"The same field OpenAI defines, sent as one timestamp_granularities[] form field per value, and read only with verbose_json. \"segment\" fills segments and \"word\" fills words; omitted, segments alone are filled.","type":"array","items":{"anyOf":[{"type":"string","const":"segment"},{"type":"string","const":"word"}]}},"stream":{"type":"string","const":"false","description":"The OpenAI field of the same name. Streamed transcript events are not served here: \"true\" answers validation, and the transcript arrives as one body. WS /v1/stt/live transcribes as the audio arrives."}},"required":["file","model"]}}}},"responses":{"200":{"description":"The transcribed recording, in OpenAI's shape. Returned when response_format is \"json\", \"verbose_json\", or absent; verbose_json adds the duration, segments, and words.","headers":{"x-request-id":{"description":"Identifies the request for support and log correlation. Sent on every response, errors included, except the cached catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.","schema":{"type":"string"}},"x-credits-charged":{"description":"Credits charged for this request, exact to a hundredth. An idempotent replay reports the original charge. Sent on audio responses and on the OpenAI-compatible transcription endpoint, whose body follows OpenAI's shape and carries no usage object. POST /v1/translate, /v1/ocr, /v1/extract, /v1/stt, and /v1/voices/design report the charge in the usage object of the body instead.","schema":{"type":"string"}},"x-ratelimit-limit":{"description":"Requests per minute the plan allows.","schema":{"type":"string"}},"x-ratelimit-remaining":{"description":"Requests left in the current minute window.","schema":{"type":"string"}},"x-ratelimit-reset":{"description":"Unix time in seconds when the current window resets.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"description":"The transcribed recording, in OpenAI's shape. Returned when response_format is \"json\", \"verbose_json\", or absent; verbose_json adds the duration, segments, and words.","type":"object","properties":{"text":{"description":"The transcript, verbatim in the language spoken. Empty when the recording carried no speech.","type":"string"},"language":{"description":"The language hint the request sent, named as OpenAI names languages, such as \"thai\", or an empty string when it sent none. Carried by verbose_json.","type":"string"},"duration":{"description":"The recording's length in seconds, carried by verbose_json.","type":"number"},"segments":{"description":"The transcript cut into the cues POST /v1/stt cuts for subtitles, carried by verbose_json. OpenAI's decoding statistics are not measured. Their fields carry 0 and an empty tokens list, for clients that require OpenAI's full segment shape.","type":"array","items":{"type":"object","properties":{"id":{"description":"The segment's position, from 0.","type":"integer"},"seek":{"description":"Always 0.","type":"integer"},"start":{"description":"Start, in seconds.","type":"number"},"end":{"description":"End, in seconds.","type":"number"},"text":{"description":"The segment's text, as the transcript renders it.","type":"string"},"tokens":{"description":"Always empty.","type":"array","items":{"type":"integer"}},"temperature":{"description":"Always 0.","type":"number"},"avg_logprob":{"description":"Always 0.","type":"number"},"compression_ratio":{"description":"Always 0.","type":"number"},"no_speech_prob":{"description":"Always 0.","type":"number"}},"required":["id","seek","start","end","text","tokens","temperature","avg_logprob","compression_ratio","no_speech_prob"]}},"words":{"description":"One entry per word, carried by verbose_json when timestamp_granularities names \"word\".","type":"array","items":{"type":"object","properties":{"word":{"description":"The word, with Thai segmented.","type":"string"},"start":{"description":"Start, in seconds.","type":"number"},"end":{"description":"End, in seconds.","type":"number"}},"required":["word","start","end"]}}},"required":["text"]}},"text/plain":{"schema":{"type":"string","description":"The text body response_format \"text\" chooses, in UTF-8."}},"application/x-subrip":{"schema":{"type":"string","description":"The srt body response_format \"srt\" chooses, in UTF-8."}},"text/vtt":{"schema":{"type":"string","description":"The vtt body response_format \"vtt\" chooses, in UTF-8."}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`unknown_model`: The model field does not name a served model.\n\n`audio_invalid`: The audio field could not be read as an MP3, WAV, FLAC, Ogg, M4A, AAC, or WebM recording, or its length could not be read from the container. A damaged or truncated file answers this code. Nothing was charged.\n\n`audio_too_long`: The recording is longer than the model's per-request ceiling. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","unknown_model","audio_invalid","audio_too_long"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"402":{"description":"`insufficient_credits`: The account does not have enough credits for this request. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["insufficient_credits"]},"status":{"type":"integer","const":402}}}}}},"403":{"description":"`key_limit`: This API key reached its spending cap. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["key_limit"]},"status":{"type":"integer","const":403}}}}}},"409":{"description":"`idempotency_in_flight`: Another request with this Idempotency-Key is in flight right now.\n\n`idempotency_refunded`: The original request under this Idempotency-Key failed and was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_in_flight","idempotency_refunded"]},"status":{"type":"integer","const":409}}}}}},"413":{"description":"`audio_too_large`: The decoded recording exceeds the model's size ceiling. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["audio_too_large"]},"status":{"type":"integer","const":413}}}}}},"422":{"description":"`idempotency_mismatch`: This Idempotency-Key was already used for a different request.\n\n`content_blocked`: The upstream safety system declined to process this content. The charge was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_mismatch","content_blocked"]},"status":{"type":"integer","const":422}}}}}},"429":{"description":"`rate_limited`: Requests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.\n\n`concurrency_limited`: The account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["rate_limited","concurrency_limited"]},"status":{"type":"integer","const":429}}}}}},"500":{"description":"`internal`: Request state was inconsistent on the server.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["internal"]},"status":{"type":"integer","const":500}}}}}},"502":{"description":"`provider_error`: Model inference failed after the request was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_error"]},"status":{"type":"integer","const":502}}}}}},"503":{"description":"`provider_unavailable`: The model behind this endpoint is not available right now. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_unavailable"]},"status":{"type":"integer","const":503}}}}}}}}},"/v1/ocr":{"post":{"operationId":"createOcr","summary":"Read a document","description":"Reads a PDF or image and returns its text, as Markdown or as typed layout blocks, one entry per page. Cost is deterministic from the page count, which is read from the document before the charge. When reading fails after the charge, it is refunded automatically and the response is 502, or 422 content_blocked when the content is declined.","tags":["OCR"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"idempotency-key","in":"header","required":false,"description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","schema":{"minLength":1,"maxLength":200,"pattern":"^[!-~]+$","description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document":{"minLength":1,"maxLength":41000000,"description":"The document to read, as the base64 encoding of a PDF, PNG, JPEG, or WebP file. An image counts as one page. Cost is 6.5 credits per page, charged before reading and refunded automatically when reading fails. A PDF may carry up to 50 pages, and more answers 400 too_many_pages. The decoded file may be up to 10,485,760 bytes, and larger answers 413 document_too_large. A file that cannot be read as one of these formats answers 400 document_invalid, and a PDF that needs a password to open answers 400 document_password_required. GET /v1/models reports the ceilings as max_pages and max_bytes.","type":"string"},"model":{"minLength":1,"maxLength":100,"description":"OCR model id, for example paxa-ocr-lite-v1. GET /v1/models lists the served catalog.","examples":["paxa-ocr-lite-v1"],"type":"string"},"output":{"default":"markdown","description":"Shape of the result. \"markdown\", the default, returns each page as GitHub-flavored Markdown. \"structured\" returns each page as typed layout blocks, for callers that feed the reading into a pipeline.","anyOf":[{"type":"string","const":"markdown"},{"type":"string","const":"structured"}]}},"required":["document","model"]}}}},"responses":{"200":{"description":"The recognized document.","headers":{"x-request-id":{"description":"Identifies the request for support and log correlation. Sent on every response, errors included, except the cached catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.","schema":{"type":"string"}},"x-ratelimit-limit":{"description":"Requests per minute the plan allows.","schema":{"type":"string"}},"x-ratelimit-remaining":{"description":"Requests left in the current minute window.","schema":{"type":"string"}},"x-ratelimit-reset":{"description":"Unix time in seconds when the current window resets.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"description":"The recognized document.","type":"object","properties":{"pages":{"description":"One entry per page of the document, in page order.","type":"array","items":{"type":"object","properties":{"page":{"description":"Page number, starting at 1. An image request has exactly one page.","type":"integer"},"markdown":{"description":"The page's content as GitHub-flavored Markdown, in reading order. Present when output is \"markdown\". Join pages with a blank line to rebuild the document.","type":"string"},"blocks":{"description":"The page's content as typed blocks in reading order. Present when output is \"structured\".","type":"array","items":{"type":"object","properties":{"type":{"description":"What the block is: \"heading\", \"paragraph\", \"list\", \"table\", or \"figure\". Blocks arrive in reading order.","type":"string"},"text":{"description":"The block's text. Present on \"heading\", \"paragraph\", and \"figure\" blocks. A figure's text is its caption or nearby label, and is empty when it has none.","type":"string"},"level":{"description":"Heading depth, starting at 1 for the most prominent. Present on \"heading\" blocks.","type":"integer"},"items":{"description":"The list entries in order. Present on \"list\" blocks.","type":"array","items":{"type":"string"}},"rows":{"description":"The table cells as rows of column values, first row first. Present on \"table\" blocks.","type":"array","items":{"type":"array","items":{"type":"string"}}}},"required":["type"]}}},"required":["page"]}},"usage":{"description":"What the request was billed for.","type":"object","properties":{"pages":{"description":"Pages this request was billed for.","type":"integer"},"credits":{"description":"What this delivery cost, in credits, exact to a hundredth. An idempotent replay reports the ORIGINAL request's charge, since that one charge is what paid for this delivery too; your balance moves only once.","type":"number"}},"required":["pages","credits"]}},"required":["pages","usage"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`unknown_model`: The model field does not name a served model.\n\n`document_invalid`: The document field could not be read as a PDF, PNG, JPEG, or WebP file. A damaged or truncated PDF answers this code. Nothing was charged.\n\n`document_password_required`: The PDF needs a password to open. A PDF that carries permissions-only encryption, the kind that opens without being asked for a password, is read normally. Nothing was charged.\n\n`too_many_pages`: The document has more pages than the model's per-request ceiling. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","unknown_model","document_invalid","document_password_required","too_many_pages"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"402":{"description":"`insufficient_credits`: The account does not have enough credits for this request. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["insufficient_credits"]},"status":{"type":"integer","const":402}}}}}},"403":{"description":"`key_limit`: This API key reached its spending cap. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["key_limit"]},"status":{"type":"integer","const":403}}}}}},"409":{"description":"`idempotency_in_flight`: Another request with this Idempotency-Key is in flight right now.\n\n`idempotency_refunded`: The original request under this Idempotency-Key failed and was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_in_flight","idempotency_refunded"]},"status":{"type":"integer","const":409}}}}}},"413":{"description":"`document_too_large`: The decoded document exceeds the model's size ceiling. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["document_too_large"]},"status":{"type":"integer","const":413}}}}}},"422":{"description":"`idempotency_mismatch`: This Idempotency-Key was already used for a different request.\n\n`content_blocked`: The upstream safety system declined to process this content. The charge was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_mismatch","content_blocked"]},"status":{"type":"integer","const":422}}}}}},"429":{"description":"`rate_limited`: Requests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.\n\n`concurrency_limited`: The account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["rate_limited","concurrency_limited"]},"status":{"type":"integer","const":429}}}}}},"500":{"description":"`internal`: Request state was inconsistent on the server.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["internal"]},"status":{"type":"integer","const":500}}}}}},"502":{"description":"`provider_error`: Model inference failed after the request was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_error"]},"status":{"type":"integer","const":502}}}}}},"503":{"description":"`provider_unavailable`: The model behind this endpoint is not available right now. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_unavailable"]},"status":{"type":"integer","const":503}}}}}}}}},"/v1/extract":{"post":{"operationId":"createExtraction","summary":"Extract fields from a document","description":"Reads a PDF or image and fills in the schema you send: an object of typed fields, each delivered as the span printed in the document or as null with the reason. Cost is deterministic from the page count and the schema's leaf count, both read before the charge. When extraction fails after the charge, it is refunded automatically and the response is 502, or 422 content_blocked when the content is declined. A schema outside the dialect answers 400 schema_invalid naming the field and the reason, uncharged.","tags":["OCR"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"idempotency-key","in":"header","required":false,"description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","schema":{"minLength":1,"maxLength":200,"pattern":"^[!-~]+$","description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document":{"minLength":1,"maxLength":41000000,"description":"The document to extract from, as the base64 encoding of a PDF, PNG, JPEG, or WebP file. An image counts as one page. Cost is 13 credits per page for a schema of up to 50 leaf fields and 19.5 credits per page beyond that, charged before extraction and refunded automatically when extraction fails. A PDF may carry up to 20 pages, and more answers 400 too_many_pages. The decoded file may be up to 10,485,760 bytes, and larger answers 413 document_too_large. A file that cannot be read as one of these formats answers 400 document_invalid, and a PDF that needs a password to open answers 400 document_password_required. Send one document per request. A file holding several documents gets one object, with no promise which document it describes. GET /v1/models reports the ceilings as max_pages, max_bytes, and max_leaves.","type":"string"},"model":{"minLength":1,"maxLength":100,"description":"Extraction model id, for example paxa-doc-extract-v1. The id is the contract version: the dialect, the response shape, and the leaf-counting rule are fixed under it, and a change ships as a new id served beside it. GET /v1/models lists the served catalog.","examples":["paxa-doc-extract-v1"],"type":"string"},"schema":{"additionalProperties":true,"description":"The fields to extract, as an object with one key, fields, mapping each field name to its definition. A definition carries a type, an optional required flag, an optional description of up to 200 characters the model reads, and the keys its type offers. Leaf types: \"string\", \"integer\", \"number\", \"date\", \"time\", \"enum\", \"id\", \"thai_id\", \"email\", \"phone\", \"postal_code\", \"province\", \"bank\", \"insurer\", \"card_scheme\", \"payment_method\", \"legal_form\", \"currency\", \"unit\", \"amount_words\". Containers: \"object\" with its own fields, and \"array\" with items (a leaf or an object) and a required max_items of 1 to 200. Names are 1 to 64 characters of letters, digits, and underscores. At most 3 containers sit below the root. An array holds leaves or objects, and an array inside an array is refused. The schema's leaf count is every leaf once plus every leaf inside an array once per element the array is sized for, and it must stay within the model's max_leaves. A schema outside the dialect answers 400 schema_invalid with the offending path and a reason, uncharged. A serialized schema over 131,072 bytes answers the same code with reason too_large. The Document Extraction guide is the full reference for every type, its keys, and its delivered form.","type":"object","properties":{},"required":[]},"include_pages":{"default":false,"description":"true adds a pages array to the response: the text read from each page as GitHub-flavored Markdown, the reading the fields were extracted from, at no extra charge. Omitted, the response carries the fields alone.","type":"boolean"},"include_evidence":{"default":false,"description":"true adds an evidence object to the response, mapping each field path to the printed span its value was read from, whether or not a value could be read from it. Costs nothing extra. Omitted, the response carries no spans.","type":"boolean"}},"required":["document","model","schema"]}}}},"responses":{"200":{"description":"The filled-in schema.","headers":{"x-request-id":{"description":"Identifies the request for support and log correlation. Sent on every response, errors included, except the cached catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.","schema":{"type":"string"}},"x-ratelimit-limit":{"description":"Requests per minute the plan allows.","schema":{"type":"string"}},"x-ratelimit-remaining":{"description":"Requests left in the current minute window.","schema":{"type":"string"}},"x-ratelimit-reset":{"description":"Unix time in seconds when the current window resets.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"description":"The filled-in schema.","type":"object","properties":{"status":{"description":"\"complete\" when every required field carries a value, \"incomplete\" otherwise. Computed from the fields alone. A file that is not what the schema describes comes back \"incomplete\" with most required fields in missing, which is the wrong-document signal.","anyOf":[{"type":"string","const":"complete"},{"type":"string","const":"incomplete"}]},"fields":{"additionalProperties":true,"description":"Your schema's tree with plain values. A string field carries the printed text and a number or integer field a number. A date is ISO YYYY-MM-DD and a time is 24-hour HH:MM, with seconds when printed. An enum carries one listed value, and a closed-set field the form its format asked for. Null wherever nothing was read. Every value is a span printed in the document, or null. Nothing is inferred, computed, or looked up.","type":"object","properties":{},"required":[]},"missing":{"description":"Every required field delivered as null, for any reason.","type":"array","items":{"description":"A required field that is null. Paths are in dot form, array elements indexed: \"items[2].amount\".","type":"string"}},"unverified":{"description":"Every field delivered as null because its span could not be read as the field's type, each with its reason. Empty when every value stood.","type":"array","items":{"description":"A field whose printed span could not be read as the field's type, or whose value the reading did not carry.","type":"object","properties":{"path":{"description":"The field delivered as null. Paths are in dot form, array elements indexed: \"items[2].amount\".","type":"string"},"reason":{"description":"Why the span could not stand as the field's value: one of not_in_readout, not_as_printed, no_number, ambiguous_number, not_an_integer, no_date, ambiguous_date, no_time, ambiguous_time, not_an_enum_value, ambiguous_match, not_13_digits, bad_checksum, wrong_digit_count, not_an_email, not_a_phone, not_a_postal_code, no_amount_words, below_min, above_max, too_many_decimals, too_short, too_long, pattern_mismatch, before_min, after_max, in_the_future. The Document Extraction reference states each one.","type":"string"}},"required":["path","reason"]}},"truncated":{"description":"Every array whose document carried more elements than it was sized for. The first max_items elements are in fields, in reading order. Raise max_items to read the rest. Empty when every array fit.","type":"array","items":{"description":"An array that carried more than max_items. Paths are in dot form, array elements indexed: \"items[2].amount\".","type":"string"}},"assumed":{"description":"Every date whose year was printed with two digits, with the digits and the year delivered. Always present and empty when nothing was assumed. A caller who would rather decide the era reads it here.","type":"array","items":{"description":"A two-digit year the reading resolved to an era. A four-digit year is never listed.","type":"object","properties":{"path":{"description":"The date field. Paths are in dot form, array elements indexed: \"items[2].amount\".","type":"string"},"printed":{"description":"The two digits of the year as printed, for example \"69\".","type":"string"},"read_as":{"description":"The four-digit year delivered in the field, in the Common Era.","type":"integer"}},"required":["path","printed","read_as"]}},"evidence":{"additionalProperties":true,"description":"Field path to the printed span the value was read from, for every field whose span the reading carries. Present when the request set include_evidence to true.","type":"object","properties":{},"required":[]},"pages":{"description":"One entry per page of the document, in page order. The fields were extracted from this reading. Present when the request set include_pages to true.","type":"array","items":{"type":"object","properties":{"page":{"description":"Page number, starting at 1. An image request has exactly one page.","type":"integer"},"markdown":{"description":"The page's content as GitHub-flavored Markdown, in reading order. The fields were read from this text.","type":"string"}},"required":["page","markdown"]}},"usage":{"description":"What the request was billed for.","type":"object","properties":{"pages":{"description":"Pages this request was billed for.","type":"integer"},"leaves":{"description":"The schema's leaf count, which decides the per-page rate: up to 50 bills at credits_per_page, more at large_schema_credits_per_page.","type":"integer"},"credits":{"description":"What this delivery cost, in credits, exact to a hundredth. An idempotent replay reports the ORIGINAL request's charge, since that one charge is what paid for this delivery too; your balance moves only once.","type":"number"}},"required":["pages","leaves","credits"]}},"required":["status","fields","missing","unverified","truncated","assumed","usage"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`unknown_model`: The model field does not name a served model.\n\n`schema_invalid`: The extraction schema is outside the dialect. The problem body carries two extra fields: path, the offending field in dot form (empty for the schema as a whole), and reason, one stable code from the schema reasons list. Nothing was charged.\n\n`document_invalid`: The document field could not be read as a PDF, PNG, JPEG, or WebP file. A damaged or truncated PDF answers this code. Nothing was charged.\n\n`document_password_required`: The PDF needs a password to open. A PDF that carries permissions-only encryption, the kind that opens without being asked for a password, is read normally. Nothing was charged.\n\n`too_many_pages`: The document has more pages than the model's per-request ceiling. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","unknown_model","schema_invalid","document_invalid","document_password_required","too_many_pages"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"402":{"description":"`insufficient_credits`: The account does not have enough credits for this request. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["insufficient_credits"]},"status":{"type":"integer","const":402}}}}}},"403":{"description":"`key_limit`: This API key reached its spending cap. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["key_limit"]},"status":{"type":"integer","const":403}}}}}},"409":{"description":"`idempotency_in_flight`: Another request with this Idempotency-Key is in flight right now.\n\n`idempotency_refunded`: The original request under this Idempotency-Key failed and was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_in_flight","idempotency_refunded"]},"status":{"type":"integer","const":409}}}}}},"413":{"description":"`document_too_large`: The decoded document exceeds the model's size ceiling. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["document_too_large"]},"status":{"type":"integer","const":413}}}}}},"422":{"description":"`idempotency_mismatch`: This Idempotency-Key was already used for a different request.\n\n`content_blocked`: The upstream safety system declined to process this content. The charge was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_mismatch","content_blocked"]},"status":{"type":"integer","const":422}}}}}},"429":{"description":"`rate_limited`: Requests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.\n\n`concurrency_limited`: The account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["rate_limited","concurrency_limited"]},"status":{"type":"integer","const":429}}}}}},"500":{"description":"`internal`: Request state was inconsistent on the server.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["internal"]},"status":{"type":"integer","const":500}}}}}},"502":{"description":"`provider_error`: Model inference failed after the request was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_error"]},"status":{"type":"integer","const":502}}}}}},"503":{"description":"`provider_unavailable`: The model behind this endpoint is not available right now. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_unavailable"]},"status":{"type":"integer","const":503}}}}}}}}},"/v1/translate":{"post":{"operationId":"createTranslation","summary":"Translate text","description":"Translates text into Thai from any source language. Options control register, loanword handling, style examples, and an inline glossary. Cost is deterministic from text length, and the request is charged before translation. When translation fails, the charge is refunded automatically and the response is 502, or 422 content_blocked when the content is declined. An array of segments translates in one request, under one charge and one shared document context, and segment boundaries come back as sent.","tags":["Translation"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"idempotency-key","in":"header","required":false,"description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","schema":{"minLength":1,"maxLength":200,"pattern":"^[!-~]+$","description":"Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"description":"The text to translate: one string, or an array of up to 200 segments translated together (one request, one charge, shared document context; segment boundaries are preserved in the response). Translated characters cost 25 credits per 1000. Reference characters, meaning instructions, context, examples, do_not_translate strings, and glossary, cost 8 credits per 1000 because the model reads them without translating them. Both are billed per character, summed, and rounded up once to the next hundredth of a credit, minimum 2 credits per request. This field accepts at most 20,000 characters, counted once per requested rendering, because each rendering produces that much Thai. The whole request accepts at most 200,000 billable characters. The response reports the exact split in usage.","examples":["The quick brown fox jumps over the lazy dog."],"anyOf":[{"anyOf":[{"minLength":1,"description":"One segment of text to translate.","type":"string"},{"type":"object","properties":{"id":{"minLength":1,"maxLength":128,"description":"Your identifier for this segment, echoed on its result. Send ids and your code can match results to inputs by key, with no dependence on position. Ids are never interpreted, and need not be unique.","type":"string"},"text":{"minLength":1,"description":"One segment of text to translate.","type":"string"},"max_length":{"minimum":1,"maximum":20000,"description":"Longest acceptable translation of this segment, in characters. The model compresses the translation to fit and does not truncate it. A value far below what the meaning needs therefore costs fidelity. A result that still exceeds the limit is delivered, with the overrun named in its review member.","type":"integer"},"do_not_translate":{"minItems":1,"maxItems":100,"description":"Strings that must appear in the translation exactly as written: product names, code identifiers, citations. Applied on top of borrowed_words, which decides everything else.","type":"array","items":{"minLength":1,"maxLength":200,"type":"string"}}},"required":["text"]}]},{"minItems":1,"maxItems":200,"type":"array","items":{"anyOf":[{"minLength":1,"description":"One segment of text to translate.","type":"string"},{"type":"object","properties":{"id":{"minLength":1,"maxLength":128,"description":"Your identifier for this segment, echoed on its result. Send ids and your code can match results to inputs by key, with no dependence on position. Ids are never interpreted, and need not be unique.","type":"string"},"text":{"minLength":1,"description":"One segment of text to translate.","type":"string"},"max_length":{"minimum":1,"maximum":20000,"description":"Longest acceptable translation of this segment, in characters. The model compresses the translation to fit and does not truncate it. A value far below what the meaning needs therefore costs fidelity. A result that still exceeds the limit is delivered, with the overrun named in its review member.","type":"integer"},"do_not_translate":{"minItems":1,"maxItems":100,"description":"Strings that must appear in the translation exactly as written: product names, code identifiers, citations. Applied on top of borrowed_words, which decides everything else.","type":"array","items":{"minLength":1,"maxLength":200,"type":"string"}}},"required":["text"]}]}}]},"model":{"minLength":1,"maxLength":100,"description":"Translation model id, for example paxa-translation-lite-v1. GET /v1/models lists the served catalog.","examples":["paxa-translation-lite-v1"],"type":"string"},"source":{"default":"auto","maxLength":16,"description":"Source language as an ISO 639-1 code, or \"auto\" (the default) to detect it per segment. Codes accepted as explicit pins today: en, zh, ja, ko, ar, fr, de, hi, id, ms, pt, ru, es, vi. Auto-detection accepts any input language, including mixed-language text.","type":"string"},"target":{"type":"string","const":"th","default":"th","description":"Target language. Thai is the only target served today; the field exists so more targets can arrive without a breaking change."},"formality":{"default":"auto","description":"Thai register: \"formal\" produces polite written Thai (polite particles where natural, formal vocabulary), \"casual\" produces conversational Thai, and \"auto\" (the default) matches the tone of the source.","anyOf":[{"type":"string","const":"auto"},{"type":"string","const":"formal"},{"type":"string","const":"casual"}]},"borrowed_words":{"default":"auto","description":"How to handle borrowed words. \"auto\", the default, uses the established Thai form where one exists. \"transliterate\" renders borrowed words in Thai script. \"preserve\" keeps technical terms and proper nouns in their original script.","anyOf":[{"type":"string","const":"auto"},{"type":"string","const":"transliterate"},{"type":"string","const":"preserve"}]},"format":{"default":"text","description":"What the segments are written in. \"text\" (the default) treats them as prose. \"markdown\" and \"html\" translate only the human-readable prose. Link targets, code, tags, and attributes come back untouched. Thai is written without spaces between words. An inline tag moved by one character therefore changes where a word breaks, which is why these modes exist.","anyOf":[{"type":"string","const":"text"},{"type":"string","const":"markdown"},{"type":"string","const":"html"}]},"alternatives":{"default":1,"minimum":1,"maximum":5,"description":"How many distinct renderings to return per segment, best first. Above 1 the extras arrive in the alternatives array of each result. Each rendering is a full pass over the text. The text rate therefore multiplies by this number, and so does the text ceiling: asking for 5 renderings leaves a 5th of the character allowance for the text itself. Reference material is read once and charged once. At most 5.","type":"integer"},"do_not_translate":{"minItems":1,"maxItems":100,"description":"Strings that must appear in the translation exactly as written: product names, code identifiers, citations. Applied on top of borrowed_words, which decides everything else.","type":"array","items":{"minLength":1,"maxLength":200,"type":"string"}},"instructions":{"minLength":1,"maxLength":4000,"description":"Free-form direction for the translator, covering tone, register, terminology, house rules, and anything the fixed options cannot express. Applied to every segment. Its characters bill at the reference rate. At most 4,000 characters. Instructions govern style only. They cannot change the response shape. They cannot make the model act on the text it is translating either.","examples":["Write for a hotel front desk. Keep room names in English."],"type":"string"},"context":{"minLength":1,"maxLength":100000,"description":"Background that conditions the translation without being translated: the surrounding document, the domain, or the intended audience. Its characters bill at the reference rate. At most 100,000 characters.","type":"string"},"examples":{"minItems":1,"maxItems":100,"description":"Style examples the translation imitates in tone, register, and phrasing. Accepts up to 100 source and target pairs. Both sides of every pair bill at the reference rate.","type":"array","items":{"type":"object","properties":{"source":{"minLength":1,"maxLength":2000,"description":"A source sentence in the style to follow.","type":"string"},"target":{"minLength":1,"maxLength":2000,"description":"Its preferred Thai translation.","type":"string"}},"required":["source","target"]}},"glossary":{"minItems":1,"maxItems":5000,"description":"Terminology the translation must use. Accepts up to 5,000 term pairs. The model applies each term contextually, inflecting, casing, and placing it grammatically. Send a whole glossary. Terms whose source does not appear in the text are dropped before the request runs. Dropped terms are neither sent to the model nor billed. Matching ignores case, accents, and typographic apostrophes. It sees through suffixes and German compounds. It needs no word boundaries, and works in Chinese, Japanese, and Thai. Both sides of every surviving pair bill at the reference rate. The response reports how many were sent.","type":"array","items":{"type":"object","properties":{"source":{"minLength":1,"maxLength":200,"description":"The term as it appears in the source text.","type":"string"},"target":{"minLength":1,"maxLength":200,"description":"The Thai rendering to use for it.","type":"string"},"always":{"default":false,"description":"Send this term even when it does not appear in the text. Use it for a term that governs a concept, such as an abbreviation the text spells out. Use it also when inflection changes the stem of the word, as an irregular plural or a Russian case ending does.","type":"boolean"}},"required":["source","target"]}},"glossary_mode":{"default":"matched","description":"How to handle the glossary. \"matched\", the default, sends and bills only the terms whose source appears in the text. \"all\" sends and bills every term you attach, for callers who would rather pay than reason about matching.","anyOf":[{"type":"string","const":"matched"},{"type":"string","const":"all"}]}},"required":["text","model"]}}}},"responses":{"200":{"description":"The translated text.","headers":{"x-request-id":{"description":"Identifies the request for support and log correlation. Sent on every response, errors included, except the cached catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.","schema":{"type":"string"}},"x-ratelimit-limit":{"description":"Requests per minute the plan allows.","schema":{"type":"string"}},"x-ratelimit-remaining":{"description":"Requests left in the current minute window.","schema":{"type":"string"}},"x-ratelimit-reset":{"description":"Unix time in seconds when the current window resets.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"description":"The translated text.","type":"object","properties":{"translations":{"description":"One entry per input segment in input order; exactly one entry when text was a single string.","type":"array","items":{"type":"object","properties":{"id":{"description":"The id you gave this segment, echoed back. Absent when the segment was sent as a bare string.","type":"string"},"text":{"description":"The Thai translation of this segment.","type":"string"},"detected_source":{"description":"The detected (or explicitly pinned) source language of this segment as an ISO 639-1 code; \"und\" when detection is inconclusive.","type":"string"},"alternatives":{"description":"Further renderings of this segment, best first, when alternatives was above 1. The primary rendering stays in text and is not repeated here.","type":"array","items":{"type":"string"}},"review":{"description":"Present only when a delivered translation is worth checking. These are our own checks on the result. They never fail or refuse a request, and the translation is delivered and charged either way. A response whose segments carry no review member passed every check.","type":"object","properties":{"reason":{"description":"Why this segment is worth a look. `empty`, the model returned nothing. `untranslated`, the output matches the input. `glossary_term_missing`, a term you sent does not appear. `preserved_span_missing`, a do_not_translate string is absent. `too_long`, the result exceeds max_length. `markup_changed`, the markup structure differs.","type":"string"},"detail":{"description":"The specific term, span, or measurement behind the reason, when there is one.","type":"string"}},"required":["reason"]}},"required":["text","detected_source"]}},"usage":{"description":"What the request was billed for.","type":"object","properties":{"text_chars":{"description":"Characters of text, summed across segments.","type":"integer"},"reference_chars":{"description":"Characters the model read without translating: instructions, context, examples, do_not_translate strings, and the glossary terms actually sent.","type":"integer"},"billable_chars":{"description":"text_chars plus reference_chars.","type":"integer"},"credits":{"description":"What this delivery cost, in credits, exact to a hundredth. An idempotent replay reports the ORIGINAL request's charge, since that one charge is what paid for this delivery too; your balance moves only once.","type":"number"},"glossary_terms_sent":{"description":"Glossary terms that reached the model and were billed for.","type":"integer"},"glossary_terms_available":{"description":"Glossary terms attached to the request. The gap between this and glossary_terms_sent is what pruning dropped.","type":"integer"}},"required":["text_chars","reference_chars","billable_chars","credits","glossary_terms_sent","glossary_terms_available"]}},"required":["translations","usage"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.\n\n`unknown_model`: The model field does not name a served model.\n\n`unsupported_language`: The source field names a language the model does not accept as an explicit pin.\n\n`text_too_long`: The text exceeds the model's per-request character ceiling.\n\n`request_too_large`: The request's billable characters exceed the model's total ceiling. Translation bills every field: instructions, context, examples, do_not_translate strings, and the glossary count toward the ceiling alongside the text.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation","unknown_model","unsupported_language","text_too_long","request_too_large"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"402":{"description":"`insufficient_credits`: The account does not have enough credits for this request. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["insufficient_credits"]},"status":{"type":"integer","const":402}}}}}},"403":{"description":"`key_limit`: This API key reached its spending cap. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["key_limit"]},"status":{"type":"integer","const":403}}}}}},"409":{"description":"`idempotency_in_flight`: Another request with this Idempotency-Key is in flight right now.\n\n`idempotency_refunded`: The original request under this Idempotency-Key failed and was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_in_flight","idempotency_refunded"]},"status":{"type":"integer","const":409}}}}}},"422":{"description":"`idempotency_mismatch`: This Idempotency-Key was already used for a different request.\n\n`content_blocked`: The upstream safety system declined to process this content. The charge was refunded.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["idempotency_mismatch","content_blocked"]},"status":{"type":"integer","const":422}}}}}},"429":{"description":"`rate_limited`: Requests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.\n\n`concurrency_limited`: The account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["rate_limited","concurrency_limited"]},"status":{"type":"integer","const":429}}}}}},"500":{"description":"`internal`: Request state was inconsistent on the server.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["internal"]},"status":{"type":"integer","const":500}}}}}},"502":{"description":"`provider_error`: Model inference failed after the request was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_error"]},"status":{"type":"integer","const":502}}}}}},"503":{"description":"`provider_unavailable`: The model behind this endpoint is not available right now. Nothing was charged.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["provider_unavailable"]},"status":{"type":"integer","const":503}}}}}}}}},"/v1/models":{"get":{"operationId":"listModels","summary":"List models","description":"Returns the served model catalog: every model id with its per-request character ceilings, pricing, and the ids of its voices. This endpoint is public and needs no API key.","tags":["Catalog"],"security":[],"parameters":[],"responses":{"200":{"description":"The served model catalog.","headers":{},"content":{"application/json":{"schema":{"description":"The served model catalog.","type":"object","properties":{"models":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Model id to send as the model field.","type":"string"},"name":{"description":"Display name.","type":"string"},"product":{"description":"Capability of the model: \"tts\", \"translation\", \"ocr\", \"extraction\", or \"stt\".","type":"string"},"max_chars":{"description":"Per-request ceiling on the text field in characters. Null for models that read files, such as OCR, Document Extraction, and transcription.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_request_chars":{"description":"Ceiling on the request's total billable characters, where that differs from max_chars. Null when it does not. Translation models bill every field. This ceiling bounds the text together with instructions, context, examples, do_not_translate strings, and glossary.","anyOf":[{"type":"integer"},{"type":"null"}]},"credits_per_1k_chars":{"description":"Cost in credits per 1000 input characters, billed per character and rounded up to the next hundredth of a credit. TTS requests that set timestamps bill at 1.25x this rate. Null for models billed by page or by recording length.","anyOf":[{"type":"integer"},{"type":"null"}]},"reference_credits_per_1k_chars":{"description":"Cost in credits per 1000 characters of material the model reads without translating: instructions, context, examples, do_not_translate strings, and glossary. Null for models that have no such material.","anyOf":[{"type":"integer"},{"type":"null"}]},"credits_per_page":{"description":"Cost in credits per page for models billed by page, such as OCR and Document Extraction, exact to a hundredth. Null for models billed otherwise.","anyOf":[{"type":"number"},{"type":"null"}]},"large_schema_credits_per_page":{"description":"Cost in credits per page a Document Extraction request takes when its schema declares large_schema_min_leaves leaf fields or more. Null for other models.","anyOf":[{"type":"number"},{"type":"null"}]},"large_schema_min_leaves":{"description":"Leaf count from which a Document Extraction schema bills at large_schema_credits_per_page. Null for other models.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_leaves":{"description":"Most leaf fields one Document Extraction schema may declare, counted after arrays and nested objects are unfolded; more answers 400 schema_invalid. Null for other models.","anyOf":[{"type":"integer"},{"type":"null"}]},"credits_per_hour":{"description":"Cost in credits per hour of audio for models billed by audio length: a recording's exact length, or the audio received over a realtime connection. Null for models billed otherwise.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_pages":{"description":"Most pages one request accepts; null for models not billed by page. Document Extraction's ceiling is lower than OCR's.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_duration_seconds":{"description":"Longest recording one request accepts, in seconds; null for models that do not read audio.","anyOf":[{"type":"integer"},{"type":"null"}]},"diarization_limit_seconds":{"description":"Length at which speaker attribution stops, in seconds, well below max_duration_seconds. With diarization on, a recording this long or longer answers 400 diarization_audio_too_long; with it off, it transcribes normally. Null for models that do not read audio.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_session_seconds":{"description":"Most audio one realtime connection accepts, in seconds; reaching it ends the session as an end frame would. Null for models that do not serve a connection.","anyOf":[{"type":"integer"},{"type":"null"}]},"encodings":{"description":"Audio encodings a realtime connection's start frame may declare, each with the sample rates it accepts. Null for models that do not serve a connection.","anyOf":[{"type":"array","items":{"type":"object","properties":{"encoding":{"type":"string"},"sample_rates":{"type":"array","items":{"type":"integer"}}},"required":["encoding","sample_rates"]}},{"type":"null"}]},"max_bytes":{"description":"Largest decoded file one request accepts, in bytes. Null for models that read text.","anyOf":[{"type":"integer"},{"type":"null"}]},"formats":{"description":"File formats the model reads: \"pdf\", \"png\", \"jpeg\", \"webp\" for OCR and Document Extraction; \"mp3\", \"wav\", \"flac\", \"ogg\", \"m4a\", \"aac\", \"webm\" for transcription. Null for models that read text.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"min_request_credits":{"description":"Smallest charge one request to this model can incur, in credits.","type":"number"},"voices":{"description":"Ids of the voices on this model's roster, the values the voice field accepts. GET /v1/voices carries each voice's details. Empty for models without voices.","type":"array","items":{"type":"string"}},"sources":{"description":"Source language codes the translation source field accepts alongside \"auto\"; null for non-translation models.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"target":{"description":"Target language code the model translates into; null for non-translation models.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","product","max_chars","max_request_chars","credits_per_1k_chars","reference_credits_per_1k_chars","credits_per_page","large_schema_credits_per_page","large_schema_min_leaves","max_leaves","credits_per_hour","max_pages","max_duration_seconds","diarization_limit_seconds","max_session_seconds","encodings","max_bytes","formats","min_request_credits","voices","sources","target"]}},"object":{"type":"string","const":"list"},"data":{"description":"The same models in OpenAI's list shape. A client built on an OpenAI SDK lists them with models.list(), and models carries the detail.","type":"array","items":{"type":"object","properties":{"id":{"description":"Model id, as in models.","type":"string"},"object":{"type":"string","const":"model"},"created":{"description":"Unix time of the UTC day the model opened, in seconds.","type":"integer"},"owned_by":{"type":"string","const":"paxalabs"}},"required":["id","object","created","owned_by"]}}},"required":["models","object","data"]}}}}}}},"/v1/voices":{"get":{"operationId":"listVoices","summary":"List voices","description":"Returns the voice roster across every TTS model. Each id is the value the voice field accepts, on POST /v1/tts and on the OpenAI-compatible endpoint alike, and emotion_tags names the tags a voice speaks. This endpoint is public and needs no API key.","tags":["Catalog"],"security":[],"parameters":[],"responses":{"200":{"description":"Every served voice, once per TTS model that serves it; model tells the entries apart.","headers":{},"content":{"application/json":{"schema":{"description":"Every served voice, once per TTS model that serves it; model tells the entries apart.","type":"object","properties":{"voices":{"type":"array","items":{"type":"object","properties":{"id":{"description":"The stable voice identifier the voice field accepts, for example \"khanomkrok\". An identifier is stable across model generations and is never reused.","type":"string"},"name":{"description":"Display name, for example \"Khanom Krok\". For display only; the voice field does not accept it.","type":"string"},"gender":{"description":"Voice gender, \"f\" or \"m\".","type":"string"},"language":{"description":"The one language the voice is designed to speak: \"th\" (Thai), \"en\" (English), or \"zh\" (Mandarin Chinese, Simplified script); it reads the other two with a foreign accent. Null for a voice that speaks all three, as every Paxa TTS Flash v2 voice does.","anyOf":[{"type":"string","const":"th"},{"type":"string","const":"en"},{"type":"string","const":"zh"},{"type":"null"}]},"accent":{"description":"Regional Thai accent: \"isan\", \"northern\", or \"southern\". Null for every other voice.","anyOf":[{"type":"string"},{"type":"null"}]},"experimental":{"description":"True for a voice whose delivery may still change while it is tuned. Its id stays stable like every other voice.","type":"boolean"},"emotion_tags":{"description":"The emotion tag words this voice reads in its text, such as happy in [happy]. Empty for a voice that skips tags, and for every Paxa TTS Flash v2 voice, which reads that model's own tags instead.","type":"array","items":{"type":"string"}},"description":{"description":"One-line English description of the voice's character and intended use.","type":"string"},"model":{"description":"The model serving this voice.","type":"string"}},"required":["id","name","gender","language","accent","experimental","emotion_tags","description","model"]}}},"required":["voices"]}}}}}}},"/v1/audio/voices":{"get":{"operationId":"listAudioVoices","summary":"List voices for OpenAI-compatible apps","description":"Returns the voices of paxa-tts-flash-v2, the model OpenAI's model names run on, at the path OpenAI-compatible apps such as Open WebUI read to fill their voice picker. Each entry is the one GET /v1/voices reports. This endpoint is public and needs no API key.","tags":["Catalog"],"security":[],"parameters":[],"responses":{"200":{"description":"The voices of paxa-tts-flash-v2, the model OpenAI's model names run on, each as GET /v1/voices reports it.","headers":{},"content":{"application/json":{"schema":{"description":"The voices of paxa-tts-flash-v2, the model OpenAI's model names run on, each as GET /v1/voices reports it.","type":"object","properties":{"voices":{"type":"array","items":{"type":"object","properties":{"id":{"description":"The stable voice identifier the voice field accepts, for example \"khanomkrok\". An identifier is stable across model generations and is never reused.","type":"string"},"name":{"description":"Display name, for example \"Khanom Krok\". For display only; the voice field does not accept it.","type":"string"},"gender":{"description":"Voice gender, \"f\" or \"m\".","type":"string"},"language":{"description":"The one language the voice is designed to speak: \"th\" (Thai), \"en\" (English), or \"zh\" (Mandarin Chinese, Simplified script); it reads the other two with a foreign accent. Null for a voice that speaks all three, as every Paxa TTS Flash v2 voice does.","anyOf":[{"type":"string","const":"th"},{"type":"string","const":"en"},{"type":"string","const":"zh"},{"type":"null"}]},"accent":{"description":"Regional Thai accent: \"isan\", \"northern\", or \"southern\". Null for every other voice.","anyOf":[{"type":"string"},{"type":"null"}]},"experimental":{"description":"True for a voice whose delivery may still change while it is tuned. Its id stays stable like every other voice.","type":"boolean"},"emotion_tags":{"description":"The emotion tag words this voice reads in its text, such as happy in [happy]. Empty for a voice that skips tags, and for every Paxa TTS Flash v2 voice, which reads that model's own tags instead.","type":"array","items":{"type":"string"}},"description":{"description":"One-line English description of the voice's character and intended use.","type":"string"},"model":{"description":"The model serving this voice.","type":"string"}},"required":["id","name","gender","language","accent","experimental","emotion_tags","description","model"]}}},"required":["voices"]}}}}}}},"/v1/audio/models":{"get":{"operationId":"listAudioModels","summary":"List speech models for OpenAI-compatible apps","description":"Returns paxa-tts-flash-v2, the model OpenAI's model names run on, at the path OpenAI-compatible apps such as Open WebUI read to fill their model picker. Every voice GET /v1/audio/voices lists runs on it. The entry is the one GET /v1/models reports. This endpoint is public and needs no API key.","tags":["Catalog"],"security":[],"parameters":[],"responses":{"200":{"description":"paxa-tts-flash-v2, the model OpenAI's model names run on, as GET /v1/models reports it.","headers":{},"content":{"application/json":{"schema":{"description":"paxa-tts-flash-v2, the model OpenAI's model names run on, as GET /v1/models reports it.","type":"object","properties":{"models":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Model id to send as the model field.","type":"string"},"name":{"description":"Display name.","type":"string"},"product":{"description":"Capability of the model: \"tts\", \"translation\", \"ocr\", \"extraction\", or \"stt\".","type":"string"},"max_chars":{"description":"Per-request ceiling on the text field in characters. Null for models that read files, such as OCR, Document Extraction, and transcription.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_request_chars":{"description":"Ceiling on the request's total billable characters, where that differs from max_chars. Null when it does not. Translation models bill every field. This ceiling bounds the text together with instructions, context, examples, do_not_translate strings, and glossary.","anyOf":[{"type":"integer"},{"type":"null"}]},"credits_per_1k_chars":{"description":"Cost in credits per 1000 input characters, billed per character and rounded up to the next hundredth of a credit. TTS requests that set timestamps bill at 1.25x this rate. Null for models billed by page or by recording length.","anyOf":[{"type":"integer"},{"type":"null"}]},"reference_credits_per_1k_chars":{"description":"Cost in credits per 1000 characters of material the model reads without translating: instructions, context, examples, do_not_translate strings, and glossary. Null for models that have no such material.","anyOf":[{"type":"integer"},{"type":"null"}]},"credits_per_page":{"description":"Cost in credits per page for models billed by page, such as OCR and Document Extraction, exact to a hundredth. Null for models billed otherwise.","anyOf":[{"type":"number"},{"type":"null"}]},"large_schema_credits_per_page":{"description":"Cost in credits per page a Document Extraction request takes when its schema declares large_schema_min_leaves leaf fields or more. Null for other models.","anyOf":[{"type":"number"},{"type":"null"}]},"large_schema_min_leaves":{"description":"Leaf count from which a Document Extraction schema bills at large_schema_credits_per_page. Null for other models.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_leaves":{"description":"Most leaf fields one Document Extraction schema may declare, counted after arrays and nested objects are unfolded; more answers 400 schema_invalid. Null for other models.","anyOf":[{"type":"integer"},{"type":"null"}]},"credits_per_hour":{"description":"Cost in credits per hour of audio for models billed by audio length: a recording's exact length, or the audio received over a realtime connection. Null for models billed otherwise.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_pages":{"description":"Most pages one request accepts; null for models not billed by page. Document Extraction's ceiling is lower than OCR's.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_duration_seconds":{"description":"Longest recording one request accepts, in seconds; null for models that do not read audio.","anyOf":[{"type":"integer"},{"type":"null"}]},"diarization_limit_seconds":{"description":"Length at which speaker attribution stops, in seconds, well below max_duration_seconds. With diarization on, a recording this long or longer answers 400 diarization_audio_too_long; with it off, it transcribes normally. Null for models that do not read audio.","anyOf":[{"type":"integer"},{"type":"null"}]},"max_session_seconds":{"description":"Most audio one realtime connection accepts, in seconds; reaching it ends the session as an end frame would. Null for models that do not serve a connection.","anyOf":[{"type":"integer"},{"type":"null"}]},"encodings":{"description":"Audio encodings a realtime connection's start frame may declare, each with the sample rates it accepts. Null for models that do not serve a connection.","anyOf":[{"type":"array","items":{"type":"object","properties":{"encoding":{"type":"string"},"sample_rates":{"type":"array","items":{"type":"integer"}}},"required":["encoding","sample_rates"]}},{"type":"null"}]},"max_bytes":{"description":"Largest decoded file one request accepts, in bytes. Null for models that read text.","anyOf":[{"type":"integer"},{"type":"null"}]},"formats":{"description":"File formats the model reads: \"pdf\", \"png\", \"jpeg\", \"webp\" for OCR and Document Extraction; \"mp3\", \"wav\", \"flac\", \"ogg\", \"m4a\", \"aac\", \"webm\" for transcription. Null for models that read text.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"min_request_credits":{"description":"Smallest charge one request to this model can incur, in credits.","type":"number"},"voices":{"description":"Ids of the voices on this model's roster, the values the voice field accepts. GET /v1/voices carries each voice's details. Empty for models without voices.","type":"array","items":{"type":"string"}},"sources":{"description":"Source language codes the translation source field accepts alongside \"auto\"; null for non-translation models.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"target":{"description":"Target language code the model translates into; null for non-translation models.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","product","max_chars","max_request_chars","credits_per_1k_chars","reference_credits_per_1k_chars","credits_per_page","large_schema_credits_per_page","large_schema_min_leaves","max_leaves","credits_per_hour","max_pages","max_duration_seconds","diarization_limit_seconds","max_session_seconds","encodings","max_bytes","formats","min_request_credits","voices","sources","target"]}}},"required":["models"]}}}}}}},"/v1/me":{"get":{"operationId":"getAccount","summary":"Get key, balance, and plan","description":"Returns the presented API key with its identity and spend, the account's live credit balance, and the effective plan with its rate limit and concurrency. Poll this endpoint when reconciling usage.","tags":["Account"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"responses":{"200":{"description":"Key, balance, and plan for the authenticated account.","headers":{},"content":{"application/json":{"schema":{"description":"Key, balance, and plan for the authenticated account.","type":"object","properties":{"key":{"description":"The API key that authenticated this request.","type":"object","properties":{"id":{"description":"Key id.","type":"string"},"name":{"description":"The label given to the key at creation.","type":"string"},"key_start":{"description":"First characters of the key, for display.","type":"string"},"credit_limit":{"description":"Spending cap for this key in credits; null means uncapped.","anyOf":[{"type":"number"},{"type":"null"}]},"credits_spent":{"description":"Credits this key has spent, exact to a hundredth (may lag live spend by up to 5 minutes).","type":"number"},"created_at":{"description":"Creation time, ISO 8601.","type":"string"},"last_used_at":{"description":"Last verified use, ISO 8601; null when never used.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","key_start","credit_limit","credits_spent","created_at","last_used_at"]},"balance":{"description":"Live credit balance of the account, exact to a hundredth of a credit.","type":"object","properties":{"wallet":{"description":"Purchased and granted credits; never expire.","type":"number"},"plan":{"description":"Monthly plan credits; reset each billing period and spend first.","type":"number"},"total":{"description":"wallet plus plan.","type":"number"}},"required":["wallet","plan","total"]},"plan":{"description":"The effective plan.","type":"object","properties":{"key":{"description":"Plan key: \"free\", \"starter\", \"standard\", or \"pro\".","type":"string"},"rpm":{"description":"Requests per minute the plan allows; null means no limit. One window covers the whole account, across every product.","anyOf":[{"type":"integer"},{"type":"null"}]},"concurrency":{"description":"Concurrent requests the plan allows, counted per product. An open live connection holds one slot of its product.","type":"integer"},"monthly_credits":{"description":"Credits granted each billing period; 0 on the free plan.","type":"integer"}},"required":["key","rpm","concurrency","monthly_credits"]}},"required":["key","balance","plan"]}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}}}}},"/v1/feedback":{"post":{"operationId":"createFeedback","summary":"Send feedback","description":"Reports a wrong or poor output, an API problem, or a platform issue to the Paxa team. Name the requests by their x-request-id values so the report links to your logs. A rating on named requests is a complete report on its own. Sending feedback costs nothing and takes no rate-limit token; GET /v1/feedback shows each report's status and the team's note.","tags":["Account"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"A report needs a message, an example, or a rating on at least one request id. Omit a field you do not send; null fails validation. Keys shaped like pxa_... in the text are replaced with pxa_[redacted] before storage.","type":"object","properties":{"kind":{"description":"What the report is about. Use model when an output was wrong or poor, api when the API misbehaved, platform for the dashboard, billing, or docs, and other for anything else.","anyOf":[{"type":"string","const":"model"},{"type":"string","const":"api"},{"type":"string","const":"platform"},{"type":"string","const":"other"}]},"rating":{"description":"Your judgment of the output the request ids name. A rating alone is a complete report when request_ids names at least one request.","anyOf":[{"type":"string","const":"good"},{"type":"string","const":"bad"}]},"message":{"maxLength":10000,"description":"What happened and what you expected, in plain text or Markdown.","type":"string"},"request_ids":{"maxItems":20,"description":"Up to 20 x-request-id values the report is about, or the connection id of a live session. They link the report to the requests in your logs.","type":"array","items":{"pattern":"^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$","type":"string"}},"example":{"description":"A worked case as text, with the exact input, the output you received, and the output you expected.","type":"object","properties":{"input":{"maxLength":10000,"description":"The input you sent.","type":"string"},"output":{"maxLength":10000,"description":"The output you received.","type":"string"},"expected":{"maxLength":10000,"description":"The output you expected instead.","type":"string"}},"required":[]}},"required":["kind"]}}}},"responses":{"200":{"description":"One feedback report.","headers":{},"content":{"application/json":{"schema":{"description":"One feedback report.","type":"object","properties":{"id":{"description":"Report id. Pass it as before to list older reports.","type":"string"},"kind":{"description":"What the report is about.","anyOf":[{"type":"string","const":"model"},{"type":"string","const":"api"},{"type":"string","const":"platform"},{"type":"string","const":"other"}]},"rating":{"description":"The rating sent, or null.","anyOf":[{"type":"string","const":"good"},{"type":"string","const":"bad"},{"type":"null"}]},"message":{"description":"The message as stored, or null.","anyOf":[{"type":"string"},{"type":"null"}]},"request_ids":{"description":"The request ids sent, lowercased, deduplicated, and sorted.","type":"array","items":{"type":"string"}},"example":{"description":"The example as stored; every field is null without one.","type":"object","properties":{"input":{"description":"The example input, or null.","anyOf":[{"type":"string"},{"type":"null"}]},"output":{"description":"The example output, or null.","anyOf":[{"type":"string"},{"type":"null"}]},"expected":{"description":"The expected output, or null.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["input","output","expected"]},"source":{"description":"api: sent to this endpoint with a key. app: sent from the dashboard.","anyOf":[{"type":"string","const":"api"},{"type":"string","const":"app"}]},"status":{"description":"A report is open until the team has acted on it, and resolved from then on. A report that carries only a rating feeds quality measures and stays open.","anyOf":[{"type":"string","const":"open"},{"type":"string","const":"resolved"}]},"resolution":{"description":"The team's note on the outcome; null when none was written.","anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"description":"Creation time, ISO 8601.","type":"string"},"resolved_at":{"description":"Resolution time, ISO 8601; null while open.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","kind","rating","message","request_ids","example","source","status","resolution","created_at","resolved_at"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}},"429":{"description":"`feedback_limit`: The account reached a daily feedback cap: 50 written reports, or 1,000 reports that carry only a rating, in the last 24 hours. Nothing was stored.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["feedback_limit"]},"status":{"type":"integer","const":429}}}}}}}},"get":{"operationId":"listFeedback","summary":"List feedback","description":"Lists the account's feedback reports, newest first, with each report's status and the team's note. Filter by request_id to see whether a report on a request already arrived before sending another.","tags":["Account"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Reports per page.","schema":{"minimum":1,"maximum":50,"default":20,"description":"Reports per page.","type":"integer"}},{"name":"before","in":"query","required":false,"description":"A report id from this account. The page starts with the next older report.","schema":{"pattern":"^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$","description":"A report id from this account. The page starts with the next older report.","type":"string"}},{"name":"request_id","in":"query","required":false,"description":"Lists only reports naming this request id. Check it before sending a report again.","schema":{"pattern":"^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$","description":"Lists only reports naming this request id. Check it before sending a report again.","type":"string"}}],"responses":{"200":{"description":"One page of the account's feedback reports.","headers":{},"content":{"application/json":{"schema":{"description":"One page of the account's feedback reports.","type":"object","properties":{"reports":{"description":"The account's reports, newest first.","type":"array","items":{"description":"One feedback report.","type":"object","properties":{"id":{"description":"Report id. Pass it as before to list older reports.","type":"string"},"kind":{"description":"What the report is about.","anyOf":[{"type":"string","const":"model"},{"type":"string","const":"api"},{"type":"string","const":"platform"},{"type":"string","const":"other"}]},"rating":{"description":"The rating sent, or null.","anyOf":[{"type":"string","const":"good"},{"type":"string","const":"bad"},{"type":"null"}]},"message":{"description":"The message as stored, or null.","anyOf":[{"type":"string"},{"type":"null"}]},"request_ids":{"description":"The request ids sent, lowercased, deduplicated, and sorted.","type":"array","items":{"type":"string"}},"example":{"description":"The example as stored; every field is null without one.","type":"object","properties":{"input":{"description":"The example input, or null.","anyOf":[{"type":"string"},{"type":"null"}]},"output":{"description":"The example output, or null.","anyOf":[{"type":"string"},{"type":"null"}]},"expected":{"description":"The expected output, or null.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["input","output","expected"]},"source":{"description":"api: sent to this endpoint with a key. app: sent from the dashboard.","anyOf":[{"type":"string","const":"api"},{"type":"string","const":"app"}]},"status":{"description":"A report is open until the team has acted on it, and resolved from then on. A report that carries only a rating feeds quality measures and stays open.","anyOf":[{"type":"string","const":"open"},{"type":"string","const":"resolved"}]},"resolution":{"description":"The team's note on the outcome; null when none was written.","anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"description":"Creation time, ISO 8601.","type":"string"},"resolved_at":{"description":"Resolution time, ISO 8601; null while open.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","kind","rating","message","request_ids","example","source","status","resolution","created_at","resolved_at"]}},"has_more":{"description":"Older reports exist. Pass the last report's id as before to fetch them.","type":"boolean"}},"required":["reports","has_more"]}}}},"400":{"description":"`validation`: The request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["validation"]},"status":{"type":"integer","const":400}}}}}},"401":{"description":"`unauthorized`: The request carried no API key, or the key is invalid or disabled.","content":{"application/problem+json":{"schema":{"type":"object","required":["title","status"],"properties":{"title":{"type":"string","enum":["unauthorized"]},"status":{"type":"integer","const":401}}}}}}}}},"/health":{"get":{"operationId":"getHealth","summary":"Health check","description":"Reports whether the server is running and which products it can serve. The check touches neither the database nor Redis, and answers while those dependencies are down.","tags":["System"],"security":[],"parameters":[],"responses":{"200":{"description":"The server is accepting requests; products and models list what it can serve.","headers":{},"content":{"application/json":{"schema":{"description":"The server is accepting requests; products and models list what it can serve.","type":"object","properties":{"status":{"type":"string","const":"ok"},"products":{"description":"Per-product availability.","type":"object","properties":{"tts":{"description":"Speech synthesis is being served right now, by at least one model.","type":"boolean"},"voice_design":{"description":"Voice design (POST /v1/voices/design) is being served right now.","type":"boolean"},"translation":{"description":"Translation is being served right now.","type":"boolean"},"ocr":{"description":"Document reading is being served right now.","type":"boolean"},"extraction":{"description":"Document Extraction is being served right now.","type":"boolean"},"stt":{"description":"Transcription is being served right now.","type":"boolean"}},"required":["tts","voice_design","translation","ocr","extraction","stt"]},"models":{"type":"object","patternProperties":{"^.*$":{"type":"boolean"}},"description":"Per-model availability, keyed by model id; provider_unavailable errors point here."}},"required":["status","products","models"]}}}}}}}}}