{"openapi":"3.1.0","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"AthanLab Developer API","version":"1.0.0","summary":"Myanmar text-to-speech as asynchronous jobs.","description":"Natural Myanmar text-to-speech over REST. Speech is generated in asynchronous jobs: create a job, poll it, then download the audio.\n\nAccess needs an approved developer application and an active Max plan. Authenticate with an API key in the `X-API-Key` header or as `Authorization: Bearer <key>`, from your server only. Full guide: https://athanlab.com/docs.","termsOfService":"https://athanlab.com/api-terms","contact":{"name":"AthanLab support","email":"support@athanlab.com","url":"https://athanlab.com/docs"},"x-api-terms-version":"2026-10-06"},"externalDocs":{"description":"Guide, quickstart and error reference","url":"https://athanlab.com/docs"},"servers":[{"url":"https://api.athanlab.com/api/v1","description":"Production"}],"security":[{"ApiKeyHeader":[]},{"BearerAuth":[]}],"tags":[{"name":"Speech","description":"Asynchronous speech jobs: create, poll, download, cancel."},{"name":"Deliveries","description":"Send audio to AthanInbox, the key owner's inbox on athanlab.com: a titled text or up to 50 chapters, made in the background and kept in AthanInbox until the owner deletes it."},{"name":"Voices","description":"The voices a key can speak with."},{"name":"Account","description":"The balance your keys draw on."},{"name":"Meta","description":"This document."}],"paths":{"/speech":{"post":{"operationId":"createSpeech","summary":"Create a speech job","description":"Accepts and charges a job, then returns `202 Accepted` at once — the audio is generated in the background. Poll the job at the `Location` header: wait this response's `Retry-After` (2 seconds) before the first poll, then back off to about 10 seconds between polls until it finishes (a successful poll sends no `Retry-After` of its own), then download its audio (https://athanlab.com/docs#polling).\n\nThe charge is the length of `text` in UTF-16 code units (https://athanlab.com/docs#size-limits). Send an `Idempotency-Key` header so a retry never charges twice (https://athanlab.com/docs#idempotency). With `dry_run: true` the response is a `200` quote, and nothing is created or charged (https://athanlab.com/docs#dry-run).\n\nWriting text that sounds natural — sentence punctuation, Burmese and English (made-up names spelled the way they sound in Burmese), and what each `number_mode` reads: https://athanlab.com/docs#writing-text.\n\nRequires the `speech:write` scope. The key's account must be on the Max plan. Refused while the API is in read-only mode.","tags":["Speech"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSpeechRequest"},"examples":{"example":{"value":{"text":"မင်္ဂလာပါ။ အသံလက်ဘ်မှ ကြိုဆိုပါတယ်။","voice_id":"athanlab-default-female-v1","output_format":"mp3","metadata":{"order_id":"A-1042"}}}}}}},"responses":{"200":{"description":"A quote for a `dry_run` request. Nothing was created or charged.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DryRunQuote"},"examples":{"example":{"value":{"object":"speech.quote","characters":35,"dispatches":1,"spendable":1950000,"sufficient":true}}}}}},"202":{"description":"The job was accepted and charged, or an earlier request with the same Idempotency-Key was replayed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"Idempotent-Replayed":{"$ref":"#/components/headers/Idempotent-Replayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSpeechResponse"},"examples":{"example":{"value":{"object":"speech","id":"8f14e45fceea167a5a36dedd4bea2543","status":"processing","created_at":"2026-10-01T08:30:00.000Z","completed_at":null,"progress":0,"input":{"characters":35,"voice_id":"athanlab-default-female-v1","output_format":"mp3","number_mode":"smart"},"usage":{"characters_charged":35,"characters_refunded":0},"audio":null,"error":null,"metadata":{"order_id":"A-1042"},"balance":{"spendable_chars":1949965}}}}}}},"400":{"$ref":"#/components/responses/validation_error"},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"402":{"description":"One of: `plan_required`, `insufficient_characters`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"plan_required":{"$ref":"#/components/examples/plan_required"},"insufficient_characters":{"$ref":"#/components/examples/insufficient_characters"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"404":{"$ref":"#/components/responses/voice_not_found"},"409":{"$ref":"#/components/responses/idempotency_conflict"},"413":{"$ref":"#/components/responses/payload_too_large"},"415":{"$ref":"#/components/responses/unsupported_media_type"},"422":{"description":"One of: `unspeakable_text`, `text_too_long`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unspeakable_text":{"$ref":"#/components/examples/unspeakable_text"},"text_too_long":{"$ref":"#/components/examples/text_too_long"}}}}},"429":{"description":"One of: `rate_limited`, `auth_blocked`, `concurrency_limit`, `key_budget_exceeded`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"},"concurrency_limit":{"$ref":"#/components/examples/concurrency_limit"},"key_budget_exceeded":{"$ref":"#/components/examples/key_budget_exceeded"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"description":"One of: `api_disabled`, `api_read_only`, `server_restarting`, `capacity_unavailable`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"api_disabled":{"$ref":"#/components/examples/api_disabled"},"api_read_only":{"$ref":"#/components/examples/api_read_only"},"server_restarting":{"$ref":"#/components/examples/server_restarting"},"capacity_unavailable":{"$ref":"#/components/examples/capacity_unavailable"}}}}}},"x-required-scope":"speech:write","x-requires-max":true,"x-read-only-mode":"refused","x-error-codes":["payload_too_large","rate_limited","api_disabled","api_read_only","unsupported_media_type","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error","validation_error","idempotency_conflict","plan_required","unspeakable_text","text_too_long","voice_not_found","server_restarting","concurrency_limit","capacity_unavailable","key_budget_exceeded","insufficient_characters"]},"get":{"operationId":"listSpeech","summary":"List speech jobs","description":"Jobs created by any of the account's keys, newest first. Page with `cursor` (the previous page's `next_cursor`, unchanged) until `has_more` is false.\n\nRequires the `speech:read` scope. Keeps working while the API is in read-only mode.","tags":["Speech"],"parameters":[{"name":"status","in":"query","required":false,"description":"Only jobs in this status.","schema":{"type":"string","enum":["processing","succeeded","failed","cancelled"]}},{"name":"limit","in":"query","required":false,"description":"Jobs per page, 1–100.","schema":{"default":20,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"The `next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of jobs.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeechList"},"examples":{"example":{"value":{"object":"list","data":[{"object":"speech","id":"8f14e45fceea167a5a36dedd4bea2543","status":"succeeded","created_at":"2026-10-01T08:30:00.000Z","completed_at":"2026-10-01T08:30:07.412Z","progress":1,"input":{"characters":35,"voice_id":"athanlab-default-female-v1","output_format":"mp3","number_mode":"smart"},"usage":{"characters_charged":35,"characters_refunded":0},"audio":{"format":"mp3","duration_seconds":3.84,"url":"/api/v1/speech/8f14e45fceea167a5a36dedd4bea2543/audio","expires_at":"2026-10-31T08:30:00.000Z"},"error":null,"metadata":{"order_id":"A-1042"}}],"has_more":false,"next_cursor":null}}}}}},"400":{"description":"One of: `validation_error`, `invalid_cursor`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"validation_error":{"$ref":"#/components/examples/validation_error"},"invalid_cursor":{"$ref":"#/components/examples/invalid_cursor"}}}}},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"429":{"description":"One of: `rate_limited`, `auth_blocked`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"$ref":"#/components/responses/api_disabled"}},"x-required-scope":"speech:read","x-requires-max":false,"x-read-only-mode":"allowed","x-error-codes":["rate_limited","api_disabled","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error","validation_error","invalid_cursor"]}},"/speech/{id}":{"get":{"operationId":"getSpeech","summary":"Get a speech job","description":"The job's current state. Poll it while `status` is `processing`, as https://athanlab.com/docs#polling describes. A job of another account returns 404.\n\nRequires the `speech:read` scope. Keeps working while the API is in read-only mode.","tags":["Speech"],"parameters":[{"$ref":"#/components/parameters/JobId"}],"responses":{"200":{"description":"The job.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeechJob"},"examples":{"example":{"value":{"object":"speech","id":"8f14e45fceea167a5a36dedd4bea2543","status":"succeeded","created_at":"2026-10-01T08:30:00.000Z","completed_at":"2026-10-01T08:30:07.412Z","progress":1,"input":{"characters":35,"voice_id":"athanlab-default-female-v1","output_format":"mp3","number_mode":"smart"},"usage":{"characters_charged":35,"characters_refunded":0},"audio":{"format":"mp3","duration_seconds":3.84,"url":"/api/v1/speech/8f14e45fceea167a5a36dedd4bea2543/audio","expires_at":"2026-10-31T08:30:00.000Z"},"error":null,"metadata":{"order_id":"A-1042"}}}}}}},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"404":{"$ref":"#/components/responses/not_found"},"429":{"description":"One of: `rate_limited`, `auth_blocked`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"$ref":"#/components/responses/api_disabled"}},"x-required-scope":"speech:read","x-requires-max":false,"x-read-only-mode":"allowed","x-error-codes":["rate_limited","api_disabled","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error","not_found"]}},"/speech/{id}/cancel":{"post":{"operationId":"cancelSpeech","summary":"Cancel a speech job","description":"Stops a job that is still processing. The share already generated stays charged; the rest is refunded (`usage.characters_refunded`). Cancelling a cancelled job returns it again. Works in read-only mode and after your Max plan ends, because it only ever returns characters. No request body.\n\nRequires the `speech:write` scope. Keeps working while the API is in read-only mode.","tags":["Speech"],"parameters":[{"$ref":"#/components/parameters/JobId"}],"responses":{"200":{"description":"The cancelled job.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeechJob"},"examples":{"example":{"value":{"object":"speech","id":"8f14e45fceea167a5a36dedd4bea2543","status":"cancelled","created_at":"2026-10-01T08:30:00.000Z","completed_at":"2026-10-01T08:30:03.000Z","progress":null,"input":{"characters":35,"voice_id":"athanlab-default-female-v1","output_format":"mp3","number_mode":"smart"},"usage":{"characters_charged":35,"characters_refunded":21},"audio":null,"error":null,"metadata":{"order_id":"A-1042"}}}}}}},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"404":{"$ref":"#/components/responses/not_found"},"409":{"$ref":"#/components/responses/job_finished"},"413":{"$ref":"#/components/responses/payload_too_large"},"415":{"$ref":"#/components/responses/unsupported_media_type"},"429":{"description":"One of: `rate_limited`, `auth_blocked`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"$ref":"#/components/responses/api_disabled"}},"x-required-scope":"speech:write","x-requires-max":false,"x-read-only-mode":"allowed","x-error-codes":["payload_too_large","rate_limited","api_disabled","unsupported_media_type","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error","not_found","job_finished"]}},"/speech/{id}/audio":{"get":{"operationId":"downloadSpeechAudio","summary":"Download a job's audio","description":"Streams the finished audio. Defaults to the job's `output_format`; `?format=` downloads the other one (MP3 is encoded on first request, so the first WAV→MP3 download of a job can take a few seconds). `HEAD` returns the same status and headers as `GET` — `Content-Length` and `ETag` included — without the body, so it also returns 410 once the audio is gone. Until an MP3 has been encoded it has no size or ETag yet, so `HEAD` of a `wav` job's first MP3 omits both. Audio is kept for 30 days (`audio.expires_at`).\n\nRequires the `speech:read` scope. Keeps working while the API is in read-only mode.","tags":["Speech"],"parameters":[{"$ref":"#/components/parameters/JobId"},{"name":"format","in":"query","required":false,"description":"Download in this format instead of the job's `output_format`.","schema":{"type":"string","enum":["mp3","wav"]}}],"responses":{"200":{"description":"The audio file (sent as an attachment named `athanlab-speech-{id}.mp3` or `.wav`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Content-Disposition":{"$ref":"#/components/headers/Content-Disposition"},"Content-Length":{"$ref":"#/components/headers/Content-Length"},"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"audio/mpeg":{"schema":{"type":"string","contentMediaType":"audio/mpeg"}},"audio/wav":{"schema":{"type":"string","contentMediaType":"audio/wav"}}}},"400":{"$ref":"#/components/responses/validation_error"},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"404":{"$ref":"#/components/responses/not_found"},"409":{"description":"One of: `job_not_finished`, `job_failed`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"job_not_finished":{"$ref":"#/components/examples/job_not_finished"},"job_failed":{"$ref":"#/components/examples/job_failed"}}}}},"410":{"$ref":"#/components/responses/audio_expired"},"429":{"description":"One of: `rate_limited`, `auth_blocked`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"description":"One of: `api_disabled`, `server_busy`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"api_disabled":{"$ref":"#/components/examples/api_disabled"},"server_busy":{"$ref":"#/components/examples/server_busy"}}}}}},"x-required-scope":"speech:read","x-requires-max":false,"x-read-only-mode":"allowed","x-error-codes":["rate_limited","api_disabled","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error","not_found","validation_error","job_not_finished","job_failed","audio_expired","server_busy"]}},"/deliveries":{"post":{"operationId":"createDelivery","summary":"Send to AthanInbox","description":"Sends audio to AthanInbox, the inbox on athanlab.com of the account that owns the key — never anyone else's. Accepts and charges the whole delivery, every chapter, then returns `202 Accepted` at once: the chapters are made in the background, one at a time, and the owner follows their progress and listens in AthanInbox at https://athanlab.com/dashboard/inbox. A chapter that cannot be made is refunded to the balance it was charged from (a monthly allowance that has reset since is not carried over; `usage.characters_refunded` shows what came back) and the rest still arrive (`partially_succeeded`). The delivery stays in AthanInbox until the owner deletes it.\n\nPolling is optional. To poll, GET the delivery at the `Location` header no more often than this response's `Retry-After` (30 seconds).\n\nSend the content as `text` (one chapter) or as `chapters` — up to 50 of at most 5,000 characters each, 100,000 in total — each checked like a speech job's text before anything is charged (an error names the chapter, as in `chapters.3.text`). The charge is the total length in UTF-16 code units. Send an `Idempotency-Key` header so a retry never charges twice (https://athanlab.com/docs#idempotency). With `dry_run: true` the response is a `200` quote, and nothing is created or charged. Guide: https://athanlab.com/docs#athaninbox.\n\nRequires the `speech:write` scope. The key's account must be on the Max plan. Refused while the API is in read-only mode.","tags":["Deliveries"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeliveryRequest"},"examples":{"example":{"value":{"title":"ယနေ့ သတင်းအကျဉ်း","kind":"briefing","chapters":[{"title":"ရာသီဥတု","text":"ရန်ကုန်မြို့တွင် မိုးအနည်းငယ် ရွာနိုင်ပါသည်။ ထီးဆောင်ထားပါ။"},{"title":"ဈေးကွက်","text":"ဈေးကွက်များ တည်ငြိမ်နေပါသည်။ အသေးစိတ်ကို မနက်ဖြန် ပြောပြပါမယ်။"}],"sources":[{"title":"မိုးလေဝသ ခန့်မှန်းချက်","url":"https://example.com/weather"}],"voice_id":"athanlab-default-female-v1"}}}}}},"responses":{"200":{"description":"A quote for a `dry_run` request. Nothing was created or charged.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryQuote"},"examples":{"example":{"value":{"object":"delivery.quote","characters":121,"chapters":2,"dispatches":2,"spendable":1950000,"sufficient":true}}}}}},"202":{"description":"The delivery was accepted and charged, or an earlier request with the same Idempotency-Key was replayed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"Idempotent-Replayed":{"$ref":"#/components/headers/Idempotent-Replayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeliveryResponse"},"examples":{"example":{"value":{"object":"delivery","id":"1679091c5a880faf6fb5e6087eb1b2dc","status":"queued","title":"ယနေ့ သတင်းအကျဉ်း","kind":"briefing","created_at":"2026-10-06T00:30:00.000Z","completed_at":null,"progress":0,"input":{"characters":121,"chapters":2,"voice_id":"athanlab-default-female-v1","number_mode":"smart"},"chapters":[{"index":0,"title":"ရာသီဥတု","characters":59,"status":"queued","duration_seconds":null,"error":null},{"index":1,"title":"ဈေးကွက်","characters":62,"status":"queued","duration_seconds":null,"error":null}],"sources":[{"title":"မိုးလေဝသ ခန့်မှန်းချက်","url":"https://example.com/weather"}],"usage":{"characters_charged":121,"characters_refunded":0},"app_url":"https://athanlab.com/app/inbox/1679091c5a880faf6fb5e6087eb1b2dc","notified":false,"metadata":{},"balance":{"spendable_chars":1949879}}}}}}},"400":{"$ref":"#/components/responses/validation_error"},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"402":{"description":"One of: `plan_required`, `insufficient_characters`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"plan_required":{"$ref":"#/components/examples/plan_required"},"insufficient_characters":{"$ref":"#/components/examples/insufficient_characters"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"404":{"description":"One of: `not_found`, `voice_not_found`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"$ref":"#/components/examples/not_found"},"voice_not_found":{"$ref":"#/components/examples/voice_not_found"}}}}},"409":{"$ref":"#/components/responses/idempotency_conflict"},"413":{"$ref":"#/components/responses/payload_too_large"},"415":{"$ref":"#/components/responses/unsupported_media_type"},"422":{"description":"One of: `unspeakable_text`, `text_too_long`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unspeakable_text":{"$ref":"#/components/examples/unspeakable_text"},"text_too_long":{"$ref":"#/components/examples/text_too_long"}}}}},"429":{"description":"One of: `rate_limited`, `auth_blocked`, `concurrency_limit`, `key_budget_exceeded`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"},"concurrency_limit":{"$ref":"#/components/examples/concurrency_limit"},"key_budget_exceeded":{"$ref":"#/components/examples/key_budget_exceeded"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"description":"One of: `api_disabled`, `api_read_only`, `server_restarting`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"api_disabled":{"$ref":"#/components/examples/api_disabled"},"api_read_only":{"$ref":"#/components/examples/api_read_only"},"server_restarting":{"$ref":"#/components/examples/server_restarting"}}}}}},"x-required-scope":"speech:write","x-requires-max":true,"x-read-only-mode":"refused","x-error-codes":["payload_too_large","rate_limited","api_disabled","api_read_only","unsupported_media_type","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error","validation_error","not_found","idempotency_conflict","plan_required","unspeakable_text","text_too_long","voice_not_found","server_restarting","concurrency_limit","key_budget_exceeded","insufficient_characters"]}},"/deliveries/{id}":{"get":{"operationId":"getDelivery","summary":"Get a delivery","description":"The delivery's current state, its chapters and what it cost. The owner follows its progress in AthanInbox, so polling is optional; to poll, wait at least 30 seconds between requests. After the owner deletes it in AthanInbox it reads as `deleted`, keeping only its charge and refunds. A delivery of another account returns 404.\n\nRequires the `speech:read` scope. Keeps working while the API is in read-only mode.","tags":["Deliveries"],"parameters":[{"$ref":"#/components/parameters/DeliveryId"}],"responses":{"200":{"description":"The delivery.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"},"examples":{"example":{"value":{"object":"delivery","id":"1679091c5a880faf6fb5e6087eb1b2dc","status":"succeeded","title":"ယနေ့ သတင်းအကျဉ်း","kind":"briefing","created_at":"2026-10-06T00:30:00.000Z","completed_at":"2026-10-06T00:31:24.000Z","progress":1,"input":{"characters":121,"chapters":2,"voice_id":"athanlab-default-female-v1","number_mode":"smart"},"chapters":[{"index":0,"title":"ရာသီဥတု","characters":59,"status":"succeeded","duration_seconds":6.48,"error":null},{"index":1,"title":"ဈေးကွက်","characters":62,"status":"succeeded","duration_seconds":6.91,"error":null}],"sources":[{"title":"မိုးလေဝသ ခန့်မှန်းချက်","url":"https://example.com/weather"}],"usage":{"characters_charged":121,"characters_refunded":0},"app_url":"https://athanlab.com/app/inbox/1679091c5a880faf6fb5e6087eb1b2dc","notified":true,"metadata":{}}}}}}},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"404":{"$ref":"#/components/responses/not_found"},"429":{"description":"One of: `rate_limited`, `auth_blocked`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"$ref":"#/components/responses/api_disabled"}},"x-required-scope":"speech:read","x-requires-max":false,"x-read-only-mode":"allowed","x-error-codes":["rate_limited","api_disabled","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error","not_found"]}},"/voices":{"get":{"operationId":"listVoices","summary":"List voices","description":"AthanLab's official voices, then the voices in your own library. `default_voice_id` is the voice a job uses when it names none. Voices are added to your library in the AthanLab app, under the same consent rules as every other voice.\n\nRequires the `voices:read` scope. Keeps working while the API is in read-only mode.","tags":["Voices"],"responses":{"200":{"description":"The voices this account can use.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceList"},"examples":{"example":{"value":{"data":[{"id":"athanlab-default-female-v1","name":"AthanLab Female","category":"default","source":"athanlab","is_default":true},{"id":"athanlab-default-male-v1","name":"AthanLab Male","category":"default","source":"athanlab","is_default":false},{"id":"3c59dc048e8850243be8079a5c74d079","name":"My narrator","category":"custom","source":"user","is_default":false}],"default_voice_id":"athanlab-default-female-v1"}}}}}},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"429":{"description":"One of: `rate_limited`, `auth_blocked`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"$ref":"#/components/responses/api_disabled"}},"x-required-scope":"voices:read","x-requires-max":false,"x-read-only-mode":"allowed","x-error-codes":["rate_limited","api_disabled","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error"]}},"/voices/{id}/preview":{"get":{"operationId":"previewVoice","summary":"Get a voice sample","description":"A short-lived signed URL of the voice's reference sample, to let people hear a voice before choosing it.\n\nRequires the `voices:read` scope. Keeps working while the API is in read-only mode.","tags":["Voices"],"parameters":[{"$ref":"#/components/parameters/VoiceId"}],"responses":{"200":{"description":"The sample URL.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoicePreview"},"examples":{"example":{"value":{"id":"athanlab-default-female-v1","name":"AthanLab Female","url":"https://storage.example/athanlab-default-female-v1.wav?X-Amz-Signature=…","expires_in":3600}}}}}},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"404":{"$ref":"#/components/responses/voice_not_found"},"429":{"description":"One of: `rate_limited`, `auth_blocked`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"$ref":"#/components/responses/api_disabled"}},"x-required-scope":"voices:read","x-requires-max":false,"x-read-only-mode":"allowed","x-error-codes":["rate_limited","api_disabled","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error","voice_not_found"]}},"/usage":{"get":{"operationId":"getUsage","summary":"Get the account balance","description":"The balance your keys draw on — the same one the AthanLab app uses: the Max plan's monthly allowance first, then Athan Tokens. `entitled` says whether keys may create jobs right now. Also the two limits a create is checked against besides the balance: the account's jobs processing (`active_jobs` of `max_concurrent_jobs`) and the calling key's monthly budget (`key`). Works after your Max plan ends.\n\nRequires the `usage:read` scope. Keeps working while the API is in read-only mode.","tags":["Account"],"responses":{"200":{"description":"The balance.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"},"examples":{"example":{"value":{"plan":"max","entitled":true,"upgrade_url":null,"monthly":{"limit":2000000,"used":50000,"remaining":1950000,"resets_at":"2026-10-21T00:00:00.000Z"},"tokens":{"balance":0},"spendable":1950000,"active_jobs":1,"max_concurrent_jobs":2,"key":{"id":"c4ca4238a0b923820dcc509a6f75849b","monthly_char_budget":500000,"used_this_month":120000,"remaining":380000,"resets_at":"2026-11-01T00:00:00.000Z"}}}}}}},"401":{"description":"One of: `missing_api_key`, `invalid_api_key`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"},"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"403":{"description":"One of: `developer_pending`, `developer_rejected`, `developer_suspended`, `developer_not_approved`, `account_suspended`, `scope_missing`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"},"developer_rejected":{"$ref":"#/components/examples/developer_rejected"},"developer_suspended":{"$ref":"#/components/examples/developer_suspended"},"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"},"account_suspended":{"$ref":"#/components/examples/account_suspended"},"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"429":{"description":"One of: `rate_limited`, `auth_blocked`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"},"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"500":{"$ref":"#/components/responses/internal_error"},"503":{"$ref":"#/components/responses/api_disabled"}},"x-required-scope":"usage:read","x-requires-max":false,"x-read-only-mode":"allowed","x-error-codes":["rate_limited","api_disabled","auth_blocked","missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","internal_error"]}},"/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"This OpenAPI document","description":"The OpenAPI 3.1 description of this API. Public: no key needed, and served even while the API is switched off.","tags":["Meta"],"security":[],"responses":{"200":{"description":"The document.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Not modified: the If-None-Match header matched the current ETag.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"ETag":{"$ref":"#/components/headers/ETag"}}},"429":{"$ref":"#/components/responses/rate_limited"},"500":{"$ref":"#/components/responses/internal_error"}},"x-required-scope":null,"x-requires-max":false,"x-read-only-mode":"not_applicable","x-error-codes":["rate_limited","internal_error"]}}},"components":{"securitySchemes":{"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Your API key (`ak_live_` followed by 32 hex characters). Keep it on your server — never in a browser or a mobile app."},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"ak_live_<32 hex>","description":"The same API key as `Authorization: Bearer <key>`. When both headers are sent, X-API-Key is used."}},"schemas":{"CreateSpeechRequest":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":5000,"description":"The text to speak, 1–5,000 characters, billed at that same count. Characters are UTF-16 code units (JavaScript `String.length`): a Burmese or Latin character counts 1, an emoji or any other character outside the Basic Multilingual Plane counts 2. `maxLength` counts code points, so a client-side check can pass text the API rejects; `dry_run` returns the exact count."},"voice_id":{"description":"A voice from `GET /voices`. Defaults to the API default voice (`default_voice_id`).","anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}]},"output_format":{"default":"mp3","description":"The audio format the job prepares. Either format can still be downloaded later.","type":"string","enum":["mp3","wav"]},"number_mode":{"default":"smart","description":"How digits are read: smart (Burmese digits as words, other digits as written), place, digits, english, english_digits.","type":"string","enum":["smart","place","digits","english","english_digits"]},"metadata":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":40,"pattern":"^[A-Za-z0-9][A-Za-z0-9_.:-]*$"},"additionalProperties":{"type":"string","maxLength":500},"description":"Up to 16 string key/value pairs stored with the job and returned on every read (keys ≤ 40 characters, values ≤ 500, ≤ 8000 bytes in total)."},"dry_run":{"description":"Validate and quote the request without creating a job or charging anything.","type":"boolean"}},"required":["text"],"additionalProperties":false},"CreateDeliveryRequest":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120,"description":"What the listener sees in AthanInbox, 1–120 characters. Leading and trailing spaces are removed; line breaks and other control characters are refused."},"text":{"description":"The text of a one-chapter delivery, 1–5,000 characters. Send exactly one of `text` and `chapters`.","type":"string","minLength":1,"maxLength":5000},"chapters":{"description":"1–50 chapters, played in this order, totalling at most 100,000 characters. Send exactly one of `text` and `chapters`.","minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"title":{"description":"The chapter's title, 1–120 characters. Without one AthanInbox shows \"Chapter 1\", \"Chapter 2\", …","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"text":{"type":"string","minLength":1,"maxLength":5000,"description":"The chapter's text, 1–5,000 characters. Characters are UTF-16 code units (JavaScript `String.length`): a Burmese or Latin character counts 1, an emoji or any other character outside the Basic Multilingual Plane counts 2."}},"required":["text"],"additionalProperties":false}},"voice_id":{"description":"A voice from `GET /voices`, used for every chapter. Defaults to the API default voice (`default_voice_id`).","anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}]},"number_mode":{"default":"smart","description":"How digits are read in every chapter: smart (Burmese digits as words, other digits as written), place, digits, english, english_digits.","type":"string","enum":["smart","place","digits","english","english_digits"]},"kind":{"default":"other","description":"What the delivery is: article, book, lesson, briefing, other. AthanInbox files and labels it by kind.","type":"string","enum":["article","book","lesson","briefing","other"]},"sources":{"description":"Up to 20 links the listener can open from the delivery (≤ 16,000 bytes in total). AthanLab never fetches them.","maxItems":20,"type":"array","items":{"type":"object","properties":{"title":{"description":"What the link is, 1–200 characters. AthanInbox shows the link's host next to it.","anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"url":{"description":"An https link, at most 2,048 characters, with no user name or password. Shown to the listener; never fetched.","type":"string","maxLength":2048}},"required":["url"],"additionalProperties":false}},"metadata":{"description":"Up to 16 string key/value pairs stored with the delivery and returned on every read (keys ≤ 40 characters, values ≤ 500, ≤ 8000 bytes in total).","type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":40,"pattern":"^[A-Za-z0-9][A-Za-z0-9_.:-]*$"},"additionalProperties":{"type":"string","maxLength":500}},"dry_run":{"description":"Validate and quote the delivery without creating it or charging anything.","type":"boolean"}},"required":["title"],"additionalProperties":false},"SpeechJob":{"type":"object","properties":{"object":{"type":"string","const":"speech","description":"Always `speech`."},"id":{"type":"string","description":"The job id: 32 lowercase hex characters."},"status":{"type":"string","enum":["processing","succeeded","failed","cancelled"],"description":"`processing` until the job finishes: `succeeded`, `failed` or `cancelled`."},"created_at":{"type":"string","format":"date-time","description":"When the job was accepted and charged."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When the job finished; null while it is processing."},"progress":{"anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}],"description":"Share of the work done: 0 to 0.99 while processing, 1 once succeeded, null once failed or cancelled."},"input":{"type":"object","properties":{"characters":{"type":"integer","exclusiveMinimum":0,"description":"Characters billed: the length of `text` in UTF-16 code units (an emoji counts 2)."},"voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The voice the job speaks with (the API default voice when the request named none)."},"output_format":{"type":"string","enum":["mp3","wav"],"description":"The format the job prepared. Either format can be downloaded."},"number_mode":{"type":"string","enum":["smart","place","digits","english","english_digits"],"description":"How digits were read."}},"required":["characters","voice_id","output_format","number_mode"],"description":"What was requested. The input text itself is never returned."},"usage":{"type":"object","properties":{"characters_charged":{"type":"integer","minimum":0,"description":"Characters charged when the job was accepted."},"characters_refunded":{"type":"integer","minimum":0,"description":"Characters returned since: all of them when the job failed, the unprocessed share when it was cancelled."}},"required":["characters_charged","characters_refunded"],"description":"What the job cost."},"audio":{"anyOf":[{"$ref":"#/components/schemas/SpeechAudio"},{"type":"null"}],"description":"Present while the job has downloadable audio: succeeded and inside the retention period. Null otherwise."},"error":{"anyOf":[{"$ref":"#/components/schemas/SpeechJobError"},{"type":"null"}],"description":"Why the job failed; null unless status is `failed`."},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"The metadata sent with the request, unchanged."}},"required":["object","id","status","created_at","completed_at","progress","input","usage","audio","error","metadata"]},"SpeechJobError":{"type":"object","properties":{"code":{"type":"string","enum":["input_rejected","capacity_unavailable","generation_failed","interrupted"],"description":"Machine-readable reason, explained under \"Job failure codes\" at https://athanlab.com/docs#jobs. Not an HTTP error code: do not look it up as `#errors-<code>` — `capacity_unavailable` is also an HTTP 503, which charged nothing and carries Retry-After, while a failed job was charged, then refunded."},"message":{"type":"string","description":"A human-readable explanation. Do not parse it."},"retryable":{"type":"boolean","description":"True when submitting the same request again may succeed."}},"required":["code","message","retryable"]},"SpeechAudio":{"type":"object","properties":{"format":{"type":"string","enum":["mp3","wav"],"description":"The job's output format. GET the audio URL with `?format=` for the other one."},"duration_seconds":{"type":"number","minimum":0,"description":"Length of the audio in seconds."},"url":{"type":"string","description":"Download path, relative to https://api.athanlab.com. Send your API key with the request."},"expires_at":{"type":"string","format":"date-time","description":"When the audio (and the job's input text) is deleted. Downloads return 410 `audio_expired` after this."}},"required":["format","duration_seconds","url","expires_at"]},"CreateSpeechResponse":{"type":"object","properties":{"object":{"type":"string","const":"speech","description":"Always `speech`."},"id":{"type":"string","description":"The job id: 32 lowercase hex characters."},"status":{"type":"string","enum":["processing","succeeded","failed","cancelled"],"description":"`processing` until the job finishes: `succeeded`, `failed` or `cancelled`."},"created_at":{"type":"string","format":"date-time","description":"When the job was accepted and charged."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When the job finished; null while it is processing."},"progress":{"anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}],"description":"Share of the work done: 0 to 0.99 while processing, 1 once succeeded, null once failed or cancelled."},"input":{"type":"object","properties":{"characters":{"type":"integer","exclusiveMinimum":0,"description":"Characters billed: the length of `text` in UTF-16 code units (an emoji counts 2)."},"voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The voice the job speaks with (the API default voice when the request named none)."},"output_format":{"type":"string","enum":["mp3","wav"],"description":"The format the job prepared. Either format can be downloaded."},"number_mode":{"type":"string","enum":["smart","place","digits","english","english_digits"],"description":"How digits were read."}},"required":["characters","voice_id","output_format","number_mode"],"description":"What was requested. The input text itself is never returned."},"usage":{"type":"object","properties":{"characters_charged":{"type":"integer","minimum":0,"description":"Characters charged when the job was accepted."},"characters_refunded":{"type":"integer","minimum":0,"description":"Characters returned since: all of them when the job failed, the unprocessed share when it was cancelled."}},"required":["characters_charged","characters_refunded"],"description":"What the job cost."},"audio":{"anyOf":[{"$ref":"#/components/schemas/SpeechAudio"},{"type":"null"}],"description":"Present while the job has downloadable audio: succeeded and inside the retention period. Null otherwise."},"error":{"anyOf":[{"$ref":"#/components/schemas/SpeechJobError"},{"type":"null"}],"description":"Why the job failed; null unless status is `failed`."},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"The metadata sent with the request, unchanged."},"balance":{"type":"object","properties":{"spendable_chars":{"type":"integer","minimum":0,"description":"Monthly allowance remaining plus Athan Tokens."}},"required":["spendable_chars"],"description":"What the account can still spend after this job's charge."}},"required":["object","id","status","created_at","completed_at","progress","input","usage","audio","error","metadata","balance"]},"SpeechList":{"type":"object","properties":{"object":{"type":"string","const":"list","description":"Always `list`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/SpeechJob"},"description":"Jobs, newest first."},"has_more":{"type":"boolean","description":"True when older jobs exist beyond this page."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass as `cursor` to fetch the next (older) page; null on the last page."}},"required":["object","data","has_more","next_cursor"]},"DryRunQuote":{"type":"object","properties":{"object":{"type":"string","const":"speech.quote","description":"Always `speech.quote`."},"characters":{"type":"integer","exclusiveMinimum":0,"description":"What the job would be charged: the length of `text` in UTF-16 code units (an emoji counts 2)."},"dispatches":{"type":"integer","exclusiveMinimum":0,"description":"How many pieces the text is split into for synthesis."},"spendable":{"type":"integer","minimum":0,"description":"Monthly allowance remaining plus Athan Tokens, right now."},"sufficient":{"type":"boolean","description":"True when `spendable` covers `characters`."}},"required":["object","characters","dispatches","spendable","sufficient"]},"Delivery":{"type":"object","properties":{"object":{"type":"string","const":"delivery","description":"Always `delivery`."},"id":{"type":"string","description":"The delivery id: 32 lowercase hex characters."},"status":{"type":"string","enum":["queued","processing","succeeded","partially_succeeded","failed","deleted"],"description":"`queued` until its first chapter starts, `processing` while chapters are made, then `succeeded` (every chapter), `partially_succeeded` (some chapters failed and were refunded) or `failed` (none could be made; all refunded). `deleted` once the owner deleted it in AthanInbox."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The title the owner sees in AthanInbox; null once the delivery is deleted."},"kind":{"type":"string","enum":["article","book","lesson","briefing","other"],"description":"What the delivery is, as sent."},"created_at":{"type":"string","format":"date-time","description":"When the delivery was accepted and charged."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When its last chapter finished; null while queued or processing."},"progress":{"anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}],"description":"Share of the delivery made, by characters: 0 to 0.99 while queued or processing, 1 once it succeeded or partially succeeded, null once it failed or was deleted."},"input":{"type":"object","properties":{"characters":{"type":"integer","exclusiveMinimum":0,"description":"Characters charged: the total length of the chapters in UTF-16 code units (an emoji counts 2)."},"chapters":{"type":"integer","exclusiveMinimum":0,"description":"How many chapters were sent."},"voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The voice every chapter speaks with (the API default voice when the request named none)."},"number_mode":{"type":"string","enum":["smart","place","digits","english","english_digits"],"description":"How digits are read."}},"required":["characters","chapters","voice_id","number_mode"],"description":"What was sent. The text itself is never returned."},"chapters":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryChapter"},"description":"The chapters, in play order; empty once the delivery is deleted."},"sources":{"type":"array","items":{"$ref":"#/components/schemas/DeliverySource"},"description":"The links sent with the delivery, as AthanInbox shows them; empty once it is deleted."},"usage":{"type":"object","properties":{"characters_charged":{"type":"integer","minimum":0,"description":"Characters charged when the delivery was accepted: every chapter, up front."},"characters_refunded":{"type":"integer","minimum":0,"description":"Characters returned to the balance since: each failed chapter in full and, when the owner deleted the delivery while it was being made, every chapter not yet made plus the unmade share of the one in progress. A share charged to a monthly allowance that has reset since is not returned (the reset gave a fresh allowance)."}},"required":["characters_charged","characters_refunded"],"description":"What the delivery cost."},"app_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The delivery in AthanInbox on athanlab.com; it opens only for the signed-in owner. Open it on a computer for now: the AthanLab mobile app gets AthanInbox in a later update. Null once deleted."},"notified":{"type":"boolean","description":"True once AthanLab has recorded its notice to the owner that the delivery finished. It is not a read receipt."},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"The metadata sent with the request, unchanged; empty once the delivery is deleted."}},"required":["object","id","status","title","kind","created_at","completed_at","progress","input","chapters","sources","usage","app_url","notified","metadata"]},"DeliveryChapter":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"description":"The chapter's position, from 0, in the order sent."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The chapter's title as sent; null when none was sent."},"characters":{"type":"integer","exclusiveMinimum":0,"description":"Characters this chapter was charged."},"status":{"type":"string","enum":["queued","processing","succeeded","failed","cancelled"],"description":"`queued`, then `processing` while it is made, then `succeeded` or `failed` (refunded). `cancelled` when AthanLab stopped the chapter while it was being made; the share not made is refunded."},"duration_seconds":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Length of the chapter's audio in seconds; null until it succeeded."},"error":{"anyOf":[{"$ref":"#/components/schemas/DeliveryChapterError"},{"type":"null"}],"description":"Why the chapter failed; null unless status is `failed`."}},"required":["index","title","characters","status","duration_seconds","error"]},"DeliveryChapterError":{"type":"object","properties":{"code":{"type":"string","enum":["input_rejected","capacity_unavailable","generation_failed","interrupted","voice_unavailable","expired"],"description":"Machine-readable reason: the job failure codes explained at https://athanlab.com/docs#jobs, plus `voice_unavailable` (the voice was removed before the chapter ran) and `expired` (it waited past the queue limit). Not an HTTP error code."},"message":{"type":"string","description":"A human-readable explanation. Do not parse it."},"retryable":{"type":"boolean","description":"True when sending the chapter again may succeed."}},"required":["code","message","retryable"]},"DeliverySource":{"type":"object","properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What the link is; null when none was sent."},"url":{"type":"string","description":"The https link, normalised (punycode host, percent-encoded path). AthanLab never fetches it."}},"required":["title","url"]},"CreateDeliveryResponse":{"type":"object","properties":{"object":{"type":"string","const":"delivery","description":"Always `delivery`."},"id":{"type":"string","description":"The delivery id: 32 lowercase hex characters."},"status":{"type":"string","enum":["queued","processing","succeeded","partially_succeeded","failed","deleted"],"description":"`queued` until its first chapter starts, `processing` while chapters are made, then `succeeded` (every chapter), `partially_succeeded` (some chapters failed and were refunded) or `failed` (none could be made; all refunded). `deleted` once the owner deleted it in AthanInbox."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The title the owner sees in AthanInbox; null once the delivery is deleted."},"kind":{"type":"string","enum":["article","book","lesson","briefing","other"],"description":"What the delivery is, as sent."},"created_at":{"type":"string","format":"date-time","description":"When the delivery was accepted and charged."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When its last chapter finished; null while queued or processing."},"progress":{"anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}],"description":"Share of the delivery made, by characters: 0 to 0.99 while queued or processing, 1 once it succeeded or partially succeeded, null once it failed or was deleted."},"input":{"type":"object","properties":{"characters":{"type":"integer","exclusiveMinimum":0,"description":"Characters charged: the total length of the chapters in UTF-16 code units (an emoji counts 2)."},"chapters":{"type":"integer","exclusiveMinimum":0,"description":"How many chapters were sent."},"voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The voice every chapter speaks with (the API default voice when the request named none)."},"number_mode":{"type":"string","enum":["smart","place","digits","english","english_digits"],"description":"How digits are read."}},"required":["characters","chapters","voice_id","number_mode"],"description":"What was sent. The text itself is never returned."},"chapters":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryChapter"},"description":"The chapters, in play order; empty once the delivery is deleted."},"sources":{"type":"array","items":{"$ref":"#/components/schemas/DeliverySource"},"description":"The links sent with the delivery, as AthanInbox shows them; empty once it is deleted."},"usage":{"type":"object","properties":{"characters_charged":{"type":"integer","minimum":0,"description":"Characters charged when the delivery was accepted: every chapter, up front."},"characters_refunded":{"type":"integer","minimum":0,"description":"Characters returned to the balance since: each failed chapter in full and, when the owner deleted the delivery while it was being made, every chapter not yet made plus the unmade share of the one in progress. A share charged to a monthly allowance that has reset since is not returned (the reset gave a fresh allowance)."}},"required":["characters_charged","characters_refunded"],"description":"What the delivery cost."},"app_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The delivery in AthanInbox on athanlab.com; it opens only for the signed-in owner. Open it on a computer for now: the AthanLab mobile app gets AthanInbox in a later update. Null once deleted."},"notified":{"type":"boolean","description":"True once AthanLab has recorded its notice to the owner that the delivery finished. It is not a read receipt."},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"The metadata sent with the request, unchanged; empty once the delivery is deleted."},"balance":{"type":"object","properties":{"spendable_chars":{"type":"integer","minimum":0,"description":"Monthly allowance remaining plus Athan Tokens."}},"required":["spendable_chars"],"description":"What the account can still spend after this delivery's charge."}},"required":["object","id","status","title","kind","created_at","completed_at","progress","input","chapters","sources","usage","app_url","notified","metadata","balance"]},"DeliveryQuote":{"type":"object","properties":{"object":{"type":"string","const":"delivery.quote","description":"Always `delivery.quote`."},"characters":{"type":"integer","exclusiveMinimum":0,"description":"What the delivery would be charged: the total length of the chapters in UTF-16 code units (an emoji counts 2)."},"chapters":{"type":"integer","exclusiveMinimum":0,"description":"How many chapters the delivery has."},"dispatches":{"type":"integer","exclusiveMinimum":0,"description":"How many pieces the chapters are split into for synthesis, in total."},"spendable":{"type":"integer","minimum":0,"description":"Monthly allowance remaining plus Athan Tokens, right now."},"sufficient":{"type":"boolean","description":"True when `spendable` covers `characters`."}},"required":["object","characters","chapters","dispatches","spendable","sufficient"]},"Voice":{"type":"object","properties":{"id":{"type":"string","description":"Pass as `voice_id` when creating a job."},"name":{"type":"string","description":"Display name."},"category":{"type":"string","description":"Voice category."},"source":{"type":"string","enum":["athanlab","user"],"description":"`athanlab` for official voices, `user` for voices in your own library."},"is_default":{"type":"boolean","description":"True for the voice a job uses when it names none."}},"required":["id","name","category","source","is_default"]},"VoiceList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Voice"},"description":"Official voices first, then your own."},"default_voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The voice a job uses when it names none; null when that voice is temporarily unavailable (send `voice_id`)."}},"required":["data","default_voice_id"]},"VoicePreview":{"type":"object","properties":{"id":{"type":"string","description":"The voice id."},"name":{"type":"string","description":"Display name."},"url":{"type":"string","description":"A signed URL of the voice sample. No API key needed; it expires."},"expires_in":{"type":"integer","exclusiveMinimum":0,"description":"Seconds until `url` stops working."}},"required":["id","name","url","expires_in"]},"Usage":{"type":"object","properties":{"plan":{"type":"string","description":"The account's plan."},"entitled":{"type":"boolean","description":"True when keys may create jobs (the account is on Max). False means creating a job returns 402 `plan_required`."},"upgrade_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where to subscribe to Max; null while entitled."},"monthly":{"type":"object","properties":{"limit":{"type":"integer","minimum":0,"description":"Characters per 30-day period."},"used":{"type":"integer","minimum":0,"description":"Characters used this period."},"remaining":{"type":"integer","minimum":0,"description":"Characters left this period."},"resets_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When the period resets; null without a monthly allowance."}},"required":["limit","used","remaining","resets_at"],"description":"The plan's monthly character allowance."},"tokens":{"type":"object","properties":{"balance":{"type":"integer","minimum":0,"description":"Athan Tokens held."}},"required":["balance"],"description":"Athan Tokens: purchased characters, spent after the monthly allowance."},"spendable":{"type":"integer","minimum":0,"description":"monthly.remaining + tokens.balance."},"active_jobs":{"type":"integer","minimum":0,"description":"The account's jobs processing right now, across all its keys."},"max_concurrent_jobs":{"type":"integer","exclusiveMinimum":0,"description":"How many jobs the account may have processing at once. A create beyond it returns 429 `concurrency_limit`."},"key":{"type":"object","properties":{"id":{"type":"string","description":"The id of the key that made this request. A rotation keeps it, so the budget carries over."},"monthly_char_budget":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}],"description":"The most this key may spend in a UTC calendar month, set by the account owner; null when the key has no budget."},"used_this_month":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Characters this key has spent since the month began, net of refunds; null without a budget."},"remaining":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Characters left in the budget; null without a budget. A job needs both this and `spendable`: beyond it a create returns 429 `key_budget_exceeded`."},"resets_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When the budget starts over (the start of the next UTC calendar month); null without a budget."}},"required":["id","monthly_char_budget","used_this_month","remaining","resets_at"],"description":"The monthly character budget of the key making this request."}},"required":["plan","entitled","upgrade_url","monthly","tokens","spendable","active_jobs","max_concurrent_jobs","key"]},"Error":{"type":"object","description":"Every error the API returns, whatever its status, has this shape. Branch on `error.code`; retry only when `error.retryable` is true.","required":["error"],"properties":{"error":{"type":"object","required":["type","code","message","param","retryable","request_id","doc_url"],"properties":{"type":{"type":"string","enum":["authentication_error","permission_error","billing_error","rate_limit_error","service_unavailable","invalid_request_error","api_error"],"description":"Broad category of the error."},"code":{"type":"string","enum":["missing_api_key","invalid_api_key","developer_pending","developer_rejected","developer_suspended","developer_not_approved","account_suspended","scope_missing","plan_required","insufficient_characters","rate_limited","auth_blocked","concurrency_limit","key_budget_exceeded","api_disabled","api_read_only","capacity_unavailable","server_restarting","server_busy","endpoint_removed","not_found","voice_not_found","validation_error","unsupported_media_type","payload_too_large","invalid_cursor","unspeakable_text","text_too_long","idempotency_conflict","job_not_finished","job_failed","job_finished","audio_expired","internal_error"],"description":"Machine-readable error code. New codes may be added; handle an unknown one by its HTTP status and `retryable`."},"message":{"type":"string","description":"A human-readable explanation. Do not parse it."},"param":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The request field or header at fault, when there is one."},"retryable":{"type":"boolean","description":"True when the same request may succeed later without changes (honor Retry-After)."},"request_id":{"type":"string","pattern":"^req_[0-9a-f]{24}$","description":"Also sent as the X-Request-Id header. Quote it when you contact support."},"doc_url":{"type":"string","format":"uri","description":"This error's entry in the documentation."},"details":{"type":"object","description":"Structured context some errors carry, such as `notice` (a maintenance message), `replacement` (a removed endpoint's successor), `required_scope`, `job_id` (idempotency_conflict), `active_jobs` / `max_concurrent_jobs` (concurrency_limit), `required` / `spendable` (insufficient_characters) or `required` / `monthly_char_budget` / `used_this_month` / `resets_at` (key_budget_exceeded). On a delivery: `delivery_id` (idempotency_conflict) and `active_deliveries` / `max_active_deliveries` (concurrency_limit). New keys may be added."},"upgrade_url":{"type":"string","format":"uri","description":"Where to fix a billing error."}}}}}},"parameters":{"JobId":{"name":"id","in":"path","required":true,"description":"The job id from the create response.","schema":{"type":"string","pattern":"^[0-9a-f]{32}$"}},"DeliveryId":{"name":"id","in":"path","required":true,"description":"The delivery id from the create response.","schema":{"type":"string","pattern":"^[0-9a-f]{32}$"}},"VoiceId":{"name":"id","in":"path","required":true,"description":"A voice id from `GET /voices`.","schema":{"type":"string","minLength":1,"maxLength":128}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"A unique string per request (a UUID works). A retry with the same key and body within 24 hours returns what the first request created — the original job or delivery — without a new charge; the same key with a different body is rejected with 409 `idempotency_conflict`. A key used on `POST /speech` never conflicts with the same value on `POST /deliveries`. Ignored for dry runs.","schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x20-\\x7E]{1,255}$"}}},"headers":{"X-Request-Id":{"description":"Identifies this request. Sent on every response; quote it when you contact support.","schema":{"type":"string","pattern":"^req_[0-9a-f]{24}$"}},"Retry-After":{"description":"Seconds to wait before polling or retrying.","schema":{"type":"integer","minimum":1}},"X-RateLimit-Limit":{"description":"Requests allowed per minute in the bucket this request counted against (reads and writes have separate buckets).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current minute.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"When the current window resets, in Unix seconds.","schema":{"type":"integer"}},"Location":{"description":"The created resource's path, relative to https://api.athanlab.com: `/api/v1/speech/{id}` for a job, `/api/v1/deliveries/{id}` for a delivery.","schema":{"type":"string"}},"Idempotent-Replayed":{"description":"`true` when this response replays an earlier request with the same Idempotency-Key. Nothing new was charged.","schema":{"type":"string","const":"true"}},"Content-Disposition":{"description":"attachment; filename=\"athanlab-speech-{id}.mp3\"","schema":{"type":"string"}},"Content-Length":{"description":"Size of the file in bytes.","schema":{"type":"integer","minimum":0}},"ETag":{"description":"Entity tag of the content.","schema":{"type":"string"}}},"responses":{"missing_api_key":{"description":"No API key provided. Send it in the X-API-Key header or as Authorization: Bearer <key>.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"$ref":"#/components/examples/missing_api_key"}}}}},"invalid_api_key":{"description":"The API key is invalid, revoked or expired.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_api_key":{"$ref":"#/components/examples/invalid_api_key"}}}}},"developer_pending":{"description":"Your developer application is still under review. Keys start working once it is approved.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_pending":{"$ref":"#/components/examples/developer_pending"}}}}},"developer_rejected":{"description":"Your developer application was not approved. See the Developer API page in your dashboard.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_rejected":{"$ref":"#/components/examples/developer_rejected"}}}}},"developer_suspended":{"description":"Developer API access for this account is suspended. Contact support@athanlab.com.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_suspended":{"$ref":"#/components/examples/developer_suspended"}}}}},"developer_not_approved":{"description":"This account has no approved developer application. Apply from the Developer API page in your dashboard.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"developer_not_approved":{"$ref":"#/components/examples/developer_not_approved"}}}}},"account_suspended":{"description":"This account is suspended or scheduled for deletion. Contact support@athanlab.com.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"account_suspended":{"$ref":"#/components/examples/account_suspended"}}}}},"scope_missing":{"description":"This API key does not have the scope this endpoint requires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"scope_missing":{"$ref":"#/components/examples/scope_missing"}}}}},"plan_required":{"description":"Creating jobs through the API requires an active Max plan. Existing jobs, downloads and usage stay readable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"plan_required":{"$ref":"#/components/examples/plan_required"}}}}},"insufficient_characters":{"description":"The account does not have enough characters for this job. Nothing was charged. Top up Athan Tokens or wait for the monthly allowance to reset.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"insufficient_characters":{"$ref":"#/components/examples/insufficient_characters"}}}}},"rate_limited":{"description":"Too many requests. Retry after the number of seconds in the Retry-After header.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"$ref":"#/components/examples/rate_limited"}}}}},"auth_blocked":{"description":"Too many failed authentication attempts from this network. Retry after the number of seconds in the Retry-After header.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"auth_blocked":{"$ref":"#/components/examples/auth_blocked"}}}}},"concurrency_limit":{"description":"This account already has the maximum number of jobs running. Retry after one finishes (see Retry-After).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"concurrency_limit":{"$ref":"#/components/examples/concurrency_limit"}}}}},"key_budget_exceeded":{"description":"This API key has reached its monthly character budget. Nothing was charged. The owner can raise the budget from the Developer API page.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"key_budget_exceeded":{"$ref":"#/components/examples/key_budget_exceeded"}}}}},"api_disabled":{"description":"The AthanLab API is currently unavailable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"api_disabled":{"$ref":"#/components/examples/api_disabled"}}}}},"api_read_only":{"description":"The AthanLab API is in read-only mode: new jobs are paused, while existing jobs, downloads and usage stay available.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"api_read_only":{"$ref":"#/components/examples/api_read_only"}}}}},"capacity_unavailable":{"description":"The speech service is at capacity. Nothing was charged. Retry after the number of seconds in the Retry-After header.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"capacity_unavailable":{"$ref":"#/components/examples/capacity_unavailable"}}}}},"server_restarting":{"description":"The server is restarting. Nothing was charged. Retry in a few seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"server_restarting":{"$ref":"#/components/examples/server_restarting"}}}}},"server_busy":{"description":"Too many downloads are in progress. Retry after the number of seconds in the Retry-After header.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"server_busy":{"$ref":"#/components/examples/server_busy"}}}}},"endpoint_removed":{"description":"This endpoint has been removed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"endpoint_removed":{"$ref":"#/components/examples/endpoint_removed"}}}}},"not_found":{"description":"No such endpoint or resource.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"$ref":"#/components/examples/not_found"}}}}},"voice_not_found":{"description":"No voice with this id is available to this account.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"voice_not_found":{"$ref":"#/components/examples/voice_not_found"}}}}},"validation_error":{"description":"The request is invalid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"validation_error":{"$ref":"#/components/examples/validation_error"}}}}},"unsupported_media_type":{"description":"Request bodies must be sent as Content-Type: application/json.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unsupported_media_type":{"$ref":"#/components/examples/unsupported_media_type"}}}}},"payload_too_large":{"description":"The request body is too large.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"payload_too_large":{"$ref":"#/components/examples/payload_too_large"}}}}},"invalid_cursor":{"description":"The pagination cursor is invalid. Use the next_cursor value from a previous list response, unchanged.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_cursor":{"$ref":"#/components/examples/invalid_cursor"}}}}},"unspeakable_text":{"description":"The text has no speakable content. Send words to read aloud, not only emoji, punctuation or whitespace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unspeakable_text":{"$ref":"#/components/examples/unspeakable_text"}}}}},"text_too_long":{"description":"The text is too long once numbers are written out as words. Send a shorter text or a different number_mode.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"text_too_long":{"$ref":"#/components/examples/text_too_long"}}}}},"idempotency_conflict":{"description":"This Idempotency-Key was already used with a different request. Use a new key for a new request.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_conflict":{"$ref":"#/components/examples/idempotency_conflict"}}}}},"job_not_finished":{"description":"The job has not finished yet. Poll GET /speech/{id} until its status is \"succeeded\".","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"job_not_finished":{"$ref":"#/components/examples/job_not_finished"}}}}},"job_failed":{"description":"The job failed or was cancelled, so it has no audio. See the job's error for details.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"job_failed":{"$ref":"#/components/examples/job_failed"}}}}},"job_finished":{"description":"The job has already finished and can no longer be cancelled.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"job_finished":{"$ref":"#/components/examples/job_finished"}}}}},"audio_expired":{"description":"The audio for this job is no longer available: it is deleted after the retention period.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"audio_expired":{"$ref":"#/components/examples/audio_expired"}}}}},"internal_error":{"description":"Something went wrong on our side. Retry the request; if it keeps failing, contact support@athanlab.com with the request_id.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"$ref":"#/components/examples/internal_error"}}}}}},"examples":{"missing_api_key":{"summary":"401 missing_api_key","value":{"error":{"type":"authentication_error","code":"missing_api_key","message":"No API key provided. Send it in the X-API-Key header or as Authorization: Bearer <key>.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-missing_api_key"}}},"invalid_api_key":{"summary":"401 invalid_api_key","value":{"error":{"type":"authentication_error","code":"invalid_api_key","message":"The API key is invalid, revoked or expired.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-invalid_api_key"}}},"developer_pending":{"summary":"403 developer_pending","value":{"error":{"type":"permission_error","code":"developer_pending","message":"Your developer application is still under review. Keys start working once it is approved.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-developer_pending"}}},"developer_rejected":{"summary":"403 developer_rejected","value":{"error":{"type":"permission_error","code":"developer_rejected","message":"Your developer application was not approved. See the Developer API page in your dashboard.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-developer_rejected"}}},"developer_suspended":{"summary":"403 developer_suspended","value":{"error":{"type":"permission_error","code":"developer_suspended","message":"Developer API access for this account is suspended. Contact support@athanlab.com.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-developer_suspended"}}},"developer_not_approved":{"summary":"403 developer_not_approved","value":{"error":{"type":"permission_error","code":"developer_not_approved","message":"This account has no approved developer application. Apply from the Developer API page in your dashboard.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-developer_not_approved"}}},"account_suspended":{"summary":"403 account_suspended","value":{"error":{"type":"permission_error","code":"account_suspended","message":"This account is suspended or scheduled for deletion. Contact support@athanlab.com.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-account_suspended"}}},"scope_missing":{"summary":"403 scope_missing","value":{"error":{"type":"permission_error","code":"scope_missing","message":"This API key does not have the scope this endpoint requires.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-scope_missing"}}},"plan_required":{"summary":"402 plan_required","value":{"error":{"type":"billing_error","code":"plan_required","message":"Creating jobs through the API requires an active Max plan. Existing jobs, downloads and usage stay readable.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-plan_required","upgrade_url":"https://athanlab.com/dashboard/profile#plans"}}},"insufficient_characters":{"summary":"402 insufficient_characters","value":{"error":{"type":"billing_error","code":"insufficient_characters","message":"The account does not have enough characters for this job. Nothing was charged. Top up Athan Tokens or wait for the monthly allowance to reset.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-insufficient_characters","upgrade_url":"https://athanlab.com/dashboard/profile#plans"}}},"rate_limited":{"summary":"429 rate_limited","value":{"error":{"type":"rate_limit_error","code":"rate_limited","message":"Too many requests. Retry after the number of seconds in the Retry-After header.","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-rate_limited"}}},"auth_blocked":{"summary":"429 auth_blocked","value":{"error":{"type":"rate_limit_error","code":"auth_blocked","message":"Too many failed authentication attempts from this network. Retry after the number of seconds in the Retry-After header.","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-auth_blocked"}}},"concurrency_limit":{"summary":"429 concurrency_limit","value":{"error":{"type":"rate_limit_error","code":"concurrency_limit","message":"This account already has the maximum number of jobs running. Retry after one finishes (see Retry-After).","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-concurrency_limit"}}},"key_budget_exceeded":{"summary":"429 key_budget_exceeded","value":{"error":{"type":"rate_limit_error","code":"key_budget_exceeded","message":"This API key has reached its monthly character budget. Nothing was charged. The owner can raise the budget from the Developer API page.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-key_budget_exceeded"}}},"api_disabled":{"summary":"503 api_disabled","value":{"error":{"type":"service_unavailable","code":"api_disabled","message":"The AthanLab API is currently unavailable.","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-api_disabled"}}},"api_read_only":{"summary":"503 api_read_only","value":{"error":{"type":"service_unavailable","code":"api_read_only","message":"The AthanLab API is in read-only mode: new jobs are paused, while existing jobs, downloads and usage stay available.","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-api_read_only"}}},"capacity_unavailable":{"summary":"503 capacity_unavailable","value":{"error":{"type":"service_unavailable","code":"capacity_unavailable","message":"The speech service is at capacity. Nothing was charged. Retry after the number of seconds in the Retry-After header.","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-capacity_unavailable"}}},"server_restarting":{"summary":"503 server_restarting","value":{"error":{"type":"service_unavailable","code":"server_restarting","message":"The server is restarting. Nothing was charged. Retry in a few seconds.","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-server_restarting"}}},"server_busy":{"summary":"503 server_busy","value":{"error":{"type":"service_unavailable","code":"server_busy","message":"Too many downloads are in progress. Retry after the number of seconds in the Retry-After header.","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-server_busy"}}},"endpoint_removed":{"summary":"410 endpoint_removed","value":{"error":{"type":"invalid_request_error","code":"endpoint_removed","message":"This endpoint has been removed.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-endpoint_removed"}}},"not_found":{"summary":"404 not_found","value":{"error":{"type":"invalid_request_error","code":"not_found","message":"No such endpoint or resource.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-not_found"}}},"voice_not_found":{"summary":"404 voice_not_found","value":{"error":{"type":"invalid_request_error","code":"voice_not_found","message":"No voice with this id is available to this account.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-voice_not_found"}}},"validation_error":{"summary":"400 validation_error","value":{"error":{"type":"invalid_request_error","code":"validation_error","message":"The request is invalid.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-validation_error"}}},"unsupported_media_type":{"summary":"415 unsupported_media_type","value":{"error":{"type":"invalid_request_error","code":"unsupported_media_type","message":"Request bodies must be sent as Content-Type: application/json.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-unsupported_media_type"}}},"payload_too_large":{"summary":"413 payload_too_large","value":{"error":{"type":"invalid_request_error","code":"payload_too_large","message":"The request body is too large.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-payload_too_large"}}},"invalid_cursor":{"summary":"400 invalid_cursor","value":{"error":{"type":"invalid_request_error","code":"invalid_cursor","message":"The pagination cursor is invalid. Use the next_cursor value from a previous list response, unchanged.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-invalid_cursor"}}},"unspeakable_text":{"summary":"422 unspeakable_text","value":{"error":{"type":"invalid_request_error","code":"unspeakable_text","message":"The text has no speakable content. Send words to read aloud, not only emoji, punctuation or whitespace.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-unspeakable_text"}}},"text_too_long":{"summary":"422 text_too_long","value":{"error":{"type":"invalid_request_error","code":"text_too_long","message":"The text is too long once numbers are written out as words. Send a shorter text or a different number_mode.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-text_too_long"}}},"idempotency_conflict":{"summary":"409 idempotency_conflict","value":{"error":{"type":"invalid_request_error","code":"idempotency_conflict","message":"This Idempotency-Key was already used with a different request. Use a new key for a new request.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-idempotency_conflict"}}},"job_not_finished":{"summary":"409 job_not_finished","value":{"error":{"type":"invalid_request_error","code":"job_not_finished","message":"The job has not finished yet. Poll GET /speech/{id} until its status is \"succeeded\".","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-job_not_finished"}}},"job_failed":{"summary":"409 job_failed","value":{"error":{"type":"invalid_request_error","code":"job_failed","message":"The job failed or was cancelled, so it has no audio. See the job's error for details.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-job_failed"}}},"job_finished":{"summary":"409 job_finished","value":{"error":{"type":"invalid_request_error","code":"job_finished","message":"The job has already finished and can no longer be cancelled.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-job_finished"}}},"audio_expired":{"summary":"410 audio_expired","value":{"error":{"type":"invalid_request_error","code":"audio_expired","message":"The audio for this job is no longer available: it is deleted after the retention period.","param":null,"retryable":false,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-audio_expired"}}},"internal_error":{"summary":"500 internal_error","value":{"error":{"type":"api_error","code":"internal_error","message":"Something went wrong on our side. Retry the request; if it keeps failing, contact support@athanlab.com with the request_id.","param":null,"retryable":true,"request_id":"req_4f1c2a9e0b7d6c5a3e8f1b2c","doc_url":"https://athanlab.com/docs#errors-internal_error"}}}}},"x-error-codes":[{"code":"missing_api_key","status":401,"type":"authentication_error","retryable":false,"message":"No API key provided. Send it in the X-API-Key header or as Authorization: Bearer <key>."},{"code":"invalid_api_key","status":401,"type":"authentication_error","retryable":false,"message":"The API key is invalid, revoked or expired."},{"code":"developer_pending","status":403,"type":"permission_error","retryable":false,"message":"Your developer application is still under review. Keys start working once it is approved."},{"code":"developer_rejected","status":403,"type":"permission_error","retryable":false,"message":"Your developer application was not approved. See the Developer API page in your dashboard."},{"code":"developer_suspended","status":403,"type":"permission_error","retryable":false,"message":"Developer API access for this account is suspended. Contact support@athanlab.com."},{"code":"developer_not_approved","status":403,"type":"permission_error","retryable":false,"message":"This account has no approved developer application. Apply from the Developer API page in your dashboard."},{"code":"account_suspended","status":403,"type":"permission_error","retryable":false,"message":"This account is suspended or scheduled for deletion. Contact support@athanlab.com."},{"code":"scope_missing","status":403,"type":"permission_error","retryable":false,"message":"This API key does not have the scope this endpoint requires."},{"code":"plan_required","status":402,"type":"billing_error","retryable":false,"message":"Creating jobs through the API requires an active Max plan. Existing jobs, downloads and usage stay readable."},{"code":"insufficient_characters","status":402,"type":"billing_error","retryable":false,"message":"The account does not have enough characters for this job. Nothing was charged. Top up Athan Tokens or wait for the monthly allowance to reset."},{"code":"rate_limited","status":429,"type":"rate_limit_error","retryable":true,"message":"Too many requests. Retry after the number of seconds in the Retry-After header."},{"code":"auth_blocked","status":429,"type":"rate_limit_error","retryable":true,"message":"Too many failed authentication attempts from this network. Retry after the number of seconds in the Retry-After header."},{"code":"concurrency_limit","status":429,"type":"rate_limit_error","retryable":true,"message":"This account already has the maximum number of jobs running. Retry after one finishes (see Retry-After)."},{"code":"key_budget_exceeded","status":429,"type":"rate_limit_error","retryable":false,"message":"This API key has reached its monthly character budget. Nothing was charged. The owner can raise the budget from the Developer API page."},{"code":"api_disabled","status":503,"type":"service_unavailable","retryable":true,"message":"The AthanLab API is currently unavailable."},{"code":"api_read_only","status":503,"type":"service_unavailable","retryable":true,"message":"The AthanLab API is in read-only mode: new jobs are paused, while existing jobs, downloads and usage stay available."},{"code":"capacity_unavailable","status":503,"type":"service_unavailable","retryable":true,"message":"The speech service is at capacity. Nothing was charged. Retry after the number of seconds in the Retry-After header."},{"code":"server_restarting","status":503,"type":"service_unavailable","retryable":true,"message":"The server is restarting. Nothing was charged. Retry in a few seconds."},{"code":"server_busy","status":503,"type":"service_unavailable","retryable":true,"message":"Too many downloads are in progress. Retry after the number of seconds in the Retry-After header."},{"code":"endpoint_removed","status":410,"type":"invalid_request_error","retryable":false,"message":"This endpoint has been removed."},{"code":"not_found","status":404,"type":"invalid_request_error","retryable":false,"message":"No such endpoint or resource."},{"code":"voice_not_found","status":404,"type":"invalid_request_error","retryable":false,"message":"No voice with this id is available to this account."},{"code":"validation_error","status":400,"type":"invalid_request_error","retryable":false,"message":"The request is invalid."},{"code":"unsupported_media_type","status":415,"type":"invalid_request_error","retryable":false,"message":"Request bodies must be sent as Content-Type: application/json."},{"code":"payload_too_large","status":413,"type":"invalid_request_error","retryable":false,"message":"The request body is too large."},{"code":"invalid_cursor","status":400,"type":"invalid_request_error","retryable":false,"message":"The pagination cursor is invalid. Use the next_cursor value from a previous list response, unchanged."},{"code":"unspeakable_text","status":422,"type":"invalid_request_error","retryable":false,"message":"The text has no speakable content. Send words to read aloud, not only emoji, punctuation or whitespace."},{"code":"text_too_long","status":422,"type":"invalid_request_error","retryable":false,"message":"The text is too long once numbers are written out as words. Send a shorter text or a different number_mode."},{"code":"idempotency_conflict","status":409,"type":"invalid_request_error","retryable":false,"message":"This Idempotency-Key was already used with a different request. Use a new key for a new request."},{"code":"job_not_finished","status":409,"type":"invalid_request_error","retryable":true,"message":"The job has not finished yet. Poll GET /speech/{id} until its status is \"succeeded\"."},{"code":"job_failed","status":409,"type":"invalid_request_error","retryable":false,"message":"The job failed or was cancelled, so it has no audio. See the job's error for details."},{"code":"job_finished","status":409,"type":"invalid_request_error","retryable":false,"message":"The job has already finished and can no longer be cancelled."},{"code":"audio_expired","status":410,"type":"invalid_request_error","retryable":false,"message":"The audio for this job is no longer available: it is deleted after the retention period."},{"code":"internal_error","status":500,"type":"api_error","retryable":true,"message":"Something went wrong on our side. Retry the request; if it keeps failing, contact support@athanlab.com with the request_id."}],"x-job-error-codes":[{"code":"input_rejected","retryable":false,"message":"The text could not be turned into speech. Change the input before retrying. The characters were refunded."},{"code":"capacity_unavailable","retryable":true,"message":"No speech capacity became available before the job deadline. Retry later. The characters were refunded."},{"code":"generation_failed","retryable":true,"message":"Speech generation failed. Retry the request. The characters were refunded."},{"code":"interrupted","retryable":true,"message":"The job was interrupted by a server restart. Retry the request. The characters were refunded."}],"x-job-statuses":["processing","succeeded","failed","cancelled"],"x-scopes":["speech:write","speech:read","voices:read","usage:read"],"x-limits":{"text_max_characters":5000,"expanded_text_max_characters":10000,"metadata_max_keys":16,"metadata_key_max_characters":40,"metadata_value_max_characters":500,"metadata_max_bytes":8000,"request_body_max_bytes":131072,"list_default_limit":20,"list_max_limit":100,"idempotency_window_hours":24,"audio_retention_days":30,"job_deadline_minutes":45,"concurrent_jobs_per_developer":2,"concurrent_jobs_per_developer_max":8,"write_requests_per_minute_per_key":60,"read_requests_per_minute_per_key":600,"requests_per_minute_per_network":600,"failed_auth_attempts_per_network":20,"failed_auth_window_minutes":5,"failed_auth_block_minutes":10,"concurrent_downloads_per_account":4,"keys_per_developer":5,"key_expiry_days":[30,90,180,365],"key_default_expiry_days":365,"key_rotation_grace_hours":24,"preview_url_ttl_seconds":3600,"delivery_max_chapters":50,"delivery_max_characters":100000,"delivery_chapter_max_characters":5000,"delivery_title_max_characters":120,"delivery_sources_max":20,"delivery_source_url_max_characters":2048,"delivery_source_title_max_characters":200,"delivery_sources_max_bytes":16000,"delivery_request_body_max_bytes":1048576,"delivery_active_per_account":3,"delivery_queue_max_hours":168,"delivery_poll_interval_seconds":30},"x-removed-endpoints":[{"method":"POST","path":"/generate","status":410,"code":"endpoint_removed","summary":"Synchronous generation","replacement":{"method":"POST","path":"/speech","operationId":"createSpeech"},"note":"Create an asynchronous job with `POST /speech`, poll it, then download the audio."},{"method":"GET","path":"/balance","status":410,"code":"endpoint_removed","summary":"Balance","replacement":{"method":"GET","path":"/usage","operationId":"getUsage"},"note":"Use `GET /usage`."}],"x-delivery-statuses":["queued","processing","succeeded","partially_succeeded","failed","deleted"],"x-delivery-chapter-statuses":["queued","processing","succeeded","failed","cancelled"],"x-delivery-chapter-error-codes":[{"code":"input_rejected","retryable":false,"message":"The chapter text could not be turned into speech. Change it before sending it again. The characters were refunded."},{"code":"capacity_unavailable","retryable":true,"message":"No speech capacity became available for this chapter. Send it again later. The characters were refunded."},{"code":"generation_failed","retryable":true,"message":"Speech generation failed for this chapter. Send it again. The characters were refunded."},{"code":"interrupted","retryable":true,"message":"The chapter was interrupted by server restarts. Send it again. The characters were refunded."},{"code":"voice_unavailable","retryable":false,"message":"The voice was no longer available when the chapter ran. Send it again with another voice. The characters were refunded."},{"code":"expired","retryable":true,"message":"The chapter waited too long to be generated. Send it again. The characters were refunded."}]}