{"openapi":"3.0.3","info":{"title":"Lanson Audio V1","description":"Audio transcription API with two authenticated planes: a bidirectional realtime WebSocket gateway and an asynchronous offline transcription workflow. Use `POST /v1/audio/transcriptions` for an audio URL already reachable by the service, or upgrade `GET /v1/audio/transcriptions/stream` for realtime PCM16LE audio events.\n\nOffline result artifacts live in R2 under `transcription/{request_id}/`:\n- `chunk_{i}.json` — per-chunk transcription checkpoint\n- `result.json` — raw aggregated transcript\n- `review_chunk_{i}.json` — first-stage per-chunk error annotations (when `review: true`)\n- `reviewed_result.json` — final corrected transcript + change log (when `review: true`)","version":"0.1.0"},"servers":[{"url":"https://lanson-audio-v1.lanson.workers.dev"}],"paths":{"/health":{"get":{"summary":"Health check","operationId":"health","responses":{"200":{"description":"Service is up","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/readyz":{"get":{"summary":"Realtime readiness check","operationId":"readiness","description":"Checks realtime upstream configuration and its health endpoint. Returns 503 when the gateway cannot currently accept realtime traffic.","responses":{"200":{"description":"Realtime gateway is ready"},"503":{"description":"Realtime gateway is not ready"}}}},"/":{"post":{"summary":"Submit a transcription job (legacy path)","operationId":"createLegacyTranscriptionJob","description":"Backward-compatible alias for POST /v1/audio/transcriptions. Requires an API key and returns a workflow id that can be polled via GET /{workflowId}.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionParams"}}}},"responses":{"202":{"description":"Job accepted and queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"400":{"description":"Invalid request (missing audio_url etc.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/audio/transcriptions":{"post":{"summary":"Submit an offline transcription job","operationId":"createTranscriptionJob","description":"Submit an audio URL to be transcribed. The file must already be reachable over HTTP(S), for example from Cloudflare R2 or a CDN. The service fetches it temporarily, slices it, transcribes the segments, and optionally sends the final result to a webhook.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionParams"}}}},"responses":{"202":{"description":"Job accepted and queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"400":{"description":"Invalid request body or audio URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing API key"}}}},"/v1/audio/transcriptions/{workflowId}":{"get":{"summary":"Get workflow status (REST path)","operationId":"getWorkflowStatusByRestPath","description":"REST alias for GET /{workflowId}. Cloudflare Workflow ids look like `cf_` plus 64 hex characters, not UUIDs.","parameters":[{"name":"workflowId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Workflow status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowStatus"}}}},"404":{"description":"Workflow instance not found"}}}},"/{workflowId}":{"get":{"summary":"Get workflow status","operationId":"getWorkflowStatus","description":"Returns the current status and (once complete) the full step output of the transcription workflow. Instance ids are Cloudflare Workflow ids (`cf_…`), not UUIDs.","parameters":[{"name":"workflowId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Workflow status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowStatus"}}}}}}},"/r2/{key}":{"get":{"summary":"Read a persisted result artifact from R2","operationId":"readR2Artifact","description":"Reads a JSON artifact from the transcription bucket, e.g. `transcription/{request_id}/result.json` or `transcription/{request_id}/reviewed_result.json`.","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The stored JSON artifact","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Artifact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/openapi.json":{"get":{"summary":"OpenAPI specification","operationId":"getOpenApi","responses":{"200":{"description":"This document"}}}},"/docs":{"get":{"summary":"Swagger UI","operationId":"getDocs","responses":{"200":{"description":"Swagger UI HTML"}}}},"/v1/audio/transcriptions/stream":{"get":{"summary":"Realtime transcription WebSocket","description":"Upgrade to WebSocket. Authenticate with Authorization: Bearer sk-... or a short-lived token from POST /v1/audio/transcriptions/session-token. Send input_audio_buffer.append events containing PCM16LE base64 audio, followed by input_audio_buffer.flush. VAD and utterance splitting are performed server-side by the configured upstream.","operationId":"realtimeTranscription","security":[{"BearerAuth":[]}],"responses":{"101":{"description":"WebSocket session established"},"401":{"description":"Invalid or missing API key"},"503":{"description":"Realtime upstream unavailable"}}}},"/v1/audio/transcriptions/session-token":{"post":{"summary":"Issue a short-lived browser WebSocket token","operationId":"createRealtimeSessionToken","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Token valid for 60 seconds"},"401":{"description":"Invalid or missing API key"},"503":{"description":"Token issuer is not configured"}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"Use a provisioned Lanson Audio API key, for example `Authorization: Bearer sk-...`."}},"schemas":{"HealthResponse":{"type":"object","required":["ok","service"],"properties":{"ok":{"type":"boolean"},"service":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"TranscriptionParams":{"type":"object","required":["audio_url"],"properties":{"audio_url":{"type":"string","description":"Public URL of the audio file to transcribe (CDN/R2). Must be http(s).","example":"https://cdn.example.com/audio/meeting.m4a"},"request_id":{"type":"string","description":"Optional idempotency key. Re-submitting with the same request_id resumes from existing R2 checkpoints (already-transcribed chunks are skipped)."},"language":{"type":"string","description":"Audio language hint (e.g. zh, en). Required by the Modal STT endpoint.","example":"zh"},"model":{"type":"string","description":"STT model override. Default: whisper-large-v3-turbo."},"prompt":{"type":"string","description":"Optional transcription prompt / context hint."},"segment_seconds":{"type":"number","default":300,"description":"Target slice length in seconds. Default ~5 minutes.","example":300},"response_format":{"type":"string","default":"verbose_json"},"concurrency":{"type":"number","default":6,"description":"Concurrent transcription of chunks."},"webhook_url":{"type":"string","description":"POSTed the full result (raw + reviewed) when the job completes."},"review":{"type":"boolean","default":false,"description":"When true, runs the two-stage review pipeline: per-chunk error detection (stage 1) then global review producing the final corrected transcript (stage 2)."},"metadata":{"type":"object","description":"Optional contextual metadata forwarded to the review stages (e.g. medical_specialty, speaker_roles, known_medications).","additionalProperties":true}}},"JobAccepted":{"type":"object","required":["request_id","workflow_id","status"],"properties":{"request_id":{"type":"string"},"workflow_id":{"type":"string"},"status":{"type":"string","enum":["queued"]},"endpoint":{"type":"string","description":"Poll endpoint: GET /{workflow_id}"},"poll_endpoint":{"type":"string","description":"REST poll endpoint: GET /v1/audio/transcriptions/{workflow_id}"}}},"WorkflowStatus":{"type":"object","required":["workflow_id","status"],"properties":{"workflow_id":{"type":"string"},"status":{"type":"string","enum":["queued","running","paused","errored","terminated","complete","waiting","waitingForPause","unknown"]},"steps":{"type":"array","description":"Step execution history (may be empty while queued).","items":{"type":"object"}},"output":{"type":"object","description":"Full workflow output when complete: probe, slice, chunks, aggregate, review, report."},"error":{"type":"object","properties":{"name":{"type":"string"},"message":{"type":"string"}}}}}}}}