{"openapi":"3.0.3","info":{"title":"TranscriptHub","version":"1.0.0","description":"REST API for requesting YouTube video transcripts. Async processing via queue, credit-based billing, outbound webhooks on completion."},"servers":[{"url":"/api","description":"Current host (all paths are mounted under /api)"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"transcripts"},{"name":"credits"},{"name":"api-keys"},{"name":"account"},{"name":"admin"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sk_live_<32+ base62>","description":"API key. Create via POST /v1/api-keys (admin scope)."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"INVALID_VIDEO_URL"},"message":{"type":"string"},"details":{}}}}},"Pagination":{"type":"object","properties":{"page":{"type":"integer","minimum":1},"limit":{"type":"integer","minimum":1,"maximum":100},"total":{"type":"integer"},"total_pages":{"type":"integer"}}},"TranscriptStatus":{"type":"string","enum":["todo","doing","done","error","deleted"]},"Transcript":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"video_url":{"type":"string"},"video_id":{"type":"string","nullable":true},"playlist_url":{"type":"string","nullable":true},"playlist_index":{"type":"integer","nullable":true},"playlist_total":{"type":"integer","nullable":true},"requested_language":{"type":"string","nullable":true},"detected_language":{"type":"string","nullable":true},"status":{"$ref":"#/components/schemas/TranscriptStatus"},"transcript_text":{"type":"string","nullable":true},"error_message":{"type":"string","nullable":true},"attempt_count":{"type":"integer"},"cost_credits":{"type":"integer"},"api_provider":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true},"deleted_at":{"type":"string","format":"date-time","nullable":true},"keep_until":{"type":"string","format":"date-time","nullable":true}}},"TranscriptCreateRequest":{"type":"object","properties":{"videos":{"type":"array","items":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"YouTube video URL"},"requested_language":{"type":"string","description":"BCP-47 tag"}}}},"playlists":{"type":"array","items":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"YouTube playlist URL"},"requested_language":{"type":"string"}}}},"webhook_url":{"type":"string","description":"Override account webhook for these items"}}},"TranscriptCreateResponse":{"type":"object","properties":{"transcript_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"playlists":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"accepted":{"type":"boolean"},"message":{"type":"string"}}}}}},"CreditBalance":{"type":"object","properties":{"balance":{"type":"integer"},"updated_at":{"type":"string","format":"date-time"}}},"CreditTransaction":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["purchase","usage","refund","adjustment"]},"amount":{"type":"integer","description":"Positive = credit, negative = debit"},"balance_after":{"type":"integer"},"package_id":{"type":"string","format":"uuid","nullable":true},"transcript_request_id":{"type":"string","format":"uuid","nullable":true},"stripe_session_id":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"metadata":{"type":"object","additionalProperties":true},"created_at":{"type":"string","format":"date-time"}}},"CreditPurchaseRequest":{"type":"object","required":["package_id"],"properties":{"package_id":{"type":"string","format":"uuid"},"success_url":{"type":"string"},"cancel_url":{"type":"string"}}},"CreditPurchaseResponse":{"type":"object","properties":{"checkout_url":{"type":"string"},"transaction_id":{"type":"string","format":"uuid"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"key_prefix":{"type":"string"},"scope":{"type":"string","enum":["read","read-write","admin"]},"last_used_at":{"type":"string","format":"date-time","nullable":true},"revoked_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"ApiKeyCreateRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"scope":{"type":"string","enum":["read","read-write","admin"],"default":"read-write"}}},"ApiKeyCreated":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"key":{"type":"string","description":"Plaintext key — shown once"}}}]},"WebhookConfig":{"type":"object","properties":{"webhook_url":{"type":"string","nullable":true},"webhook_secret":{"type":"string","nullable":true,"description":"Returned only on regenerate"},"updated_at":{"type":"string","format":"date-time"}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"transcript_request_id":{"type":"string","format":"uuid"},"target_url":{"type":"string"},"event_type":{"type":"string"},"status":{"type":"string","enum":["pending","success","failed","retrying"]},"attempt_count":{"type":"integer"},"response_status":{"type":"integer","nullable":true},"last_attempt_at":{"type":"string","format":"date-time","nullable":true},"next_attempt_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"}},"Retry-After":{"schema":{"type":"integer"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ValidationError":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":{"Page":{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}}},"paths":{"/v1/transcripts":{"get":{"tags":["transcripts"],"summary":"List transcripts","parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/TranscriptStatus"}},{"name":"playlist_url","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Transcript"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["transcripts"],"summary":"Create transcript requests (videos and/or playlists)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptCreateRequest"}}}},"responses":{"201":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TranscriptCreateResponse"}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/transcripts/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["transcripts"],"summary":"Retrieve a transcript","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Transcript"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["transcripts"],"summary":"Retry an errored transcript","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["todo"]},"requested_language":{"type":"string"}}}}}},"responses":{"200":{"description":"OK"},"402":{"description":"Insufficient credits"},"409":{"description":"Invalid status transition"}}},"delete":{"tags":["transcripts"],"summary":"Delete a transcript","responses":{"204":{"description":"Deleted"},"409":{"description":"Cannot delete while processing"}}}},"/v1/credits/balance":{"get":{"tags":["credits"],"summary":"Get current credit balance","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreditBalance"}}}}}}}}},"/v1/credits/transactions":{"get":{"tags":["credits"],"summary":"List credit transactions","parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"name":"type","in":"query","schema":{"type":"string","enum":["purchase","usage","refund","adjustment"]}}],"responses":{"200":{"description":"OK"}}}},"/v1/credits/purchase":{"post":{"tags":["credits"],"summary":"Create a Stripe checkout session for credit purchase","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditPurchaseRequest"}}}},"responses":{"201":{"description":"Checkout created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreditPurchaseResponse"}}}}}}}}},"/v1/api-keys":{"get":{"tags":["api-keys"],"summary":"List API keys (admin scope)","responses":{"200":{"description":"OK"}}},"post":{"tags":["api-keys"],"summary":"Create an API key (admin scope) — plaintext returned once","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ApiKeyCreated"}}}}}}}}},"/v1/api-keys/{id}":{"delete":{"tags":["api-keys"],"summary":"Revoke an API key","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Revoked"}}}},"/v1/account/webhook":{"get":{"tags":["account"],"summary":"Get current outbound webhook config (secret never returned)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookConfig"}}}}}}}},"patch":{"tags":["account"],"summary":"Set outbound webhook URL (regenerates secret, returned once)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["webhook_url"],"properties":{"webhook_url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookConfig"}}}}}}}},"delete":{"tags":["account"],"summary":"Clear outbound webhook configuration","responses":{"204":{"description":"Cleared"}}}},"/v1/account/webhook/deliveries":{"get":{"tags":["account"],"summary":"List webhook delivery attempts","parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","success","failed","retrying"]}},{"name":"transcript_id","in":"query","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}}}}}}}