{ "openapi": "3.1.0", "info": { "title": "Scriberr API", "version": "v1" }, "paths": { "/health": { "get": { "summary": "Health check", "responses": { "200": { "description": "Service is alive" } } } }, "/api/v1/health": { "get": { "summary": "API health check", "responses": { "200": { "description": "API is alive" } } } }, "/api/v1/ready": { "get": { "summary": "Readiness check", "responses": { "200": { "description": "Service is ready" }, "503": { "description": "Service is not ready" } } } }, "/api/v1/auth/registration-status": { "get": { "summary": "Get registration status", "responses": { "200": { "description": "Registration status" } } } }, "/api/v1/auth/register": { "post": { "summary": "Register the local user", "responses": { "200": { "description": "Token response" }, "409": { "description": "Registration already completed" }, "422": { "description": "Validation error" } } } }, "/api/v1/auth/login": { "post": { "summary": "Log in", "responses": { "200": { "description": "Token response" }, "401": { "description": "Invalid credentials" } } } }, "/api/v1/auth/refresh": { "post": { "summary": "Rotate refresh token", "responses": { "200": { "description": "Token response" }, "401": { "description": "Invalid refresh token" } } } }, "/api/v1/auth/logout": { "post": { "summary": "Revoke refresh token", "responses": { "200": { "description": "Logout accepted" } } } }, "/api/v1/auth/me": { "get": { "summary": "Get current user", "responses": { "200": { "description": "Current user" }, "401": { "description": "Authentication required" } } } }, "/api/v1/auth/change-password": { "post": { "summary": "Change password", "responses": { "200": { "description": "Password changed" }, "401": { "description": "Authentication failed" }, "422": { "description": "Validation error" } } } }, "/api/v1/auth/change-username": { "post": { "summary": "Change username", "responses": { "200": { "description": "Username changed" }, "401": { "description": "Authentication failed" } } } }, "/api/v1/api-keys": { "get": { "summary": "List API keys", "responses": { "200": { "description": "API key list" }, "401": { "description": "JWT required" } } }, "post": { "summary": "Create API key", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key. Exact retries return the original successful response; mismatched reuse returns 409.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "201": { "description": "API key created" }, "401": { "description": "JWT required" } } } }, "/api/v1/api-keys/{id}": { "delete": { "summary": "Delete API key", "responses": { "204": { "description": "API key deleted" }, "401": { "description": "JWT required" }, "404": { "description": "API key not found" } } } }, "/api/v1/files": { "get": { "summary": "List files", "parameters": [ { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } }, { "name": "cursor", "in": "query", "description": "Opaque pagination cursor returned as next_cursor." }, { "name": "q", "in": "query", "description": "Case-insensitive title search." }, { "name": "kind", "in": "query", "schema": { "type": "string", "enum": ["audio", "video", "youtube"] } }, { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["uploaded", "processing", "ready", "failed"] } }, { "name": "updated_after", "in": "query", "schema": { "type": "string", "format": "date-time" } }, { "name": "sort", "in": "query", "schema": { "type": "string", "enum": ["created_at", "-created_at", "updated_at", "-updated_at", "title", "-title"] } } ], "responses": { "200": { "description": "File list" }, "401": { "description": "Authentication required" }, "422": { "description": "Invalid query parameter" } } }, "post": { "summary": "Upload file", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for upload creation.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "201": { "description": "File uploaded" }, "400": { "description": "Invalid upload" }, "401": { "description": "Authentication required" }, "413": { "description": "Upload too large" }, "415": { "description": "Unsupported media type" } } } }, "/api/v1/files:import-youtube": { "post": { "summary": "Import YouTube media", "description": "Creates a processing YouTube file resource, downloads media asynchronously through the configured importer, then transitions the file to ready or failed. Events are published as file.processing, file.ready, or file.failed.", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for YouTube import creation.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "202": { "description": "YouTube import accepted with status processing" }, "401": { "description": "Authentication required" }, "409": { "description": "Idempotency key conflict" }, "422": { "description": "Validation error, including unsupported URL host or scheme" } } } }, "/api/v1/files/{id}": { "get": { "summary": "Get file", "responses": { "200": { "description": "File" }, "404": { "description": "File not found" } } }, "patch": { "summary": "Update file metadata", "responses": { "200": { "description": "File updated" }, "422": { "description": "Validation error" } } }, "delete": { "summary": "Delete file", "responses": { "204": { "description": "File deleted" } } } }, "/api/v1/files/{id}/audio": { "get": { "summary": "Stream file audio", "responses": { "200": { "description": "Audio stream" }, "206": { "description": "Partial audio stream" }, "416": { "description": "Invalid range" } } } }, "/api/v1/recordings": { "get": { "summary": "List recording sessions", "description": "Lists browser recording sessions for the authenticated user. Recording sessions are the durable ingest workflow before a finalized audio file exists.", "parameters": [ { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } } ], "responses": { "200": { "description": "Recording session list" }, "401": { "description": "Authentication required" } } }, "post": { "summary": "Create recording session", "description": "Creates a durable browser recording session before chunk upload begins. Chunks are uploaded separately and finalization is requested with the stop command.", "responses": { "201": { "description": "Recording session created" }, "401": { "description": "Authentication required" }, "422": { "description": "Invalid recording request" } } } }, "/api/v1/recordings/{id}": { "get": { "summary": "Get recording session", "responses": { "200": { "description": "Recording session" }, "401": { "description": "Authentication required" }, "404": { "description": "Recording not found" } } } }, "/api/v1/recordings/{id}/chunks/{chunk_index}": { "put": { "summary": "Upload recording chunk", "description": "Stores one browser MediaRecorder chunk. The same chunk index may be retried idempotently when checksum and size match.", "parameters": [ { "name": "X-Chunk-SHA256", "in": "header", "description": "Optional hex SHA-256 digest for idempotency and integrity validation." }, { "name": "X-Chunk-Duration-Ms", "in": "header", "description": "Optional chunk duration in milliseconds." } ], "responses": { "201": { "description": "Chunk stored" }, "401": { "description": "Authentication required" }, "409": { "description": "Chunk conflicts with existing data or session state" }, "413": { "description": "Chunk is too large" }, "422": { "description": "Invalid chunk request" } } } }, "/api/v1/recordings/{id}:stop": { "post": { "summary": "Stop recording session", "description": "Marks recording ingest complete and queues durable finalization into one audio file.", "responses": { "202": { "description": "Recording queued for finalization" }, "409": { "description": "Recording cannot be stopped from its current state" } } } }, "/api/v1/recordings/{id}:cancel": { "post": { "summary": "Cancel recording session", "responses": { "200": { "description": "Recording canceled" }, "409": { "description": "Recording cannot be canceled from its current state" } } } }, "/api/v1/recordings/{id}:retry-finalize": { "post": { "summary": "Retry recording finalization", "responses": { "202": { "description": "Recording finalization queued" }, "409": { "description": "Recording is not retryable" } } } }, "/api/v1/transcriptions": { "get": { "summary": "List transcriptions", "parameters": [ { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } }, { "name": "cursor", "in": "query", "description": "Opaque pagination cursor returned as next_cursor." }, { "name": "q", "in": "query", "description": "Case-insensitive title search." }, { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["queued", "processing", "completed", "failed", "canceled"] } }, { "name": "updated_after", "in": "query", "schema": { "type": "string", "format": "date-time" } }, { "name": "sort", "in": "query", "schema": { "type": "string", "enum": ["created_at", "-created_at", "updated_at", "-updated_at", "title", "-title"] } } ], "responses": { "200": { "description": "Transcription list" }, "401": { "description": "Authentication required" }, "422": { "description": "Invalid query parameter" } } }, "post": { "summary": "Create transcription", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for transcription creation.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "202": { "description": "Transcription queued" }, "404": { "description": "Source file not found" }, "422": { "description": "Validation error" } } } }, "/api/v1/transcriptions:submit": { "post": { "summary": "Upload and create transcription", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for combined upload and transcription creation.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "202": { "description": "File uploaded and transcription queued" }, "400": { "description": "Invalid multipart request" }, "415": { "description": "Unsupported media type" } } } }, "/api/v1/transcriptions/{id}": { "get": { "summary": "Get transcription", "responses": { "200": { "description": "Transcription" }, "404": { "description": "Transcription not found" } } }, "patch": { "summary": "Update transcription metadata", "responses": { "200": { "description": "Transcription updated" } } }, "delete": { "summary": "Delete transcription", "responses": { "204": { "description": "Transcription deleted" } } } }, "/api/v1/transcriptions/{id}:cancel": { "post": { "summary": "Cancel transcription", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for cancel command.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "200": { "description": "Transcription canceled" }, "409": { "description": "State conflict" } } } }, "/api/v1/transcriptions/{id}:retry": { "post": { "summary": "Retry transcription", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for retry command.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "202": { "description": "Retry queued" } } } }, "/api/v1/transcriptions/{id}/transcript": { "get": { "summary": "Get transcript", "responses": { "200": { "description": "Transcript" } } } }, "/api/v1/transcriptions/{id}/annotations": { "get": { "summary": "List transcript annotations", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^tr_.+" } }, { "name": "kind", "in": "query", "schema": { "type": "string", "enum": ["highlight", "note"] } }, { "name": "updated_after", "in": "query", "schema": { "type": "string", "format": "date-time" } }, { "name": "cursor", "in": "query", "description": "Opaque pagination cursor returned as next_cursor." }, { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } } ], "responses": { "200": { "description": "Annotation list" }, "401": { "description": "Authentication required" }, "404": { "description": "Transcription not found" }, "422": { "description": "Invalid query parameter" } } }, "post": { "summary": "Create transcript annotation", "description": "Creates a note or highlight anchored to transcript timing and optional word or character offsets.", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^tr_.+" } }, { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for annotation creation.", "schema": { "type": "string", "maxLength": 128 } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnnotationCreateRequest" } } } }, "responses": { "201": { "description": "Annotation created" }, "401": { "description": "Authentication required" }, "404": { "description": "Transcription not found" }, "422": { "description": "Validation error" } } } }, "/api/v1/transcriptions/{id}/annotations/{annotation_id}": { "get": { "summary": "Get transcript annotation", "responses": { "200": { "description": "Annotation" }, "401": { "description": "Authentication required" }, "404": { "description": "Annotation not found" } } }, "patch": { "summary": "Update transcript annotation", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnnotationUpdateRequest" } } } }, "responses": { "200": { "description": "Annotation updated" }, "401": { "description": "Authentication required" }, "404": { "description": "Annotation not found" }, "422": { "description": "Validation error" } } }, "delete": { "summary": "Delete transcript annotation", "responses": { "204": { "description": "Annotation deleted" }, "401": { "description": "Authentication required" }, "404": { "description": "Annotation not found" } } } }, "/api/v1/transcriptions/{id}/annotations/{annotation_id}/entries": { "post": { "summary": "Create transcript note entry", "description": "Adds a note entry to an existing note annotation root. The parent annotation must be kind note.", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^tr_.+" } }, { "name": "annotation_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^ann_.+" } }, { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for note entry creation.", "schema": { "type": "string", "maxLength": 128 } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnnotationEntryRequest" } } } }, "responses": { "201": { "description": "Note entry created" }, "401": { "description": "Authentication required" }, "404": { "description": "Annotation not found" }, "422": { "description": "Validation error" } } } }, "/api/v1/transcriptions/{id}/annotations/{annotation_id}/entries/{entry_id}": { "patch": { "summary": "Update transcript note entry", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^tr_.+" } }, { "name": "annotation_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^ann_.+" } }, { "name": "entry_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^annent_.+" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnnotationEntryRequest" } } } }, "responses": { "200": { "description": "Note entry updated" }, "401": { "description": "Authentication required" }, "404": { "description": "Entry not found" }, "422": { "description": "Validation error" } } }, "delete": { "summary": "Delete transcript note entry", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^tr_.+" } }, { "name": "annotation_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^ann_.+" } }, { "name": "entry_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^annent_.+" } } ], "responses": { "204": { "description": "Note entry deleted" }, "401": { "description": "Authentication required" }, "404": { "description": "Entry not found" } } } }, "/api/v1/transcriptions/{id}/audio": { "get": { "summary": "Stream transcription audio", "responses": { "200": { "description": "Audio stream" }, "206": { "description": "Partial audio stream" } } } }, "/api/v1/transcriptions/{id}/events": { "get": { "summary": "Stream transcription events", "description": "Authenticated Server-Sent Events stream filtered to the requested transcription. Replay with Last-Event-ID is not supported yet.", "responses": { "200": { "description": "SSE stream using text/event-stream" }, "401": { "description": "Authentication required" }, "404": { "description": "Transcription not found" } } } }, "/api/v1/transcriptions/{id}/logs": { "get": { "summary": "Get transcription logs", "responses": { "501": { "description": "Deferred placeholder" } } } }, "/api/v1/transcriptions/{id}/executions": { "get": { "summary": "Get transcription executions", "responses": { "501": { "description": "Deferred placeholder" } } } }, "/api/v1/profiles": { "get": { "summary": "List profiles", "responses": { "200": { "description": "Profile list" } } }, "post": { "summary": "Create profile", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for profile creation.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "201": { "description": "Profile created" }, "422": { "description": "Validation error" } } } }, "/api/v1/profiles/{id}": { "get": { "summary": "Get profile", "responses": { "200": { "description": "Profile" }, "404": { "description": "Profile not found" } } }, "patch": { "summary": "Update profile", "responses": { "200": { "description": "Profile updated" } } }, "delete": { "summary": "Delete profile", "responses": { "204": { "description": "Profile deleted" } } } }, "/api/v1/profiles/{id}:set-default": { "post": { "summary": "Set default profile", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for set-default command.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "200": { "description": "Profile set as default" } } } }, "/api/v1/settings": { "get": { "summary": "Get settings", "responses": { "200": { "description": "Settings" } } }, "patch": { "summary": "Update settings", "responses": { "200": { "description": "Settings updated" }, "422": { "description": "Validation error" } } } }, "/api/v1/chat/models": { "get": { "summary": "List configured chat models", "description": "Returns active LLM provider models with context-window metadata. Returns LLM_PROVIDER_NOT_CONFIGURED when chat cannot run.", "responses": { "200": { "description": "Chat model capabilities" }, "409": { "description": "LLM provider is not configured" }, "503": { "description": "LLM provider is unavailable" } } } }, "/api/v1/chat/sessions": { "get": { "summary": "List chat sessions for a parent transcript", "parameters": [ { "name": "parent_transcription_id", "in": "query", "required": true, "schema": { "type": "string", "example": "tr_123" } } ], "responses": { "200": { "description": "Chat session collection envelope" } } }, "post": { "summary": "Create chat session", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for session creation.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "201": { "description": "Chat session created with parent transcript context" }, "409": { "description": "LLM provider is not configured" }, "422": { "description": "Requested model is not available" } } } }, "/api/v1/chat/sessions/{session_id}": { "get": { "summary": "Get chat session", "responses": { "200": { "description": "Chat session" }, "404": { "description": "Chat session not found" } } }, "patch": { "summary": "Update chat session", "responses": { "200": { "description": "Chat session updated" } } }, "delete": { "summary": "Delete chat session", "responses": { "204": { "description": "Chat session deleted" } } } }, "/api/v1/chat/sessions/{session_id}/context": { "get": { "summary": "List chat context sources", "description": "Returns context source metadata and state only; raw transcript text and compacted snapshots are not exposed.", "responses": { "200": { "description": "Context source collection envelope" } } } }, "/api/v1/chat/sessions/{session_id}/context/transcripts": { "post": { "summary": "Add transcript context source", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for context-source creation.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "201": { "description": "Context source added" }, "404": { "description": "Transcript not found or not completed" } } } }, "/api/v1/chat/sessions/{session_id}/context/transcripts/{context_source_id}": { "patch": { "summary": "Update context source state", "responses": { "200": { "description": "Context source updated" } } }, "delete": { "summary": "Remove context source", "responses": { "204": { "description": "Context source removed" } } } }, "/api/v1/chat/sessions/{session_id}/messages:stream": { "post": { "summary": "Create user message and stream assistant response", "description": "Server-Sent Events stream. Reasoning and final Markdown content are emitted separately with chat.delta.reasoning and chat.delta.content. The terminal event includes assistant_message.content and assistant_message.reasoning_content.", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Optional retry key for stream start.", "schema": { "type": "string", "maxLength": 128 } } ], "responses": { "200": { "description": "SSE stream using text/event-stream" }, "409": { "description": "LLM provider is not configured" }, "422": { "description": "Validation error or model unavailable" } } } }, "/api/v1/chat/runs/{run_id}:cancel": { "post": { "summary": "Cancel chat generation run", "responses": { "200": { "description": "Run canceled or already terminal" }, "404": { "description": "Run not found" } } } }, "/api/v1/chat/sessions/{session_id}/title:generate": { "post": { "summary": "Generate chat session title", "responses": { "200": { "description": "Generated title" } } } }, "/api/v1/events": { "get": { "summary": "Stream global events", "description": "Authenticated Server-Sent Events stream for API-visible file, transcription, profile, and settings changes. Replay with Last-Event-ID is not supported yet.", "responses": { "200": { "description": "SSE stream using text/event-stream" }, "401": { "description": "Authentication required" } } } }, "/api/v1/models/transcription": { "get": { "summary": "List transcription models", "responses": { "200": { "description": "Model capabilities" } } } }, "/api/v1/admin/queue": { "get": { "summary": "Queue stats", "responses": { "200": { "description": "Queue stats" } } } } }, "components": { "schemas": { "AnnotationAnchor": { "type": "object", "required": ["start_ms", "end_ms"], "properties": { "start_ms": { "type": "integer", "minimum": 0 }, "end_ms": { "type": "integer", "minimum": 0 }, "start_word": { "type": "integer", "minimum": 0 }, "end_word": { "type": "integer", "minimum": 0 }, "start_char": { "type": "integer", "minimum": 0 }, "end_char": { "type": "integer", "minimum": 0 }, "text_hash": { "type": "string" } } }, "AnnotationCreateRequest": { "type": "object", "required": ["kind", "quote", "anchor"], "properties": { "kind": { "type": "string", "enum": ["highlight", "note"] }, "content": { "type": "string" }, "color": { "type": "string" }, "quote": { "type": "string" }, "anchor": { "$ref": "#/components/schemas/AnnotationAnchor" } } }, "AnnotationUpdateRequest": { "type": "object", "properties": { "content": { "type": "string" }, "color": { "type": "string" }, "quote": { "type": "string" }, "anchor": { "$ref": "#/components/schemas/AnnotationAnchor" } } }, "AnnotationEntryRequest": { "type": "object", "required": ["content"], "properties": { "content": { "type": "string" } } } }, "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer" }, "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" } } } }