{"openapi":"3.1.0","info":{"title":"Lesan AI Unified API - Public Doc","description":"Production-ready Speech-to-Text API supporting real-time streaming, batch processing, speaker diarization, and language auto-detection.\n\n## Authentication\nAll endpoints except `/health` require a Bearer token.\nUse `Authorization: Bearer <api_key>` header.\n\n## Pagination\nList endpoints return paginated results with `data`, `has_more`, `next_cursor`, and `total` fields. Pass `cursor` from the response to fetch the next page.\n\n## Errors\nAll errors follow the format: `{\"error\": {\"type\": \"...\", \"code\": \"...\", \"message\": \"...\"}}`\n\n**Note**: Admin endpoints are hidden here.\n\n**Machine-readable docs**: this spec is also served as JSON at `/openapi.json`. For LLM tooling, see `/llms.txt` (index) and `/llms-full.txt` (full text).","version":"4.0.0"},"servers":[{"url":"https://asr.lesan.ai","description":"ASR API (health, transcribe, jobs, webhooks, etc.)"},{"url":"https://api.lesan.ai","description":"Translation API (for /translate/v1 only)"}],"paths":{"/":{"get":{"tags":["Health"],"summary":"Root","description":"API root with available endpoints.","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RootResponse"}}}}}}},"/health":{"get":{"tags":["Health"],"summary":"Health Check","description":"Health check with system status.\n\nReturns information about:\n- Server status\n- Loaded ASR models\n- Available languages\n- Device (CPU/CUDA)\n- VAD availability\n- Storage configuration\n- Redis connectivity","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/languages":{"get":{"tags":["Health"],"summary":"List Languages","description":"List available languages for transcription.\n\nReturns:\n- Available language codes\n- Currently loaded models\n- Default language","operationId":"list_languages_v1_languages_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguagesResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}]}},"/v1/transcriptions":{"post":{"tags":["Transcription"],"summary":"Create transcription job","description":"Transcribe audio from URL or uploaded file.\n\n**Input Methods:**\n- **JSON** (`Content-Type: application/json`): Provide `audio_url`\n- **File Upload** (`Content-Type: multipart/form-data`): Provide `file`\n\nBoth sync and async modes use the same processing pipeline.\nJobs are enqueued to Redis for batch workers to process.\n\n**JSON Example:**\n```json\n{\n    \"audio_url\": \"https://example.com/audio.mp3\",\n    \"language\": \"en\",\n    \"mode\": \"async\"\n}\n```\n\n**File Upload Example (curl):**\n```bash\ncurl -X POST /transcribe \\\n  -H \"Authorization: Bearer <key>\" \\\n  -F \"file=@audio.mp3\" \\\n  -F \"language=en\" \\\n  -F \"mode=async\"\n```\n\n**Modes:**\n- `async` (default): Returns immediately with job_id for polling\n- `sync`: Waits for completion, returns full result\n\n**Supported Formats:** MP3, WAV, M4A, FLAC, OGG, WEBM, AAC","operationId":"transcribe_single_v1_transcriptions_post","security":[{"BearerAuth":[]}],"responses":{"202":{"description":"Transcription job created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Transcription"],"summary":"List transcription jobs","description":"List transcription jobs for the current user.\n\nSupports cursor-based pagination. Use `next_cursor` from the response\nto fetch the next page. Use `source` to filter by job origin\n('batch' for file transcriptions, 'websocket' for streaming sessions).","operationId":"list_jobs_v1_transcriptions_get","security":[{"BearerAuth":[]}],"parameters":[{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by source: 'batch' or 'websocket'","title":"Source"},"description":"Filter by source: 'batch' or 'websocket'"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionListResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/transcriptions/{job_id}":{"get":{"tags":["Transcription"],"summary":"Get transcription status and results","description":"Get status and results of a transcription job.\n\n**Statuses:**\n- `queued`: Job waiting to be processed\n- `processing`: Job currently running\n- `completed`: Job finished successfully\n- `failed`: Job failed with error\n- `cancelled`: Job was cancelled","operationId":"get_job_status_v1_transcriptions__job_id__get","security":[{"BearerAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionResponse"},"examples":{"singleFileJob":{"summary":"Single-file transcription job","value":{"completed_at":"2024-01-01T12:00:02Z","created_at":"2024-01-01T12:00:00Z","duration_seconds":2.54,"id":"txn_550e8400-e29b-41d4-a716-446655440000","language":"en","object":"transcription","processing_time_seconds":1.2,"segments":[{"duration_ms":2420,"end_ms":2540,"id":0,"start_ms":120,"text":"Hello, how are you today?"}],"status":"completed","text":"Hello, how are you today?","url":"/v1/transcriptions/txn_550e8400-e29b-41d4-a716-446655440000"}},"batchJob":{"summary":"Batch job — per-file results under metadata.jobs","value":{"id":"4e42d202-bb19-42f5-a0d6-f7abb9791191","object":"transcription","status":"completed","language":"am","text":null,"segments":null,"progress":{"stage":"completed","percent":100,"total_segments":2,"done_segments":2},"error":null,"metadata":{"batch_mode":true,"total_count":2,"completed_count":2,"failed_count":0,"processing_count":0,"queued_count":0,"jobs":[{"job_id":"15b8e1de-b0ab-43d0-825e-a09e1e973ce7","url":"https://example.com/audio1.mp3","status":"completed","text":"..."},{"job_id":"dd403af8-40f9-4cde-8c92-ba5c35e36c38","url":"https://example.com/audio2.mp3","status":"completed","text":"..."}]},"created_at":"2026-03-05T12:14:53.841894Z","completed_at":null,"url":"/v1/transcriptions/4e42d202-bb19-42f5-a0d6-f7abb9791191"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Transcription"],"summary":"Delete a transcription job","description":"Delete a transcription job and its data.","operationId":"delete_job_v1_transcriptions__job_id__delete","security":[{"BearerAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/transcriptions/batch":{"post":{"tags":["Transcription"],"summary":"Batch transcribe multiple audio files","description":"Submit multiple audio files for batch transcription.\n\n**Input Methods:**\n- **JSON** (`Content-Type: application/json`): Provide `audio_urls` array\n- **File Upload** (`Content-Type: multipart/form-data`): Provide multiple `files`\n\nEach file/URL is dispatched as a separate job to the async pipeline.\nThe batch job tracks overall progress.\n\nProcessing is always async. Poll `GET /transcribe/{job_id}` for results.\n\n**JSON Example:**\n```json\n{\n    \"audio_urls\": [\"https://example.com/a.mp3\", \"https://example.com/b.mp3\"],\n    \"language\": \"en\"\n}\n```\n\n**File Upload Example (curl):**\n```bash\ncurl -X POST /transcribe/batch \\\n  -H \"Authorization: Bearer <key>\" \\\n  -F \"files=@audio1.mp3\" \\\n  -F \"files=@audio2.mp3\" \\\n  -F \"language=en\"\n```\n\n**Supported Formats:** MP3, WAV, M4A, FLAC, OGG, WEBM, AAC\n\n**Polling batch results:** the 202 response represents the batch job; poll `GET /v1/transcriptions/{job_id}` with its `id`. Per-file results appear in `metadata.jobs[]`, each entry carrying `job_id`, `url` (the submitted audio URL), `status`, and `text`. Fetch a child `job_id` for that file’s full result.","operationId":"transcribe_batch_v1_transcriptions_batch_post","responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionResponse"},"example":{"id":"4e42d202-bb19-42f5-a0d6-f7abb9791191","object":"transcription","status":"queued","language":"am","text":null,"segments":null,"speakers":null,"progress":null,"error":null,"metadata":{"batch_mode":true,"total_count":2},"created_at":"2026-03-05T12:14:53.849727Z","completed_at":null,"url":"/v1/transcriptions/4e42d202-bb19-42f5-a0d6-f7abb9791191"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["audio_urls"],"properties":{"audio_urls":{"type":"array","items":{"type":"string"},"description":"Audio URLs to transcribe (https:// or lesan://). Up to 100 per batch."},"language":{"type":"string","description":"Language code, e.g. 'am', 'ti', or 'en'."}}},"example":{"audio_urls":["https://example.com/audio1.mp3","https://example.com/audio2.mp3"],"language":"am"}}}}}},"/v1/transcriptions/{job_id}/cancel":{"post":{"tags":["Transcription"],"summary":"Cancel a transcription job","description":"Cancel a transcription job.\n\nOnly jobs in `queued` or `processing` status can be cancelled.","operationId":"cancel_job_v1_transcriptions__job_id__cancel_post","security":[{"BearerAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Job cannot be cancelled (already completed/failed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/transcriptions/{job_id}/audio":{"get":{"tags":["Transcription"],"summary":"Get audio download URL","description":"Get a fresh signed URL for the job's audio artifact.\n\nReturns a 302 redirect to a time-limited signed URL (1 hour).\nOld jobs without a stored audio key return 404.","operationId":"get_audio_download_v1_transcriptions__job_id__audio_get","security":[{"BearerAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"302":{"description":"Redirect to signed audio URL"},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Job not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/transcriptions/{job_id}/transcript":{"get":{"tags":["Transcription"],"summary":"Get transcript download URL","description":"Get a fresh signed URL for the job's transcript JSON artifact.\n\nReturns a 302 redirect to a time-limited signed URL (1 hour).\nOld jobs without a stored transcript key return 404.","operationId":"get_transcript_download_v1_transcriptions__job_id__transcript_get","security":[{"BearerAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"302":{"description":"Redirect to signed transcript URL"},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Job not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/uploads":{"post":{"tags":["Production"],"summary":"Upload audio file","description":"Upload an audio file directly via multipart/form-data.\n\n**Supported formats:** mp3, wav, ogg, flac, m4a, webm\n\n**Max size:** 500MB\n\nAfter upload, use the returned `file_url` in your transcription request.","operationId":"upload_file_v1_uploads_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_file_v1_uploads_post"}}},"required":true},"responses":{"201":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileUploadResponse"}}}},"400":{"description":"Invalid file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"File too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}]}},"/v1/uploads/signed-url":{"get":{"tags":["Production"],"summary":"Get signed upload URL","description":"Generate a pre-signed URL for direct-to-storage upload.\n\nThis allows clients to upload large files directly to cloud storage,\nbypassing the API server. Ideal for:\n- Large files (>100MB)\n- Client-side uploads from browsers\n- Reducing API server load\n\n**Flow:**\n1. Call this endpoint to get a signed URL\n2. Upload file directly to the signed URL using PUT\n3. Use the returned `file_url` in your transcription request","operationId":"get_signed_upload_url_v1_uploads_signed_url_get","security":[{"BearerAuth":[]}],"parameters":[{"name":"filename","in":"query","required":true,"schema":{"type":"string","description":"Filename for the upload","title":"Filename"},"description":"Filename for the upload"},{"name":"content_type","in":"query","required":false,"schema":{"type":"string","description":"MIME type","default":"audio/mpeg","title":"Content Type"},"description":"MIME type"},{"name":"content_length","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Expected file size","title":"Content Length"},"description":"Expected file size"}],"responses":{"200":{"description":"Signed URL generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedUrlResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/webhooks":{"post":{"tags":["Production"],"summary":"Register webhook","description":"Register a webhook URL to receive job completion notifications.\n\n**Events:**\n- `job.completed` - Job finished successfully\n- `job.failed` - Job failed with error\n- `job.progress` - Job progress updates (optional)\n- `*` - All events\n\n**Payload:** See `WebhookPayload` schema for the JSON structure sent to your URL.\n\n**Signature Verification:** If you provide a `secret`, we'll sign payloads with HMAC-SHA256.\nThe signature is in the `X-Webhook-Signature` header: `t={timestamp},v1={signature}`","operationId":"create_webhook_v1_webhooks_post","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateRequest"}}}},"responses":{"201":{"description":"Webhook registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Production"],"summary":"List webhooks","description":"List all registered webhooks for the current API key.","operationId":"list_webhooks_v1_webhooks_get","security":[{"BearerAuth":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status: active, paused, failed","title":"Status"},"description":"Filter by status: active, paused, failed"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/webhooks/{webhook_id}":{"get":{"tags":["Production"],"summary":"Get webhook","description":"Get details of a specific webhook.","operationId":"get_webhook_v1_webhooks__webhook_id__get","security":[{"BearerAuth":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Production"],"summary":"Update webhook","description":"Update webhook configuration.","operationId":"update_webhook_v1_webhooks__webhook_id__patch","security":[{"BearerAuth":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Production"],"summary":"Delete webhook","description":"Delete a registered webhook.","operationId":"delete_webhook_v1_webhooks__webhook_id__delete","security":[{"BearerAuth":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"204":{"description":"Webhook deleted"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/webhooks/{webhook_id}/test":{"post":{"tags":["Production"],"summary":"Test webhook","description":"Send a test payload to verify webhook is working.","operationId":"test_webhook_v1_webhooks__webhook_id__test_post","security":[{"BearerAuth":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Test delivery result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/usage":{"get":{"tags":["Production"],"summary":"Get usage statistics","description":"Get current API usage and quota information.\n\nShows:\n- Current rate limit usage (minute/hour/day)\n- Concurrent job/connection limits\n- Audio minutes processed (if applicable)\n- Historical statistics","operationId":"get_usage_v1_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}]}},"/v1/media/probe":{"post":{"tags":["Media"],"summary":"Probe a media URL for metadata without downloading","description":"Inspect a remote media URL and return its duration, title, thumbnail,\nuploader, and related metadata. Useful for pre-submission credit gating\nand ETA display — clients typically call this before POST /v1/transcriptions.\n\nSupports yt-dlp platforms (YouTube, Twitter/X, TikTok, Vimeo, ...) and\ndirect HTTP(S) audio/video URLs. Results are cached in Redis for\n~5 minutes (when the Redis backend is active).","operationId":"probe_media_v1_media_probe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaProbeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaProbeResponse"}}}},"400":{"description":"Invalid URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Media unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Media not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream probe error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Probe timed out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}]}},"/v1/ws/transcribe":{"get":{"tags":["Streaming"],"summary":"Real-time streaming transcription","operationId":"transcribe_websocket","description":"WebSocket endpoint for real-time audio transcription.\n\n**Protocol:** The client opens a WebSocket connection (HTTP 101 Upgrade). After the server sends a `ready` message, the client streams audio as binary frames and sends text commands to control the session.\n\n## Authentication\nSend `Authorization: Bearer <api_key>` in the WebSocket upgrade headers. The token is validated **before** the connection is accepted.\n\n## Connection Lifecycle\n```\nClient                          Server\n  |--- WS Upgrade + Bearer -------->|\n  |                  (validate key) |\n  |<--------- 101 Switching --------|\n  |<-- {type: \"ready\"} -------------|\n  |--- binary audio data ---------->|\n  |<-- {type: \"chunk_received\"} ----|\n  |--- \"TRANSCRIBE\" --------------->|\n  |<-- {type: \"transcription\"} -----|\n  |--- \"END\" ---------------------->|\n  |<-- {type: \"transcription\",      |\n  |         is_final: true} ---------|\n  |         [connection closed]      |\n```\n\n## Client Commands (text frames)\n| Command | Description |\n|---------|-------------|\n| `FORMAT:<name>` | Change audio format (e.g. `FORMAT:pcm_s16le_48k`) |\n| `TRANSCRIBE` | Transcribe current buffer (keeps connection open) |\n| `END` | Transcribe buffer and close connection |\n| `CLEAR` | Discard audio buffer without transcribing |\n| `PING` | Health check; server replies with `pong` |\n\n## Audio Formats\n| Format | Sample Rate | Codec | Notes |\n|--------|-------------|-------|-------|\n| `pcm_s16le` | 16 kHz | PCM 16-bit LE | **Default** |\n| `pcm_s16le_48k` | 48 kHz | PCM 16-bit LE | Higher fidelity |\n| `wav` | 16 kHz | PCM in WAV | Standard WAV container |\n| `webm_opus` | 48 kHz | Opus in WebM | Browser MediaRecorder |\n| `opus_raw_16k` | 16 kHz | Raw Opus frames | WASM / low-level clients |\n\n## Server VAD Mode\nWhen `turn_detection=server_vad`, the server uses Voice Activity Detection to automatically segment speech into turns. Each completed turn triggers a `turn_detected` message with the transcription. No manual `TRANSCRIBE` commands are needed.\n\n**VAD algorithms:**\n- `energy` (default) - RMS energy threshold, fast and lightweight\n- `silero` - Neural network VAD, higher accuracy, requires PyTorch\n\n## Raw Opus Mode (`format=opus_raw_16k`)\nOptimized for WASM clients streaming individual Opus frames. Each binary message should contain exactly one Opus frame (20 ms at 16 kHz). Turn detection is always active; transcriptions are emitted automatically.\n\n## Limits\n- Max chunk size: 1 MB per message\n- Max buffer: 50 MB (~26 min at 16 kHz mono 16-bit)\n- Idle timeout: 300 seconds\n\n## Close Codes\n| Code | Meaning |\n|------|---------|\n| `1000` | Normal closure or idle timeout |\n| `1009` | Buffer limit exceeded (50 MB) |\n| `4001` | Missing Authorization header |\n| `4003` | Invalid API key |\n| `4029` | Rate limit or connection limit exceeded |\n\n## Server Messages\nAll server messages are JSON text frames with a `type` discriminator. See the `WebSocketServerMessage` schema for the full union type.","parameters":[{"name":"language","in":"query","required":false,"description":"Language code for transcription (e.g. `en`, `es`, `am`). Defaults to server default.","schema":{"type":"string","examples":["en"]}},{"name":"lang","in":"query","required":false,"deprecated":true,"description":"Deprecated. Use `language` instead.","schema":{"type":"string"}},{"name":"turn_detection","in":"query","required":false,"description":"Set to `server_vad` to enable automatic speech turn detection.","schema":{"type":"string","enum":["server_vad"]}},{"name":"vad_type","in":"query","required":false,"description":"VAD algorithm. `energy` is fast; `silero` is more accurate but requires PyTorch.","schema":{"type":"string","enum":["energy","silero"],"default":"energy"}},{"name":"format","in":"query","required":false,"description":"Audio format. Set to `opus_raw_16k` to use the raw Opus handler.","schema":{"type":"string","enum":["pcm_s16le","pcm_s16le_48k","wav","webm_opus","opus_raw_16k"]}}],"responses":{"101":{"description":"WebSocket connection established. Server immediately sends a `ready` JSON message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebSocketServerMessage"},"examples":{"ready":{"summary":"Initial ready message","value":{"type":"ready","session_id":"550e8400-e29b-41d4-a716-446655440000","format":"pcm_s16le","sample_rate":16000,"job_id":"txn_550e8400-e29b-41d4-a716-446655440000"}},"transcription":{"summary":"Final transcription result","value":{"type":"transcription","transcription":"hello world","language":"en","is_final":true,"is_turn":false,"duration_seconds":2.5,"processing_time_seconds":0.3,"audio_size_bytes":80000,"session_id":"550e8400-e29b-41d4-a716-446655440000","job_id":"txn_550e8400-e29b-41d4-a716-446655440000"}},"turn_detected":{"summary":"VAD turn detection","value":{"type":"turn_detected","transcription":"how are you","language":"en","turn_start_ms":0,"turn_end_ms":1500,"duration_seconds":1.5,"processing_time_seconds":0.2,"session_id":"550e8400-e29b-41d4-a716-446655440000"}},"error":{"summary":"Error message","value":{"type":"error","error":"Unsupported format: flac","session_id":"550e8400-e29b-41d4-a716-446655440000","supported_formats":["pcm_s16le","pcm_s16le_48k","wav","webm_opus","opus_raw_16k"]}}}}}}},"security":[{"BearerAuth":[]}],"x-websocket":true,"x-websocket-messages":{"client":{"binary":"Raw audio bytes in the configured format","text":["FORMAT:<format_name>","TRANSCRIBE","END","CLEAR","PING"]},"server":{"$ref":"#/components/schemas/WebSocketServerMessage"}}}},"/translate/v1":{"post":{"tags":["Translation"],"summary":"Translate text","description":"Translates input text from a source language to a target language. Supports translation between English, Amharic, and Tigrinya.\n\n**Server:** Use the **Translation API** server (select it from the server dropdown above) when trying this endpoint.","operationId":"translateText","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TranslationRequest"}}}},"responses":{"200":{"description":"Successful translation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationResponse"},"example":{"tgt_text":"ዛሬ እንዴት ነህ?"}}}},"400":{"description":"Bad request — missing or invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing_field":{"summary":"Missing required field","value":{"error":{"type":"invalid_request_error","code":"missing_required_field","message":"The 'text' field is required.","param":"text"}}},"invalid_language":{"summary":"Unsupported language","value":{"error":{"type":"invalid_request_error","code":"invalid_field_value","message":"Unsupported language 'xx'. Supported: en, am, ti.","param":"src_lang"}}}}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"authentication_error","code":"invalid_api_key","message":"The API key provided is invalid."}}}}},"429":{"description":"Too many requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"rate_limit_error","code":"rate_limit_exceeded","message":"Rate limit exceeded. Retry after 2 seconds.","retry_after":2}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"server_error","code":"internal_error","message":"An internal error occurred. Please try again."}}}}}},"servers":[{"url":"https://api.lesan.ai","description":"Translation API"}]},"servers":[{"url":"https://api.lesan.ai","description":"Translation API"}]}},"components":{"schemas":{"APIKeyCreatedResponse":{"properties":{"id":{"type":"string","title":"Id"},"key":{"type":"string","title":"Key","description":"The raw API key - SHOW ONLY ONCE"},"prefix":{"type":"string","title":"Prefix"},"key_type":{"type":"string","title":"Key Type"},"environment":{"type":"string","title":"Environment"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"created_at":{"type":"string","title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"rate_limits":{"type":"object","title":"Rate Limits"},"warning":{"type":"string","title":"Warning","default":"This is the only time you will see this key. Please store it securely."}},"type":"object","required":["id","key","prefix","key_type","environment","scopes","name","created_at","expires_at","rate_limits"],"title":"APIKeyCreatedResponse","description":"Response when a new API key is created."},"APIKeyListResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/APIKeyResponse"},"type":"array","title":"Keys"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["keys","total"],"title":"APIKeyListResponse","description":"Response for listing API keys."},"APIKeyResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"prefix":{"type":"string","title":"Prefix"},"key_fragment":{"type":"string","title":"Key Fragment"},"display_key":{"type":"string","title":"Display Key"},"key_type":{"type":"string","title":"Key Type"},"environment":{"type":"string","title":"Environment"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"rate_limits":{"type":"object","title":"Rate Limits"},"allowed_ips":{"items":{"type":"string"},"type":"array","title":"Allowed Ips"},"allowed_origins":{"items":{"type":"string"},"type":"array","title":"Allowed Origins"}},"type":"object","required":["id","user_id","prefix","key_fragment","display_key","key_type","environment","scopes","name","description","status","created_at","updated_at","expires_at","last_used_at","rate_limits","allowed_ips","allowed_origins"],"title":"APIKeyResponse","description":"API key metadata response (no raw key)."},"APIKeyRotatedResponse":{"properties":{"new_key":{"$ref":"#/components/schemas/APIKeyCreatedResponse"},"retired_key_id":{"type":"string","title":"Retired Key Id"},"retired_status":{"type":"string","title":"Retired Status","description":"'revoked' (grace 0) or 'active' until retired_expires_at"},"retired_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Retired Expires At","description":"When the old key expires, if a grace window was given"}},"type":"object","required":["new_key","retired_key_id","retired_status"],"title":"APIKeyRotatedResponse","description":"Response for a key rotation: the new key (raw, once) + the retired one."},"Body_upload_file_v1_uploads_post":{"properties":{"file":{"type":"string","format":"binary","title":"File","description":"Audio file to upload"}},"type":"object","required":["file"],"title":"Body_upload_file_v1_uploads_post"},"CreateAPIKeyRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Name","description":"Name/label for the key"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"Description"},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes","description":"Permission scopes: read, write, admin","default":["read","write"]},"key_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Type","description":"Key type: secret, publishable, restricted, admin","default":"secret"},"environment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Environment","description":"Environment: live, test, dev","default":"live"},"expires_in_days":{"anyOf":[{"type":"integer","maximum":365,"minimum":1},{"type":"null"}],"title":"Expires In Days","description":"Expiration in days (null for no expiration)"},"allowed_ips":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Ips","description":"IP whitelist (null for no restriction)"},"allowed_origins":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Origins","description":"Origin whitelist for CORS (null for no restriction)"},"rate_limits":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Rate Limits","description":"Custom rate limits"}},"type":"object","title":"CreateAPIKeyRequest","description":"Request to create a new API key.","example":{"allowed_ips":["10.0.0.1","10.0.0.2"],"description":"Main API key for production backend","environment":"live","expires_in_days":90,"key_type":"secret","name":"Production API Key","scopes":["read","write"]}},"DeleteResponse":{"properties":{"id":{"type":"string","title":"Id","description":"ID of deleted resource"},"object":{"type":"string","title":"Object","description":"Resource type"},"deleted":{"type":"boolean","title":"Deleted","description":"Whether deletion was successful","default":true}},"type":"object","required":["id","object"],"title":"DeleteResponse","description":"Standard delete response."},"ErrorDetail":{"properties":{"type":{"type":"string","title":"Type","description":"Error type category"},"code":{"type":"string","title":"Code","description":"Machine-readable error code"},"message":{"type":"string","title":"Message","description":"Human-readable error message"},"param":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Param","description":"Parameter that caused the error"}},"type":"object","required":["type","code","message"],"title":"ErrorDetail","description":"Structured error detail following Stripe-style error envelope.","example":{"code":"JOB_NOT_FOUND","message":"No transcription found with id txn_abc123","param":"id","type":"invalid_request_error"}},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"The category of error.","enum":["invalid_request_error","authentication_error","permission_error","rate_limit_error","server_error"],"example":"invalid_request_error"},"code":{"type":"string","description":"A machine-readable error code.","example":"missing_required_field"},"message":{"type":"string","description":"A human-readable description of the error.","example":"The 'text' field is required."},"param":{"type":"string","description":"The request parameter that caused the error, if applicable.","example":"text"},"retry_after":{"type":"integer","description":"Seconds to wait before retrying (only on 429 errors).","example":2}},"required":["type","code","message"]}}},"FileUploadResponse":{"properties":{"file_id":{"type":"string","title":"File Id","description":"Unique file identifier"},"file_url":{"type":"string","title":"File Url","description":"Internal URL to access the file"},"filename":{"type":"string","title":"Filename","description":"Original filename"},"size_bytes":{"type":"integer","title":"Size Bytes","description":"File size in bytes"},"content_type":{"type":"string","title":"Content Type","description":"MIME type"},"duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Seconds","description":"Audio duration if detected"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"When the file will be deleted"}},"type":"object","required":["file_id","file_url","filename","size_bytes","content_type"],"title":"FileUploadResponse","description":"Response after file upload.","example":{"content_type":"audio/mpeg","duration_seconds":125.5,"expires_at":"2024-01-02T12:00:00Z","file_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","file_url":"internal://uploads/f47ac10b-58cc-4372-a567-0e02b2c3d479","filename":"interview.mp3","size_bytes":5242880}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","enum":["healthy","unhealthy","degraded"],"title":"Status"},"version":{"type":"string","title":"Version","description":"API version"},"loaded_models":{"items":{"type":"string"},"type":"array","title":"Loaded Models"},"available_models":{"items":{"type":"string"},"type":"array","title":"Available Models"},"device":{"type":"string","title":"Device","description":"Compute device (cpu/cuda)"},"silero_vad_available":{"type":"boolean","title":"Silero Vad Available"},"ina_vad_available":{"type":"boolean","title":"Ina Vad Available","description":"INA Speech Segmenter availability","default":false},"streaming_available":{"type":"boolean","title":"Streaming Available","description":"WebSocket streaming availability","default":true},"storage_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Storage Provider","description":"Storage provider (local/gcs/s3)"},"storage_bucket":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Storage Bucket","description":"Storage bucket name (for gcs/s3)"},"storage_configured":{"type":"boolean","title":"Storage Configured","description":"Whether storage is configured","default":false},"redis_status":{"type":"string","enum":["healthy","unhealthy","not_configured"],"title":"Redis Status","description":"Redis connection status","default":"not_configured"},"redis_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redis Version","description":"Redis server version"},"redis_connected_clients":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Redis Connected Clients","description":"Number of Redis clients"},"redis_memory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redis Memory","description":"Redis memory usage"},"auth_enabled":{"type":"boolean","title":"Auth Enabled","description":"Whether API key authentication is enforced","default":true},"timestamp":{"type":"string","title":"Timestamp","description":"Current server time"}},"type":"object","required":["status","version","device","silero_vad_available","timestamp"],"title":"HealthResponse","description":"Health check response.","example":{"auth_enabled":true,"available_models":["en","am","ti"],"device":"cuda","ina_vad_available":false,"loaded_models":["en","am"],"redis_connected_clients":5,"redis_memory":"1.5M","redis_status":"healthy","redis_version":"7.2.3","silero_vad_available":true,"status":"healthy","storage_bucket":"my-bucket","storage_configured":true,"storage_provider":"gcs","streaming_available":true,"timestamp":"2024-12-31T12:00:00Z","version":"4.0.0"}},"JobStatusEnum":{"type":"string","enum":["queued","processing","streaming","completed","failed","cancelled"],"title":"JobStatusEnum","description":"Job status values."},"LanguagesResponse":{"properties":{"languages":{"items":{"type":"string"},"type":"array","title":"Languages","description":"Available language codes"},"loaded":{"items":{"type":"string"},"type":"array","title":"Loaded","description":"Currently loaded languages"},"default":{"type":"string","title":"Default","description":"Default language"}},"type":"object","required":["languages","loaded","default"],"title":"LanguagesResponse","description":"Available languages response."},"MediaProbeRequest":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url","description":"Public media URL to probe (http/https only)"}},"type":"object","required":["url"],"title":"MediaProbeRequest","description":"Request body for POST /v1/media/probe.","example":{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}},"MediaProbeResponse":{"properties":{"object":{"type":"string","enum":["media_probe"],"const":"media_probe","title":"Object","description":"Object type","default":"media_probe"},"url":{"type":"string","title":"Url","description":"Probed URL (canonical, as echoed back)"},"source":{"type":"string","title":"Source","description":"Detected source platform (youtube, twitter, direct, etc.)"},"extractor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extractor","description":"yt-dlp extractor name, or 'generic' for direct URLs"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Media title"},"uploader":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uploader","description":"Uploader/channel name"},"duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Seconds","description":"Duration in seconds (null if unknown)"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url","description":"Thumbnail image URL"},"upload_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upload Date","description":"Upload date (ISO 8601 YYYY-MM-DD)"},"is_live":{"type":"boolean","title":"Is Live","description":"True if the URL points to a live stream","default":false},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Declared source language, if any"},"file_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size Bytes","description":"Content-Length (direct URLs) or yt-dlp filesize_approx"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type","description":"MIME type (direct URLs)"},"warnings":{"items":{"type":"string","enum":["live_stream","duration_unknown","very_long","age_restricted"]},"type":"array","title":"Warnings","description":"Non-fatal issues the client should surface"},"cached":{"type":"boolean","title":"Cached","description":"True if this response came from the probe cache","default":false},"probed_at":{"type":"string","title":"Probed At","description":"UTC timestamp when the probe ran (ISO 8601)"},"metadata":{"type":"object","title":"Metadata","description":"Extractor-specific extras (view_count, description, etc.)"}},"type":"object","required":["url","source","probed_at"],"title":"MediaProbeResponse","description":"Metadata about a remote media URL, returned without downloading the media.\n\nIntended for pre-submission checks (credit gating, ETA display, live-stream\ndetection). `duration_seconds` is the primary field; if null, inspect\n`warnings` for the reason.","example":{"cached":false,"duration_seconds":213,"extractor":"youtube","is_live":false,"language":"en","metadata":{"description":"The official video...","like_count":16000000,"view_count":1500000000},"object":"media_probe","probed_at":"2026-04-23T12:34:56Z","source":"youtube","thumbnail_url":"https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg","title":"Rick Astley - Never Gonna Give You Up (Official Music Video)","upload_date":"2009-10-25","uploader":"Rick Astley","url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","warnings":[]}},"ProgressResponse":{"properties":{"stage":{"type":"string","title":"Stage","description":"Current processing stage"},"percent":{"type":"integer","maximum":100,"minimum":0,"title":"Percent","description":"Progress percentage"},"total_segments":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Segments","description":"Total segments to process"},"done_segments":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Done Segments","description":"Segments completed"}},"type":"object","required":["stage","percent"],"title":"ProgressResponse","description":"Job progress information."},"QuotaInfo":{"properties":{"limit":{"type":"integer","title":"Limit","description":"Maximum allowed"},"used":{"type":"integer","title":"Used","description":"Currently used"},"remaining":{"type":"integer","title":"Remaining","description":"Remaining quota"},"reset_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reset At","description":"When quota resets"}},"type":"object","required":["limit","used","remaining"],"title":"QuotaInfo","description":"Quota information for a specific limit."},"RevokeAPIKeyRequest":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Reason","description":"Reason for revocation"}},"type":"object","title":"RevokeAPIKeyRequest","description":"Request to revoke an API key."},"RotateAPIKeyRequest":{"properties":{"grace_minutes":{"type":"integer","maximum":10080,"minimum":0,"title":"Grace Minutes","description":"How long the old key stays valid after rotation (0 = revoke immediately). During the grace window both keys work, so clients can swap with no downtime; the old key then expires.","default":0}},"type":"object","title":"RotateAPIKeyRequest","description":"Request to rotate an API key.","example":{"grace_minutes":1440}},"SegmentResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"Segment index"},"start_ms":{"type":"integer","title":"Start Ms","description":"Start time in milliseconds"},"end_ms":{"type":"integer","title":"End Ms","description":"End time in milliseconds"},"duration_ms":{"type":"integer","title":"Duration Ms","description":"Duration in milliseconds"},"text":{"type":"string","title":"Text","description":"Transcribed text"},"speaker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker","description":"Speaker label (if diarization enabled)"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence","description":"Confidence score"}},"type":"object","required":["id","start_ms","end_ms","duration_ms","text"],"title":"SegmentResponse","description":"Transcribed segment in response."},"SignedUrlResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url","description":"Pre-signed URL for direct upload"},"file_id":{"type":"string","title":"File Id","description":"File identifier to use after upload"},"file_url":{"type":"string","title":"File Url","description":"URL to use in transcription request after upload completes"},"method":{"type":"string","title":"Method","description":"HTTP method to use for upload","default":"PUT"},"headers":{"additionalProperties":{"type":"string"},"type":"object","title":"Headers","description":"Required headers for the upload request"},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the signed URL expires"},"max_size_bytes":{"type":"integer","title":"Max Size Bytes","description":"Maximum allowed file size"}},"type":"object","required":["upload_url","file_id","file_url","expires_in","max_size_bytes"],"title":"SignedUrlResponse","description":"Response with signed upload URL.","example":{"expires_in":3600,"file_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","file_url":"gs://bucket/uploads/f47ac10b-58cc-4372-a567-0e02b2c3d479","headers":{"Content-Type":"audio/mpeg"},"max_size_bytes":524288000,"method":"PUT","upload_url":"https://storage.googleapis.com/bucket/uploads/f47ac10b...?X-Goog-Signature=..."}},"SpeakerInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Speaker identifier"},"label":{"type":"string","title":"Label","description":"Speaker label (e.g., 'Speaker 1')"},"segments_count":{"type":"integer","title":"Segments Count","description":"Number of segments for this speaker"}},"type":"object","required":["id","label","segments_count"],"title":"SpeakerInfo","description":"Speaker information from diarization."},"TranscriptionErrorInfo":{"properties":{"code":{"type":"string","title":"Code","description":"Error code"},"message":{"type":"string","title":"Message","description":"Error message"}},"type":"object","required":["code","message"],"title":"TranscriptionErrorInfo","description":"Error information for failed transcriptions."},"TranscriptionListResponse":{"properties":{"has_more":{"type":"boolean","title":"Has More","description":"Whether more results are available"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Cursor for next page"},"total":{"type":"integer","title":"Total","description":"Total matching items"},"data":{"items":{"$ref":"#/components/schemas/TranscriptionResponse"},"type":"array","title":"Data","description":"List of transcriptions"}},"type":"object","required":["has_more","total"],"title":"TranscriptionListResponse","description":"Paginated list of transcriptions (v4).","example":{"data":[],"has_more":false,"total":0}},"TranscriptionResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Transcription job ID"},"object":{"type":"string","enum":["transcription"],"const":"transcription","title":"Object","description":"Object type","default":"transcription"},"status":{"$ref":"#/components/schemas/JobStatusEnum","description":"Job status"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Language code (detected if 'auto')"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Full transcribed text"},"segments":{"anyOf":[{"items":{"$ref":"#/components/schemas/SegmentResponse"},"type":"array"},{"type":"null"}],"title":"Segments","description":"Transcribed segments"},"speakers":{"anyOf":[{"items":{"$ref":"#/components/schemas/SpeakerInfo"},"type":"array"},{"type":"null"}],"title":"Speakers","description":"Speaker info (if diarization enabled)"},"progress":{"anyOf":[{"$ref":"#/components/schemas/ProgressResponse"},{"type":"null"}],"description":"Progress info"},"duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Seconds","description":"Audio duration in seconds"},"processing_time_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Processing Time Seconds","description":"Processing time in seconds"},"error":{"anyOf":[{"$ref":"#/components/schemas/TranscriptionErrorInfo"},{"type":"null"}],"description":"Error details (failed jobs)"},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata","description":"Processing metadata"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"Creation time (ISO 8601)"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"Completion time (ISO 8601)"},"result_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Url","description":"URL to transcript JSON artifact (completed jobs)"},"audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Url","description":"Canonical audio reference (lesan:// URI)"},"audio_download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Download Url","description":"Stable URL to download audio (302 redirect to signed URL)"},"url":{"type":"string","title":"Url","description":"Self link"}},"type":"object","required":["id","status","url"],"title":"TranscriptionResponse","description":"Unified transcription response (v4).\n\nReplaces JobStatusResponse + JobCreatedResponse + TranscriptionResultResponse.\nUsed for both creation (202) and status polling (200).","example":{"completed_at":"2024-01-01T12:00:02Z","created_at":"2024-01-01T12:00:00Z","duration_seconds":2.54,"id":"txn_550e8400-e29b-41d4-a716-446655440000","language":"en","object":"transcription","processing_time_seconds":1.2,"segments":[{"duration_ms":2420,"end_ms":2540,"id":0,"start_ms":120,"text":"Hello, how are you today?"}],"status":"completed","text":"Hello, how are you today?","url":"/v1/transcriptions/txn_550e8400-e29b-41d4-a716-446655440000"}},"UpdateAPIKeyRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Name","description":"New name/label"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"New description"},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes","description":"New permission scopes"},"allowed_ips":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Ips","description":"New IP whitelist"},"allowed_origins":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Origins","description":"New origin whitelist"},"rate_limits":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Rate Limits","description":"New rate limits"}},"type":"object","title":"UpdateAPIKeyRequest","description":"Request to update an API key.","example":{"name":"Updated Key Name","scopes":["read","write"]}},"UsageResponse":{"properties":{"tier":{"type":"string","title":"Tier","description":"Current subscription tier"},"requests_minute":{"$ref":"#/components/schemas/QuotaInfo","description":"Requests per minute quota"},"requests_hour":{"$ref":"#/components/schemas/QuotaInfo","description":"Requests per hour quota"},"requests_day":{"$ref":"#/components/schemas/QuotaInfo","description":"Requests per day quota"},"concurrent_jobs":{"$ref":"#/components/schemas/QuotaInfo","description":"Concurrent job limit"},"websocket_connections":{"$ref":"#/components/schemas/QuotaInfo","description":"WebSocket connection limit"},"audio_minutes_month":{"anyOf":[{"$ref":"#/components/schemas/QuotaInfo"},{"type":"null"}],"description":"Audio minutes this month"},"total_requests":{"type":"integer","title":"Total Requests","description":"Total requests all time"},"total_jobs":{"type":"integer","title":"Total Jobs","description":"Total jobs processed","default":0},"total_audio_minutes":{"type":"number","title":"Total Audio Minutes","description":"Total audio minutes processed","default":0},"account_created_at":{"type":"string","title":"Account Created At","description":"Account creation date"}},"type":"object","required":["tier","requests_minute","requests_hour","requests_day","concurrent_jobs","websocket_connections","total_requests","account_created_at"],"title":"UsageResponse","description":"API usage and quota information.","example":{"account_created_at":"2023-06-15T10:30:00Z","audio_minutes_month":{"limit":6000,"remaining":5755,"reset_at":"2024-02-01T00:00:00Z","used":245},"concurrent_jobs":{"limit":10,"remaining":8,"used":2},"requests_day":{"limit":10000,"remaining":9500,"used":500},"requests_hour":{"limit":1000,"remaining":850,"used":150},"requests_minute":{"limit":30,"remaining":25,"used":5},"tier":"pro","total_audio_minutes":3500.5,"total_jobs":1200,"total_requests":15000,"websocket_connections":{"limit":10,"remaining":9,"used":1}}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WebhookCreateRequest":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url","description":"URL to receive webhook notifications"},"events":{"items":{"$ref":"#/components/schemas/WebhookEventEnum"},"type":"array","title":"Events","description":"Events to subscribe to","default":["job.completed"]},"secret":{"anyOf":[{"type":"string","maxLength":256,"minLength":16},{"type":"null"}],"title":"Secret","description":"Secret for HMAC signature verification (optional)"},"name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Name","description":"Friendly name for the webhook"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"Description"}},"type":"object","required":["url"],"title":"WebhookCreateRequest","description":"Request to create a webhook.","example":{"events":["job.completed","job.failed"],"name":"Production Webhook","secret":"whsec_your_secret_key_here","url":"https://your-server.com/webhooks/transcription"}},"WebhookEventEnum":{"type":"string","enum":["job.completed","job.failed","job.progress","*"],"title":"WebhookEventEnum","description":"Webhook event types."},"WebhookListResponse":{"properties":{"has_more":{"type":"boolean","title":"Has More","description":"Whether more results are available"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Cursor for next page"},"total":{"type":"integer","title":"Total","description":"Total matching items"},"data":{"items":{"$ref":"#/components/schemas/WebhookResponse"},"type":"array","title":"Data"}},"type":"object","required":["has_more","total"],"title":"WebhookListResponse","description":"Paginated list of webhooks (v4)."},"WebhookResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Webhook ID"},"object":{"type":"string","enum":["webhook"],"const":"webhook","title":"Object","description":"Object type","default":"webhook"},"url":{"type":"string","title":"Url","description":"Callback URL"},"events":{"items":{"type":"string"},"type":"array","title":"Events","description":"Subscribed events"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"type":"string","title":"Status","description":"Status: active, paused, failed"},"total_deliveries":{"type":"integer","title":"Total Deliveries","description":"Total delivery attempts","default":0},"successful_deliveries":{"type":"integer","title":"Successful Deliveries","description":"Successful deliveries","default":0},"failed_deliveries":{"type":"integer","title":"Failed Deliveries","description":"Failed deliveries","default":0},"last_delivery_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Delivery At","description":"Last delivery timestamp"},"last_failure_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Failure Reason","description":"Last failure reason"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","url","events","status","created_at"],"title":"WebhookResponse","description":"Webhook configuration response.","example":{"created_at":"2024-01-01T12:00:00Z","events":["job.completed","job.failed"],"failed_deliveries":2,"id":"wh_550e8400-e29b-41d4-a716-446655440000","name":"Production Webhook","object":"webhook","status":"active","successful_deliveries":40,"total_deliveries":42,"url":"https://your-server.com/webhooks/transcription"}},"WebhookUpdateRequest":{"properties":{"url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Url","description":"New URL"},"events":{"anyOf":[{"items":{"$ref":"#/components/schemas/WebhookEventEnum"},"type":"array"},{"type":"null"}],"title":"Events","description":"New event subscriptions"},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret","description":"New secret"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"New name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New description"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Enable/disable webhook"}},"type":"object","title":"WebhookUpdateRequest","description":"Request to update a webhook."},"AdminModelsResponse":{"type":"object","properties":{"loaded":{"type":"array","items":{"type":"string"},"description":"Currently loaded language codes"},"available":{"type":"array","items":{"type":"string"},"description":"All available language codes"},"device":{"type":"string","description":"Compute device (cpu or cuda)","examples":["cuda"]},"cache_info":{"type":"object","description":"Model cache information"}},"required":["loaded","available","device"],"title":"AdminModelsResponse"},"AdminModelActionResponse":{"type":"object","properties":{"message":{"type":"string"},"loaded_models":{"type":"array","items":{"type":"string"}}},"required":["message","loaded_models"],"title":"AdminModelActionResponse"},"WebhookTestResponse":{"type":"object","properties":{"success":{"type":"boolean"},"status_code":{"type":"integer"},"response_body":{"type":"string"},"error":{"type":"string","nullable":true},"duration_ms":{"type":"integer"}},"required":["success","status_code","duration_ms"],"title":"WebhookTestResponse"},"RevokeKeyResponse":{"type":"object","properties":{"message":{"type":"string"},"key_id":{"type":"string"},"status":{"type":"string","examples":["revoked"]},"revoked_at":{"type":"string","format":"date-time","nullable":true}},"required":["message","key_id","status"],"title":"RevokeKeyResponse"},"AdminKeyStatsResponse":{"type":"object","properties":{"total_keys":{"type":"integer"},"active_keys":{"type":"integer"},"revoked_keys":{"type":"integer"},"expired_keys":{"type":"integer"},"keys_by_type":{"type":"object"},"keys_by_environment":{"type":"object"}},"required":["total_keys","active_keys","revoked_keys"],"title":"AdminKeyStatsResponse"},"RootResponse":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"docs":{"type":"string"},"endpoints":{"type":"object"}},"title":"RootResponse"},"WebSocketReadyMessage":{"type":"object","description":"Sent by the server immediately after connection is accepted.","properties":{"type":{"type":"string","const":"ready"},"session_id":{"type":"string","format":"uuid","description":"Unique session identifier"},"format":{"type":"string","description":"Negotiated audio format","examples":["pcm_s16le"]},"sample_rate":{"type":"integer","description":"Sample rate in Hz","examples":[16000]},"job_id":{"type":"string","description":"Job ID for post-session retrieval. Present when Redis backend is enabled."}},"required":["type","session_id","format","sample_rate"],"title":"WebSocketReadyMessage"},"WebSocketFormatAcceptedMessage":{"type":"object","description":"Sent after a FORMAT:<name> command is accepted.","properties":{"type":{"type":"string","const":"format_accepted"},"format":{"type":"string"},"sample_rate":{"type":"integer"},"channels":{"type":"integer"},"bit_depth":{"type":"integer"},"session_id":{"type":"string","format":"uuid"}},"required":["type","format","sample_rate","channels","bit_depth","session_id"],"title":"WebSocketFormatAcceptedMessage"},"WebSocketChunkReceivedMessage":{"type":"object","description":"Acknowledgement of an audio chunk.","properties":{"type":{"type":"string","const":"chunk_received"},"chunk_number":{"type":"integer"},"buffer_size":{"type":"integer","description":"Current buffer size in bytes"},"session_id":{"type":"string","format":"uuid"}},"required":["type","chunk_number","buffer_size","session_id"],"title":"WebSocketChunkReceivedMessage"},"WebSocketTranscriptionMessage":{"type":"object","description":"Transcription result returned after a TRANSCRIBE or END command.","properties":{"type":{"type":"string","const":"transcription"},"transcription":{"type":"string","description":"Transcribed text"},"language":{"type":"string","description":"Language code used"},"is_final":{"type":"boolean","description":"True if triggered by END command"},"is_turn":{"type":"boolean"},"duration_seconds":{"type":"number","description":"Audio duration in seconds"},"processing_time_seconds":{"type":"number","description":"ASR inference time in seconds"},"audio_size_bytes":{"type":"integer"},"session_id":{"type":"string","format":"uuid"},"job_id":{"type":"string","description":"Job ID for post-session retrieval. Present in the final message (is_final: true) when Redis backend is enabled."}},"required":["type","transcription","language","is_final","duration_seconds","processing_time_seconds","session_id"],"title":"WebSocketTranscriptionMessage"},"WebSocketTurnDetectedMessage":{"type":"object","description":"Sent when server VAD detects a completed speech turn. Only emitted when turn_detection=server_vad.","properties":{"type":{"type":"string","const":"turn_detected"},"transcription":{"type":"string"},"language":{"type":"string"},"turn_start_ms":{"type":"integer","description":"Turn start offset in milliseconds"},"turn_end_ms":{"type":"integer","description":"Turn end offset in milliseconds"},"duration_seconds":{"type":"number"},"processing_time_seconds":{"type":"number"},"session_id":{"type":"string","format":"uuid"}},"required":["type","transcription","language","turn_start_ms","turn_end_ms","session_id"],"title":"WebSocketTurnDetectedMessage"},"WebSocketBufferClearedMessage":{"type":"object","description":"Sent after a CLEAR command.","properties":{"type":{"type":"string","const":"buffer_cleared"},"cleared_bytes":{"type":"integer"},"session_id":{"type":"string","format":"uuid"}},"required":["type","cleared_bytes","session_id"],"title":"WebSocketBufferClearedMessage"},"WebSocketPongMessage":{"type":"object","description":"Response to a PING command.","properties":{"type":{"type":"string","const":"pong"},"session_id":{"type":"string","format":"uuid"},"buffer_size":{"type":"integer"},"session_duration_seconds":{"type":"number"}},"required":["type","session_id"],"title":"WebSocketPongMessage"},"WebSocketErrorMessage":{"type":"object","description":"Sent when an error occurs during the session.","properties":{"type":{"type":"string","const":"error"},"error":{"type":"string","description":"Error description"},"session_id":{"type":"string","format":"uuid"},"supported_formats":{"type":"array","items":{"type":"string"},"description":"Included when the error is about an unsupported format"}},"required":["type","error"],"title":"WebSocketErrorMessage"},"WebSocketServerMessage":{"oneOf":[{"$ref":"#/components/schemas/WebSocketReadyMessage"},{"$ref":"#/components/schemas/WebSocketFormatAcceptedMessage"},{"$ref":"#/components/schemas/WebSocketChunkReceivedMessage"},{"$ref":"#/components/schemas/WebSocketTranscriptionMessage"},{"$ref":"#/components/schemas/WebSocketTurnDetectedMessage"},{"$ref":"#/components/schemas/WebSocketBufferClearedMessage"},{"$ref":"#/components/schemas/WebSocketPongMessage"},{"$ref":"#/components/schemas/WebSocketErrorMessage"}],"discriminator":{"propertyName":"type","mapping":{"ready":"#/components/schemas/WebSocketReadyMessage","format_accepted":"#/components/schemas/WebSocketFormatAcceptedMessage","chunk_received":"#/components/schemas/WebSocketChunkReceivedMessage","transcription":"#/components/schemas/WebSocketTranscriptionMessage","turn_detected":"#/components/schemas/WebSocketTurnDetectedMessage","buffer_cleared":"#/components/schemas/WebSocketBufferClearedMessage","pong":"#/components/schemas/WebSocketPongMessage","error":"#/components/schemas/WebSocketErrorMessage"}},"title":"WebSocketServerMessage","description":"Union of all server-to-client WebSocket messages, discriminated by `type`."},"TranslationRequest":{"type":"object","required":["key","text","src_lang","tgt_lang"],"properties":{"key":{"type":"string","description":"Your API key for authentication.","example":"YOUR_API_KEY"},"text":{"type":"string","description":"The text to translate.","example":"How are you doing today?"},"src_lang":{"type":"string","description":"ISO 639-1 language code of the source text.","enum":["en","am","ti"],"example":"en"},"tgt_lang":{"type":"string","description":"ISO 639-1 language code for the translation output.","enum":["en","am","ti"],"example":"am"}}},"TranslationResponse":{"type":"object","properties":{"tgt_text":{"type":"string","description":"The translated text in the target language.","example":"ዛሬ እንዴት ነህ?"}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key with sk_ prefix. Use Authorization: Bearer <key>"}}},"tags":[{"name":"Health","description":"System health and status checks"},{"name":"Streaming","description":"Real-time WebSocket transcription with multi-format audio support"},{"name":"Transcription","description":"Create, monitor, cancel, and list transcription jobs"},{"name":"Production","description":"File uploads, webhooks, and usage statistics"},{"name":"Translation","description":"Translate text between supported languages"}],"security":[{"BearerAuth":[]}]}