{"openapi":"3.1.0","info":{"title":"TextFromTrack API","description":"Public API for transcribing audio files. Authenticate with a Personal Access Token created at https://app.textfromtrack.com/account/tokens. See https://github.com/r45635/audio-lyrics-extractor/blob/main/docs/api.md for a curl/Python/JS quickstart.","version":"1.0"},"paths":{"/api/v1/transcriptions":{"post":{"tags":["v1"],"summary":"Create Transcription","description":"Submit an audio file for transcription.\n\nReturns 201 with ``{job_id, status, credits_quoted}``. Poll\n``GET /api/v1/transcriptions/{job_id}`` until ``status == \"done\"``,\nthen download exports via ``GET /api/v1/transcriptions/{id}/export``.\n\n``timestamps`` controls which transcription model is used:\n\n- ``\"auto\"`` (default): use whatever the server is configured with.\n- ``\"required\"``: force a timestamps-capable model (whisper-1). Use\n  this when you need SRT/LRC output with real per-line timing.\n- ``\"none\"``: force a text-only model. Faster and cheaper; segments\n  are returned but all share the same 0→duration span (no real timing).\n\n``language`` is an optional ISO 639-1 two-letter code (e.g. ``\"fr\"``,\n``\"zh\"``) that skips Whisper's auto-detection and reduces hallucinations\non short tracks or tracks with minimal speech.\n\n``audio_type`` is ``\"auto\"`` (default), ``\"speech\"``, or ``\"music\"``.\n``\"music\"`` tightens the hallucination filter and forces vocal separation\nwhen ``MUSIC_MODE_FORCE_VOCAL_SEPARATION`` is enabled.","operationId":"create_transcription_api_v1_transcriptions_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_transcription_api_v1_transcriptions_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["v1"],"summary":"List Transcriptions","description":"Paginated list of the caller's transcription jobs (newest first).","operationId":"list_transcriptions_api_v1_transcriptions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Per Page"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status (pending/processing/done/error)","title":"Status"},"description":"Filter by status (pending/processing/done/error)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transcriptions/{job_id}":{"get":{"tags":["v1"],"summary":"Get Transcription","description":"Return the current state + telemetry for a transcription job.","operationId":"get_transcription_api_v1_transcriptions__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["v1"],"summary":"Delete Transcription","description":"Delete a transcription job and its exports.\n\nFor pending/processing jobs we mark the in-memory state as cancelled —\nthe worker thread already running can't be killed mid-flight (Python\nthreading limitation), but the user no longer sees the job. For done\njobs we drop the export directory and mark the row as deleted.","operationId":"delete_transcription_api_v1_transcriptions__job_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transcriptions/{job_id}/export":{"get":{"tags":["v1"],"summary":"Get Transcription Export","description":"Stream a generated export file. Returns 410 Gone after retention.","operationId":"get_transcription_export_api_v1_transcriptions__job_id__export_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"format","in":"query","required":true,"schema":{"type":"string","description":"One of: txt, srt, lrc, json","title":"Format"},"description":"One of: txt, srt, lrc, json"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transcriptions/{job_id}/segments":{"get":{"tags":["v1"],"summary":"Get Transcription Segments","description":"Return the transcript as a list of timestamped segments.\n\nThe same data is in the JSON export — this endpoint is a convenience\nso a desktop app can fetch structured segments without parsing a file\ndownload.","operationId":"get_transcription_segments_api_v1_transcriptions__job_id__segments_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/me":{"get":{"tags":["v1"],"summary":"Get Me","description":"Return the authenticated user's profile + credit balance.\n\nCrucially exposes ``top_up_url`` — the API never accepts payment;\ndesktop apps point users at the web flow when balance is low.","operationId":"get_me_api_v1_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/capabilities":{"get":{"tags":["v1"],"summary":"Get Capabilities","description":"Expose server-side API capabilities for client preflight checks.\n\nThird-party clients can call this once at startup and fail fast when a\ndeployment is too old to support required features (like timestamps mode),\ninstead of discovering it only after spending a credit.","operationId":"get_capabilities_api_v1_capabilities_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/webhooks":{"get":{"tags":["v1"],"summary":"List Webhooks","description":"List all active webhook endpoints for the authenticated user.\n\nSecrets are not returned — only a short ``secret_preview`` prefix.","operationId":"list_webhooks_api_v1_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["v1"],"summary":"Register Webhook","description":"Register a webhook endpoint URL.\n\nThe response includes the full plaintext ``secret`` — **store it\nimmediately**, it will not be returned again.  Use the secret to verify\nthe ``X-TFT-Signature: sha256=<hex>`` header on every delivery.\n\n**Signature verification example (Python)**::\n\n    import hashlib, hmac\n    def verify(secret: str, body: bytes, header: str) -> bool:\n        expected = \"sha256=\" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()\n        return hmac.compare_digest(expected, header)","operationId":"register_webhook_api_v1_webhooks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWebhookRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/webhooks/{hook_id}":{"delete":{"tags":["v1"],"summary":"Delete Webhook Endpoint","description":"Deregister a webhook endpoint.  Returns 204 on success, 404 if not\nfound (or belonging to a different user).","operationId":"delete_webhook_endpoint_api_v1_webhooks__hook_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","description":"Webhook endpoint ID","title":"Hook Id"},"description":"Webhook endpoint ID"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_create_transcription_api_v1_transcriptions_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"pinyin":{"type":"boolean","title":"Pinyin","default":false},"vintage":{"type":"boolean","title":"Vintage","default":false},"timestamps":{"type":"string","title":"Timestamps","default":"auto"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"ISO 639-1 language hint, e.g. 'en', 'fr', 'zh'. Skips Whisper auto-detection."},"audio_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Type","description":"'auto' | 'speech' | 'music'. 'music' tightens the hallucination filter."}},"type":"object","required":["file"],"title":"Body_create_transcription_api_v1_transcriptions_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"RegisterWebhookRequest":{"properties":{"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events","default":["job.done"]}},"type":"object","required":["url"],"title":"RegisterWebhookRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"PersonalAccessToken":{"type":"http","scheme":"bearer","bearerFormat":"tft_pat_*","description":"Personal Access Token. Format: ``tft_pat_<43 url-safe chars>``. Create at https://app.textfromtrack.com/account/tokens."}}},"servers":[{"url":"https://app.textfromtrack.com","description":"Production"}],"security":[{"PersonalAccessToken":[]}]}