{
  "openapi": "3.0.3",
  "info": {
    "title": "EasyTranscriber Complete API",
    "version": "2026-06-08",
    "description": "Complete OpenAPI inventory for EasyTranscriber. Includes the public REST API, private/session app endpoints, Notion and Stripe integration endpoints, the retired in-app MCP stub, the standalone MCP service, and the internal yt-dlp/Deepgram service.",
    "contact": {
      "name": "EasyTranscriber",
      "url": "https://easytranscriber.com"
    },
    "termsOfService": "https://easytranscriber.com/terms"
  },
  "servers": [
    {
      "url": "https://easytranscriber.com",
      "description": "Main production app"
    }
  ],
  "tags": [
    { "name": "Public REST API", "description": "API-key authenticated endpoints intended for external customers." },
    { "name": "App Transcription", "description": "Website and Chrome extension transcription endpoints." },
    { "name": "App Summarization", "description": "Website and Chrome extension summary/chat endpoints." },
    { "name": "Audio Upload", "description": "Local audio upload and transcription flow." },
    { "name": "Account", "description": "Credits, API keys, auth, history, and migration endpoints." },
    { "name": "Billing", "description": "Stripe checkout, portal, subscriptions, purchases, and pricing endpoints." },
    { "name": "Notion", "description": "Notion OAuth, destination, and export endpoints." },
    { "name": "Utility", "description": "Contact form, OpenGraph image, playlist helper, and operational utility endpoints." },
    { "name": "MCP", "description": "Model Context Protocol endpoints." },
    { "name": "yt-dlp Service", "description": "Internal Fly.io service for YouTube metadata, captions, audio streaming, and local-audio transcription." }
  ],
  "paths": {
    "/api/v1/transcribe": {
      "options": {
        "operationId": "corsApiV1Transcribe",
        "summary": "CORS preflight for public transcription",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "post": {
        "operationId": "publicTranscribe",
        "summary": "Transcribe a YouTube video",
        "description": "Extract a full YouTube transcript. Cached transcripts are free; new transcriptions debit credits based on video duration and refund on server-side failure.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PublicTranscribeRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transcript response.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PublicTranscribeResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/v1/summarize": {
      "options": {
        "operationId": "corsApiV1Summarize",
        "summary": "CORS preflight for public summarization",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "post": {
        "operationId": "publicSummarize",
        "summary": "Summarize transcript text",
        "description": "Generate an AI summary from transcript text. Costs 1 credit. Supports JSON mode by default and text streaming with `stream=true`.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "description": "When true, returns a plain text stream instead of JSON.",
            "schema": { "type": "boolean", "default": false }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SummarizeRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Summary JSON or streamed text.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/PublicSummarizeResponse" } },
              "text/plain": { "schema": { "type": "string" } }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/v1/credits": {
      "options": {
        "operationId": "corsApiV1Credits",
        "summary": "CORS preflight for public credit balance",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "publicGetCredits",
        "summary": "Get API credit balance",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "responses": {
          "200": {
            "description": "Credit balance.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreditBalanceResponse" } } }
          },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" }
        }
      }
    },
    "/api/v1/youtube/search": {
      "options": {
        "operationId": "corsApiV1YouTubeSearch",
        "summary": "CORS preflight for YouTube search",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "publicSearchYouTube",
        "summary": "Search YouTube",
        "description": "Search YouTube videos or channels. Costs 1 credit.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/SearchQuery" },
          {
            "name": "type",
            "in": "query",
            "schema": { "type": "string", "enum": ["video", "channel"], "default": "video" },
            "description": "Search result type."
          },
          { "$ref": "#/components/parameters/PageToken" }
        ],
        "responses": {
          "200": {
            "description": "Search results.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YouTubeSearchResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/v1/youtube/channel/resolve": {
      "options": {
        "operationId": "corsApiV1ResolveChannel",
        "summary": "CORS preflight for channel resolve",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "publicResolveChannel",
        "summary": "Resolve a YouTube channel",
        "description": "Resolve an @handle, channel URL, or UC... channel ID. Free, no credit cost.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/Channel" }],
        "responses": {
          "200": {
            "description": "Channel details.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelResolveResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "404": { "$ref": "#/components/responses/NotFoundV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/v1/youtube/channel/latest": {
      "options": {
        "operationId": "corsApiV1ChannelLatest",
        "summary": "CORS preflight for latest channel videos",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "publicGetChannelLatest",
        "summary": "Get latest channel videos",
        "description": "Fetches the latest videos from YouTube RSS. Free, no credit cost.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/Channel" }],
        "responses": {
          "200": {
            "description": "Latest videos.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelLatestResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "404": { "$ref": "#/components/responses/NotFoundV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/v1/youtube/channel/search": {
      "options": {
        "operationId": "corsApiV1ChannelSearch",
        "summary": "CORS preflight for channel search",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "publicSearchChannel",
        "summary": "Search within a channel",
        "description": "Search for videos inside a specific YouTube channel. Costs 1 credit.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/Channel" },
          { "$ref": "#/components/parameters/SearchQuery" },
          { "$ref": "#/components/parameters/PageToken" }
        ],
        "responses": {
          "200": {
            "description": "Channel search results.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelSearchResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsV1" },
          "404": { "$ref": "#/components/responses/NotFoundV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/v1/youtube/channel/videos": {
      "options": {
        "operationId": "corsApiV1ChannelVideos",
        "summary": "CORS preflight for channel videos",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "publicGetChannelVideos",
        "summary": "List channel uploads",
        "description": "List uploads from a channel, up to 50 per page. Costs 1 credit per page.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/Channel" },
          { "$ref": "#/components/parameters/PageToken" }
        ],
        "responses": {
          "200": {
            "description": "Channel videos.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelVideosResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsV1" },
          "404": { "$ref": "#/components/responses/NotFoundV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/v1/youtube/playlist/videos": {
      "options": {
        "operationId": "corsApiV1PlaylistVideos",
        "summary": "CORS preflight for playlist videos",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "publicGetPlaylistVideos",
        "summary": "List playlist videos",
        "description": "List videos in a YouTube playlist, up to 50 per page. Costs 1 credit per page.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/Playlist" },
          { "$ref": "#/components/parameters/PageToken" }
        ],
        "responses": {
          "200": {
            "description": "Playlist videos.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PlaylistVideosResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/v1/youtube/comments": {
      "options": {
        "operationId": "corsApiV1Comments",
        "summary": "CORS preflight for YouTube comments",
        "tags": ["Public REST API"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "publicGetComments",
        "summary": "Get YouTube comments",
        "description": "Get top-level comments and available replies for a YouTube video. Costs 1 credit per page.",
        "tags": ["Public REST API"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/Video" },
          {
            "name": "max_results",
            "in": "query",
            "description": "Number of comment threads per page.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
          },
          {
            "name": "order",
            "in": "query",
            "schema": { "type": "string", "enum": ["relevance", "time"], "default": "relevance" }
          },
          { "$ref": "#/components/parameters/PageToken" }
        ],
        "responses": {
          "200": {
            "description": "Comments.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CommentsResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsV1" },
          "403": { "$ref": "#/components/responses/ForbiddenV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/api/transcribe": {
      "options": {
        "operationId": "corsAppTranscribe",
        "summary": "CORS preflight for app transcription",
        "tags": ["App Transcription"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "post": {
        "operationId": "appTranscribe",
        "summary": "Transcribe a YouTube video for the app or extension",
        "description": "Accepts anonymous free-trial requests or authenticated Supabase cookie/JWT requests. Authenticated new transcriptions debit credits; cached hits are free.",
        "tags": ["App Transcription"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }, {}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AppTranscribeRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transcript response.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppTranscribeResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsString" },
          "403": { "$ref": "#/components/responses/FreeTrialLimit" },
          "429": { "$ref": "#/components/responses/RateLimitedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/summarize": {
      "options": {
        "operationId": "corsAppSummarize",
        "summary": "CORS preflight for app summarization",
        "tags": ["App Summarization"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "post": {
        "operationId": "appSummarize",
        "summary": "Stream an app summary",
        "description": "Streams summary text. Anonymous requests consume free trial usage; authenticated requests cost 1 credit and can save the summary to matching history.",
        "tags": ["App Summarization"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }, {}],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppSummarizeRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Text stream.",
            "content": { "text/plain": { "schema": { "type": "string" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsString" },
          "403": { "$ref": "#/components/responses/FreeTrialLimit" },
          "429": { "$ref": "#/components/responses/RateLimitedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/summarize/config": {
      "options": {
        "operationId": "corsSummarizeConfig",
        "summary": "CORS preflight for summary config",
        "tags": ["App Summarization"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "getSummarizeConfig",
        "summary": "Get public summary preset config",
        "tags": ["App Summarization"],
        "security": [],
        "responses": {
          "200": {
            "description": "Preset list and default prompt.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SummaryConfigResponse" } } }
          }
        }
      }
    },
    "/api/chat": {
      "options": {
        "operationId": "corsAppChat",
        "summary": "CORS preflight for app chat",
        "tags": ["App Summarization"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "post": {
        "operationId": "appChat",
        "summary": "Chat about a summarized transcript",
        "description": "Requires Supabase auth. Charges 1 credit for every two follow-up user messages per in-memory video/user session.",
        "tags": ["App Summarization"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatRequest" } } }
        },
        "responses": {
          "200": { "description": "Text stream.", "content": { "text/plain": { "schema": { "type": "string" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsString" },
          "429": { "$ref": "#/components/responses/RateLimitedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/transcribe-audio": {
      "post": {
        "operationId": "authorizeAudioUpload",
        "summary": "Authorize local audio upload",
        "description": "Deducts estimated credits, creates an audio upload ID, and returns a short-lived upload token for the yt-dlp service `/transcribe` endpoint.",
        "tags": ["Audio Upload"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AudioAuthorizeRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Upload authorization.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AudioAuthorizeResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsString" }
        }
      },
      "put": {
        "operationId": "finalizeAudioUpload",
        "summary": "Save local audio transcription result",
        "description": "Validates the upload token and transcription result token, reconciles estimated vs actual credits, and saves the transcript to history.",
        "tags": ["Audio Upload"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AudioFinalizeRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Finalization result.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AudioFinalizeResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "402": { "$ref": "#/components/responses/InsufficientCreditsString" },
          "409": { "$ref": "#/components/responses/ConflictString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      },
      "delete": {
        "operationId": "refundFailedAudioUpload",
        "summary": "Refund failed local audio upload",
        "description": "Refunds outstanding credits for an audio upload that failed before it was saved.",
        "tags": ["Audio Upload"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AudioRefundRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Refund result.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AudioRefundResponse" } } }
          },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "409": { "$ref": "#/components/responses/ConflictString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/credits": {
      "get": {
        "operationId": "getAppCredits",
        "summary": "Get app credit balance and tier",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Balance and tier.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppCreditsResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/credits/balance": {
      "options": {
        "operationId": "corsCreditsBalance",
        "summary": "CORS preflight for extension credit balance",
        "tags": ["Account"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "getExtensionCreditBalance",
        "summary": "Get extension credit balance and profile",
        "tags": ["Account"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Balance and profile.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExtensionBalanceResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/credits/transactions": {
      "get": {
        "operationId": "listCreditTransactions",
        "summary": "List credit transactions",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/Limit100" },
          { "$ref": "#/components/parameters/Offset" },
          { "$ref": "#/components/parameters/FromDate" },
          { "$ref": "#/components/parameters/ToDate" }
        ],
        "responses": {
          "200": { "description": "Credit transactions.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreditTransactionsResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "List API keys",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "API keys.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiKeyListResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "summary": "Create API key",
        "description": "Returns `rawKey` only once. Maximum active keys enforced by server.",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": false,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateApiKeyRequest" } } }
        },
        "responses": {
          "201": { "description": "Created key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateApiKeyResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/keys/{keyId}": {
      "delete": {
        "operationId": "deleteOrRevokeApiKey",
        "summary": "Revoke or permanently delete an API key",
        "description": "Default behavior revokes the key. Pass `permanent=true` to delete a key that has already been revoked.",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "parameters": [
          { "name": "keyId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
          { "name": "permanent", "in": "query", "schema": { "type": "boolean", "default": false } }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/Success" },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/auth/extension-session": {
      "post": {
        "operationId": "createExtensionSession",
        "summary": "Create independent Supabase session for Chrome extension",
        "description": "Cookie-authenticated route. Mints an independent Supabase session for the extension using a server-side magic-link exchange. Rate-limited to 5 calls per user per 10 minutes.",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Extension session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExtensionSessionResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "429": { "$ref": "#/components/responses/RateLimitedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/auth/confirm": {
      "get": {
        "operationId": "confirmSupabaseOtp",
        "summary": "Confirm Supabase OTP and redirect",
        "tags": ["Account"],
        "security": [],
        "parameters": [
          { "name": "token_hash", "in": "query", "schema": { "type": "string" } },
          { "name": "type", "in": "query", "schema": { "type": "string" }, "description": "Supabase EmailOtpType." },
          { "name": "next", "in": "query", "schema": { "type": "string", "default": "/dashboard" } }
        ],
        "responses": {
          "307": { "description": "Redirects to `next` or `/auth/error`." }
        }
      }
    },
    "/api/transcriptions": {
      "get": {
        "operationId": "listTranscriptions",
        "summary": "List transcription history",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/Limit1000" },
          { "$ref": "#/components/parameters/Offset" },
          { "$ref": "#/components/parameters/FromDate" },
          { "$ref": "#/components/parameters/ToDate" },
          { "name": "channel", "in": "query", "schema": { "type": "string" } },
          { "name": "keyword", "in": "query", "schema": { "type": "string" }, "description": "Case-insensitive match against `video_title`." }
        ],
        "responses": {
          "200": { "description": "Transcription history.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscriptionsResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/transcriptions/channels": {
      "get": {
        "operationId": "listTranscriptionChannels",
        "summary": "List distinct history channel names",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Distinct channel names.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChannelsResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/migrate": {
      "post": {
        "operationId": "migrateLocalTranscripts",
        "summary": "Migrate local transcript history",
        "description": "Imports up to 100 local transcripts into authenticated user history, skipping duplicates by `videoId`.",
        "tags": ["Account"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MigrateRequest" } } }
        },
        "responses": {
          "200": { "description": "Migration result.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MigrateResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/billing/subscriptions": {
      "get": {
        "operationId": "listSubscriptions",
        "summary": "List current user's subscriptions",
        "tags": ["Billing"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Subscriptions.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionsResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/billing/purchases": {
      "get": {
        "operationId": "listPurchases",
        "summary": "List one-time purchases",
        "tags": ["Billing"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Purchases.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PurchasesResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/prices": {
      "get": {
        "operationId": "listPrices",
        "summary": "List active Stripe prices",
        "tags": ["Billing"],
        "security": [],
        "responses": {
          "200": { "description": "Active prices.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PricesResponse" } } } }
        }
      }
    },
    "/api/stripe/checkout": {
      "post": {
        "operationId": "createStripeCheckout",
        "summary": "Create Stripe Checkout session",
        "tags": ["Billing"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckoutRequest" } } }
        },
        "responses": {
          "200": { "description": "Checkout session URL.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/stripe/portal": {
      "post": {
        "operationId": "createStripePortal",
        "summary": "Create Stripe billing portal session",
        "tags": ["Billing"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Portal URL.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "404": { "$ref": "#/components/responses/NotFoundString" }
        }
      }
    },
    "/api/stripe/change-plan": {
      "post": {
        "operationId": "changeStripePlan",
        "summary": "Change active subscription price",
        "tags": ["Billing"],
        "security": [{ "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChangePlanRequest" } } }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/Success" },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/stripe/webhook": {
      "post": {
        "operationId": "handleStripeWebhook",
        "summary": "Stripe webhook receiver",
        "description": "Verifies the `stripe-signature` header and processes Stripe product, price, subscription, checkout, and invoice events.",
        "tags": ["Billing"],
        "security": [{ "StripeSignatureAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "type": "object", "additionalProperties": true } },
            "text/plain": { "schema": { "type": "string" } }
          }
        },
        "responses": {
          "200": { "description": "Webhook accepted.", "content": { "application/json": { "schema": { "type": "object", "properties": { "received": { "type": "boolean" } }, "required": ["received"] } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/stripe/sync": {
      "post": {
        "operationId": "syncStripeProductsAndPrices",
        "summary": "Sync active Stripe products and prices",
        "description": "Internal operational route. The current route handler has no request-level auth guard in code.",
        "tags": ["Billing"],
        "security": [],
        "x-internal": true,
        "responses": {
          "200": { "description": "Sync counts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StripeSyncResponse" } } } }
        }
      }
    },
    "/api/notion/status": {
      "options": { "operationId": "corsNotionStatus", "summary": "CORS preflight for Notion status", "tags": ["Notion"], "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } } },
      "get": {
        "operationId": "getNotionStatus",
        "summary": "Get Notion connection status",
        "tags": ["Notion"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Connection status.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotionStatusResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" }
        }
      }
    },
    "/api/notion/pages": {
      "options": { "operationId": "corsNotionPages", "summary": "CORS preflight for Notion pages", "tags": ["Notion"], "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } } },
      "get": {
        "operationId": "listNotionWritableTargets",
        "summary": "List writable Notion pages and databases",
        "tags": ["Notion"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "description": "Writable targets.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotionPagesResponse" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "409": { "$ref": "#/components/responses/NotionRequired" },
          "502": { "$ref": "#/components/responses/BadGatewayString" }
        }
      }
    },
    "/api/notion/destination": {
      "options": { "operationId": "corsNotionDestination", "summary": "CORS preflight for Notion destination", "tags": ["Notion"], "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } } },
      "post": {
        "operationId": "setNotionDestination",
        "summary": "Set default Notion export destination",
        "tags": ["Notion"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotionDestinationRequest" } } }
        },
        "responses": {
          "200": { "description": "Saved destination.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotionDestinationResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "409": { "$ref": "#/components/responses/NotionRequired" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/notion/export": {
      "options": { "operationId": "corsNotionExport", "summary": "CORS preflight for Notion export", "tags": ["Notion"], "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } } },
      "post": {
        "operationId": "exportToNotion",
        "summary": "Export summary and transcript to Notion",
        "description": "Creates a Notion page under the user's selected destination. Does not charge credits.",
        "tags": ["Notion"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotionExportRequest" } } }
        },
        "responses": {
          "200": { "description": "Notion page URL.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "409": { "$ref": "#/components/responses/NotionRequired" },
          "502": { "$ref": "#/components/responses/BadGatewayString" }
        }
      }
    },
    "/api/notion/disconnect": {
      "options": { "operationId": "corsNotionDisconnect", "summary": "CORS preflight for Notion disconnect", "tags": ["Notion"], "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } } },
      "post": {
        "operationId": "disconnectNotion",
        "summary": "Disconnect Notion",
        "tags": ["Notion"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "responses": {
          "200": { "$ref": "#/components/responses/Ok" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/notion/oauth/start": {
      "get": {
        "operationId": "startNotionOAuth",
        "summary": "Start Notion OAuth",
        "description": "Redirects unauthenticated users to login or authenticated users to Notion authorization.",
        "tags": ["Notion"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }, {}],
        "responses": {
          "307": { "description": "Redirect to login or Notion authorization URL." },
          "500": { "description": "Notion integration is not configured.", "content": { "text/plain": { "schema": { "type": "string" } } } }
        }
      }
    },
    "/api/notion/oauth/callback": {
      "get": {
        "operationId": "handleNotionOAuthCallback",
        "summary": "Handle Notion OAuth callback",
        "tags": ["Notion"],
        "security": [],
        "parameters": [
          { "name": "code", "in": "query", "schema": { "type": "string" } },
          { "name": "state", "in": "query", "schema": { "type": "string" } },
          { "name": "error", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "HTML success page.", "content": { "text/html": { "schema": { "type": "string" } } } },
          "400": { "description": "HTML error page.", "content": { "text/html": { "schema": { "type": "string" } } } },
          "500": { "description": "HTML failure page.", "content": { "text/html": { "schema": { "type": "string" } } } }
        }
      }
    },
    "/api/playlist": {
      "get": {
        "operationId": "resolvePlaylistForDashboard",
        "summary": "Resolve a playlist for the dashboard bulk flow",
        "description": "Session-authenticated, free helper that resolves up to 200 playlist videos. Credits are charged later by `/api/transcribe`.",
        "tags": ["Utility"],
        "security": [{ "SupabaseBearerAuth": [] }, { "SupabaseCookieAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/Playlist" }],
        "responses": {
          "200": { "description": "Playlist videos.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DashboardPlaylistResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "submitContactForm",
        "summary": "Submit contact form",
        "tags": ["Utility"],
        "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactRequest" } } }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/Success" },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/og": {
      "get": {
        "operationId": "generateOpenGraphImage",
        "summary": "Generate OpenGraph image",
        "tags": ["Utility"],
        "security": [],
        "parameters": [
          { "name": "title", "in": "query", "schema": { "type": "string", "default": "EasyTranscriber" } },
          { "name": "tags", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated tag list. First five are rendered." },
          { "name": "type", "in": "query", "schema": { "type": "string", "default": "page" } }
        ],
        "responses": {
          "200": { "description": "Generated PNG image.", "content": { "image/png": { "schema": { "type": "string", "format": "binary" } } } }
        }
      }
    },
    "/api/transcriptions/backfill-channels": {
      "post": {
        "operationId": "backfillTranscriptionChannels",
        "summary": "Backfill missing transcription channel metadata",
        "description": "Internal one-time utility. Fetches up to 50 missing video IDs through the yt-dlp service and updates rows. The current route handler has no request-level auth guard in code.",
        "tags": ["Utility"],
        "security": [],
        "x-internal": true,
        "responses": {
          "200": { "description": "Backfill result.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackfillChannelsResponse" } } } },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/api/mcp": {
      "options": {
        "operationId": "corsRetiredAppMcp",
        "summary": "CORS preflight for retired app MCP route",
        "tags": ["MCP"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "get": {
        "operationId": "retiredAppMcpGet",
        "summary": "Retired app MCP endpoint",
        "description": "Returns 410 and points clients to `https://mcp.easytranscriber.com/mcp`.",
        "tags": ["MCP"],
        "security": [],
        "responses": { "410": { "$ref": "#/components/responses/McpMoved" } }
      },
      "post": {
        "operationId": "retiredAppMcpPost",
        "summary": "Retired app MCP endpoint",
        "description": "Returns 410 and points clients to `https://mcp.easytranscriber.com/mcp`.",
        "tags": ["MCP"],
        "security": [],
        "responses": { "410": { "$ref": "#/components/responses/McpMoved" } }
      },
      "delete": {
        "operationId": "retiredAppMcpDelete",
        "summary": "Retired app MCP endpoint",
        "description": "Returns 410 and points clients to `https://mcp.easytranscriber.com/mcp`.",
        "tags": ["MCP"],
        "security": [],
        "responses": { "410": { "$ref": "#/components/responses/McpMoved" } }
      }
    },
    "/mcp": {
      "servers": [{ "url": "https://mcp.easytranscriber.com", "description": "Standalone MCP service" }],
      "options": {
        "operationId": "corsStandaloneMcp",
        "summary": "CORS preflight for standalone MCP service",
        "tags": ["MCP"],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "post": {
        "operationId": "mcpPost",
        "summary": "MCP streamable HTTP POST",
        "description": "Initial POST requires `Authorization: Bearer et_...` and no `Mcp-Session-Id`. Subsequent POSTs use the returned `Mcp-Session-Id` header and contain MCP JSON-RPC messages.",
        "tags": ["MCP"],
        "security": [{ "ApiKeyAuth": [] }, { "McpSessionIdAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/McpSessionIdOptional" }, { "$ref": "#/components/parameters/McpProtocolVersion" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpMessage" } } }
        },
        "responses": {
          "200": { "description": "MCP JSON-RPC response or streamable response.", "headers": { "Mcp-Session-Id": { "schema": { "type": "string" }, "description": "Returned on successful session initialization." } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpMessage" } } } },
          "401": { "$ref": "#/components/responses/UnauthorizedV1" },
          "400": { "$ref": "#/components/responses/BadRequestV1" },
          "404": { "$ref": "#/components/responses/NotFoundV1" },
          "429": { "$ref": "#/components/responses/RateLimitedV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      },
      "get": {
        "operationId": "mcpGet",
        "summary": "MCP streamable HTTP GET/SSE",
        "tags": ["MCP"],
        "security": [{ "McpSessionIdAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/McpSessionIdRequired" }, { "$ref": "#/components/parameters/McpProtocolVersion" }],
        "responses": {
          "200": { "description": "SSE stream with keepalive and MCP messages.", "content": { "text/event-stream": { "schema": { "type": "string" } } } },
          "404": { "$ref": "#/components/responses/NotFoundV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      },
      "delete": {
        "operationId": "mcpDelete",
        "summary": "Close MCP session",
        "tags": ["MCP"],
        "security": [{ "McpSessionIdAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/McpSessionIdRequired" }, { "$ref": "#/components/parameters/McpProtocolVersion" }],
        "responses": {
          "200": { "$ref": "#/components/responses/Ok" },
          "404": { "$ref": "#/components/responses/NotFoundV1" },
          "500": { "$ref": "#/components/responses/ServerErrorV1" }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "serviceHealth",
        "summary": "Service health check",
        "description": "Health endpoint shared by standalone MCP and yt-dlp services.",
        "tags": ["MCP", "yt-dlp Service"],
        "servers": [
          { "url": "https://mcp.easytranscriber.com", "description": "Standalone MCP service" },
          { "url": "https://easytranscriber-yt-dlp.fly.dev", "description": "yt-dlp service" }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Service health.",
            "content": { "application/json": { "schema": { "oneOf": [{ "$ref": "#/components/schemas/McpHealthResponse" }, { "$ref": "#/components/schemas/YtDlpHealthResponse" }] } } }
          }
        }
      }
    },
    "/info": {
      "get": {
        "operationId": "ytDlpInfo",
        "summary": "Get YouTube video metadata",
        "tags": ["yt-dlp Service"],
        "servers": [{ "url": "https://easytranscriber-yt-dlp.fly.dev", "description": "yt-dlp service" }],
        "security": [{ "ServiceBearerAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/VideoId" }],
        "responses": {
          "200": { "description": "Video metadata.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YtDlpInfoResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" },
          "504": { "$ref": "#/components/responses/TimeoutString" }
        }
      }
    },
    "/audio": {
      "get": {
        "operationId": "ytDlpAudio",
        "summary": "Stream YouTube audio",
        "tags": ["yt-dlp Service"],
        "servers": [{ "url": "https://easytranscriber-yt-dlp.fly.dev", "description": "yt-dlp service" }],
        "security": [{ "ServiceBearerAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/VideoId" }],
        "responses": {
          "200": { "description": "Audio stream.", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" },
          "503": { "$ref": "#/components/responses/ServiceUnavailableString" },
          "504": { "$ref": "#/components/responses/TimeoutString" }
        }
      }
    },
    "/captions": {
      "get": {
        "operationId": "ytDlpCaptions",
        "summary": "Get captions using yt-dlp metadata and proxied caption URL fetch",
        "tags": ["yt-dlp Service"],
        "servers": [{ "url": "https://easytranscriber-yt-dlp.fly.dev", "description": "yt-dlp service" }],
        "security": [{ "ServiceBearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/VideoId" },
          { "name": "lang", "in": "query", "schema": { "type": "string" }, "description": "Optional preferred caption language code." }
        ],
        "responses": {
          "200": { "description": "Caption metadata and transcript, or null transcript when no caption track is available.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YtDlpCaptionsResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" },
          "504": { "$ref": "#/components/responses/TimeoutString" }
        }
      }
    },
    "/innertube": {
      "get": {
        "operationId": "ytDlpInnerTube",
        "summary": "Get captions using proxied YouTube InnerTube",
        "tags": ["yt-dlp Service"],
        "servers": [{ "url": "https://easytranscriber-yt-dlp.fly.dev", "description": "yt-dlp service" }],
        "security": [{ "ServiceBearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/VideoId" },
          { "name": "lang", "in": "query", "schema": { "type": "string" }, "description": "Optional preferred caption language code." }
        ],
        "responses": {
          "200": { "description": "InnerTube metadata and transcript, or null transcript when no caption track is available.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YtDlpCaptionsResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" }
        }
      }
    },
    "/transcribe": {
      "options": {
        "operationId": "corsYtDlpTranscribe",
        "summary": "CORS preflight for local-audio transcription service",
        "tags": ["yt-dlp Service"],
        "servers": [{ "url": "https://easytranscriber-yt-dlp.fly.dev", "description": "yt-dlp service" }],
        "responses": { "204": { "$ref": "#/components/responses/CorsPreflight" } }
      },
      "post": {
        "operationId": "ytDlpTranscribeAudioUpload",
        "summary": "Transcribe uploaded audio with Deepgram",
        "description": "Internal upload target returned by `/api/transcribe-audio`. Authenticates the opaque `X-Upload-Token`, accepts a binary audio body up to 500 MB, and returns transcript plus a signed transcription result token.",
        "tags": ["yt-dlp Service"],
        "servers": [{ "url": "https://easytranscriber-yt-dlp.fly.dev", "description": "yt-dlp service" }],
        "security": [{ "UploadTokenAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": { "schema": { "type": "string", "format": "binary" } }
          }
        },
        "responses": {
          "200": { "description": "Audio transcript.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/YtDlpTranscribeResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequestString" },
          "401": { "$ref": "#/components/responses/UnauthorizedString" },
          "405": { "$ref": "#/components/responses/MethodNotAllowedString" },
          "413": { "$ref": "#/components/responses/PayloadTooLargeString" },
          "422": { "$ref": "#/components/responses/UnprocessableString" },
          "500": { "$ref": "#/components/responses/ServerErrorString" },
          "502": { "$ref": "#/components/responses/BadGatewayString" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "EasyTranscriber API key in the form `Authorization: Bearer et_...`."
      },
      "SupabaseBearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Supabase access token, used by the Chrome extension and some app routes."
      },
      "SupabaseCookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "supabase-auth-token",
        "description": "Supabase SSR auth cookies. Exact cookie names are generated by Supabase and deployment config."
      },
      "StripeSignatureAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "stripe-signature",
        "description": "Stripe webhook signature header."
      },
      "ServiceBearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Internal service bearer key, backed by `YT_DLP_SERVICE_KEY` / service `API_KEY`."
      },
      "UploadTokenAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Upload-Token",
        "description": "Opaque signed upload token returned from `/api/transcribe-audio`."
      },
      "McpSessionIdAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Mcp-Session-Id",
        "description": "MCP streamable HTTP session ID returned by initial POST."
      }
    },
    "parameters": {
      "Channel": {
        "name": "channel",
        "in": "query",
        "required": true,
        "description": "@handle, channel URL, or UC... channel ID.",
        "schema": { "type": "string" },
        "example": "@MrBeast"
      },
      "Playlist": {
        "name": "playlist",
        "in": "query",
        "required": true,
        "description": "YouTube playlist ID or playlist URL.",
        "schema": { "type": "string" },
        "example": "https://www.youtube.com/playlist?list=PL..."
      },
      "SearchQuery": {
        "name": "q",
        "in": "query",
        "required": true,
        "description": "Search query.",
        "schema": { "type": "string" }
      },
      "Video": {
        "name": "video",
        "in": "query",
        "required": true,
        "description": "YouTube URL or video ID.",
        "schema": { "type": "string" }
      },
      "VideoId": {
        "name": "videoId",
        "in": "query",
        "required": true,
        "description": "11-character YouTube video ID.",
        "schema": { "type": "string", "pattern": "^[\\w-]{11}$" }
      },
      "PageToken": {
        "name": "page_token",
        "in": "query",
        "required": false,
        "description": "Pagination token from the previous response.",
        "schema": { "type": "string" }
      },
      "Limit100": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "default": 50, "maximum": 100, "minimum": 1 }
      },
      "Limit1000": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "default": 1000, "maximum": 1000, "minimum": 1 }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "schema": { "type": "integer", "default": 0, "minimum": 0 }
      },
      "FromDate": {
        "name": "from",
        "in": "query",
        "schema": { "type": "string", "format": "date-time" }
      },
      "ToDate": {
        "name": "to",
        "in": "query",
        "schema": { "type": "string", "format": "date-time" }
      },
      "McpSessionIdOptional": {
        "name": "Mcp-Session-Id",
        "in": "header",
        "required": false,
        "schema": { "type": "string" }
      },
      "McpSessionIdRequired": {
        "name": "Mcp-Session-Id",
        "in": "header",
        "required": true,
        "schema": { "type": "string" }
      },
      "McpProtocolVersion": {
        "name": "Mcp-Protocol-Version",
        "in": "header",
        "required": false,
        "schema": { "type": "string" },
        "example": "2025-03-26"
      }
    },
    "responses": {
      "CorsPreflight": { "description": "No content. CORS headers are returned." },
      "Success": {
        "description": "Success.",
        "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } }, "required": ["success"] } } }
      },
      "Ok": {
        "description": "OK.",
        "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } }, "required": ["ok"] } } }
      },
      "BadRequestString": { "description": "Bad request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "UnauthorizedString": { "description": "Unauthorized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "ForbiddenString": { "description": "Forbidden.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "NotFoundString": { "description": "Not found.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "ConflictString": { "description": "Conflict.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "RateLimitedString": { "description": "Rate limited.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "InsufficientCreditsString": { "description": "Insufficient credits.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequiresCreditsErrorResponse" } } } },
      "FreeTrialLimit": { "description": "Free trial limit reached.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequiresAuthErrorResponse" } } } },
      "ServerErrorString": { "description": "Server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "BadGatewayString": { "description": "Bad gateway.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "ServiceUnavailableString": { "description": "Service unavailable.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "TimeoutString": { "description": "Timeout.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "MethodNotAllowedString": { "description": "Method not allowed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "PayloadTooLargeString": { "description": "Payload too large.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "UnprocessableString": { "description": "Unprocessable entity.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StringErrorResponse" } } } },
      "BadRequestV1": { "description": "Bad request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/V1ErrorResponse" } } } },
      "UnauthorizedV1": { "description": "Unauthorized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/V1ErrorResponse" } } } },
      "ForbiddenV1": { "description": "Forbidden.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/V1ErrorResponse" } } } },
      "NotFoundV1": { "description": "Not found.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/V1ErrorResponse" } } } },
      "InsufficientCreditsV1": { "description": "Insufficient credits.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/V1ErrorResponse" } } } },
      "RateLimitedV1": { "description": "Rate limited.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/V1ErrorResponse" } } } },
      "ServerErrorV1": { "description": "Server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/V1ErrorResponse" } } } },
      "NotionRequired": { "description": "Notion connection required.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotionRequiredErrorResponse" } } } },
      "McpMoved": {
        "description": "MCP route moved.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/McpMovedResponse" } } }
      }
    },
    "schemas": {
      "StringErrorResponse": {
        "type": "object",
        "properties": { "error": { "type": "string" } },
        "required": ["error"]
      },
      "RequiresAuthErrorResponse": {
        "type": "object",
        "properties": { "error": { "type": "string" }, "requiresAuth": { "type": "boolean" } },
        "required": ["error"]
      },
      "RequiresCreditsErrorResponse": {
        "type": "object",
        "properties": { "error": { "type": "string" }, "requiresCredits": { "type": "boolean" }, "creditsShortfall": { "type": "integer" } },
        "required": ["error"]
      },
      "NotionRequiredErrorResponse": {
        "type": "object",
        "properties": { "error": { "type": "string" }, "requiresNotion": { "type": "boolean" }, "requiresDestination": { "type": "boolean" } },
        "required": ["error"]
      },
      "V1ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": { "code": { "type": "string" }, "message": { "type": "string" } },
            "required": ["code", "message"]
          }
        },
        "required": ["error"]
      },
      "PublicTranscribeRequest": {
        "type": "object",
        "required": ["url"],
        "properties": { "url": { "type": "string", "format": "uri", "description": "YouTube video URL." } }
      },
      "PublicTranscribeResponse": {
        "type": "object",
        "properties": {
          "video_id": { "type": "string" },
          "video_title": { "type": "string", "nullable": true },
          "transcript": { "type": "string" },
          "method": { "$ref": "#/components/schemas/TranscriptionMethod" },
          "cached": { "type": "boolean" },
          "credits_used": { "type": "integer" }
        },
        "required": ["video_id", "transcript", "method", "cached", "credits_used"]
      },
      "AppTranscribeRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": { "type": "string" },
          "force": { "type": "boolean", "default": false, "description": "When true, deletes the matching cached row before transcribing." },
          "lang": { "type": "string", "description": "Optional BCP-47-ish caption language preference." }
        }
      },
      "AppTranscribeResponse": {
        "type": "object",
        "properties": {
          "transcript": { "type": "string" },
          "segments": { "type": "array", "nullable": true, "items": { "$ref": "#/components/schemas/TranscriptSegment" } },
          "method": { "$ref": "#/components/schemas/TranscriptionMethod" },
          "videoTitle": { "type": "string", "nullable": true },
          "videoId": { "type": "string" },
          "durationSeconds": { "type": "integer", "nullable": true },
          "cached": { "type": "boolean" },
          "availableLanguages": { "type": "array", "nullable": true, "items": { "$ref": "#/components/schemas/CaptionLanguage" } },
          "resolvedLang": { "type": "string", "nullable": true },
          "summary": { "type": "string" },
          "warning": { "type": "string" }
        }
      },
      "TranscriptionMethod": { "type": "string", "enum": ["yt-dlp", "innertube", "deepgram", "deepgram-diarized"] },
      "TranscriptSegment": {
        "type": "object",
        "properties": { "start": { "type": "number" }, "dur": { "type": "number" }, "text": { "type": "string" } },
        "required": ["start", "text"]
      },
      "CaptionLanguage": {
        "type": "object",
        "properties": { "code": { "type": "string" }, "name": { "type": "string" }, "auto": { "type": "boolean" } },
        "required": ["code", "name", "auto"]
      },
      "SummarizeRequest": {
        "type": "object",
        "required": ["transcript"],
        "properties": {
          "transcript": { "type": "string" },
          "video_id": { "type": "string" },
          "language": { "$ref": "#/components/schemas/SummaryLanguage" }
        }
      },
      "PublicSummarizeResponse": {
        "type": "object",
        "properties": { "summary": { "type": "string" }, "video_id": { "type": "string", "nullable": true }, "credits_used": { "type": "integer" } },
        "required": ["summary", "video_id", "credits_used"]
      },
      "AppSummarizeRequest": {
        "type": "object",
        "required": ["prompt"],
        "properties": {
          "prompt": { "type": "string", "description": "Transcript text." },
          "videoId": { "type": "string" },
          "language": { "$ref": "#/components/schemas/SummaryLanguage" },
          "summaryPreset": { "type": "string", "enum": ["default", "concise", "detailed", "executive", "study"] },
          "customPrompt": { "type": "string", "maxLength": 4000 }
        }
      },
      "SummaryLanguage": {
        "type": "string",
        "enum": ["Auto", "English", "Spanish", "French", "German", "Portuguese", "Japanese", "Chinese", "Korean", "Hindi", "Arabic", "Russian", "Italian", "Dutch", "Turkish", "Polish"]
      },
      "SummaryConfigResponse": {
        "type": "object",
        "properties": {
          "presets": { "type": "array", "items": { "$ref": "#/components/schemas/SummaryPreset" } },
          "defaultPrompt": { "type": "string" }
        },
        "required": ["presets", "defaultPrompt"]
      },
      "SummaryPreset": {
        "type": "object",
        "properties": { "key": { "type": "string" }, "label": { "type": "string" }, "description": { "type": "string" } },
        "required": ["key", "label", "description"]
      },
      "ChatRequest": {
        "type": "object",
        "required": ["messages", "transcript", "summary"],
        "properties": {
          "messages": { "type": "array", "items": { "$ref": "#/components/schemas/ChatMessage" }, "minItems": 1 },
          "transcript": { "type": "string" },
          "summary": { "type": "string" },
          "videoId": { "type": "string" },
          "language": { "$ref": "#/components/schemas/SummaryLanguage" }
        }
      },
      "ChatMessage": {
        "type": "object",
        "properties": { "role": { "type": "string", "enum": ["user", "assistant"] }, "content": { "type": "string" } },
        "required": ["role", "content"]
      },
      "CreditBalanceResponse": {
        "type": "object",
        "properties": { "balance": { "type": "integer" } },
        "required": ["balance"]
      },
      "AppCreditsResponse": {
        "type": "object",
        "properties": { "balance": { "type": "integer" }, "tier": { "type": "string" }, "rpm": { "type": "integer" } },
        "required": ["balance", "tier", "rpm"]
      },
      "ExtensionBalanceResponse": {
        "type": "object",
        "properties": {
          "balance": { "type": "integer" },
          "email": { "type": "string", "nullable": true },
          "name": { "type": "string", "nullable": true },
          "plan": { "type": "string" }
        },
        "required": ["balance", "email", "name", "plan"]
      },
      "CreditTransaction": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "user_id": { "type": "string", "format": "uuid" },
          "amount": { "type": "integer" },
          "type": { "type": "string" },
          "balance_after": { "type": "integer" },
          "description": { "type": "string", "nullable": true },
          "reference_id": { "type": "string", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        },
        "additionalProperties": true
      },
      "CreditTransactionsResponse": {
        "type": "object",
        "properties": {
          "transactions": { "type": "array", "items": { "$ref": "#/components/schemas/CreditTransaction" } },
          "total": { "type": "integer" }
        },
        "required": ["transactions", "total"]
      },
      "ApiKeyInfo": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "keyPrefix": { "type": "string" },
          "lastUsedAt": { "type": "string", "format": "date-time", "nullable": true },
          "expiresAt": { "type": "string", "format": "date-time", "nullable": true },
          "revokedAt": { "type": "string", "format": "date-time", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time" }
        },
        "required": ["id", "name", "keyPrefix", "createdAt"]
      },
      "ApiKeyListResponse": {
        "type": "object",
        "properties": { "keys": { "type": "array", "items": { "$ref": "#/components/schemas/ApiKeyInfo" } } },
        "required": ["keys"]
      },
      "CreateApiKeyRequest": {
        "type": "object",
        "properties": { "name": { "type": "string", "default": "Default" } }
      },
      "CreateApiKeyResponse": {
        "type": "object",
        "properties": { "rawKey": { "type": "string" }, "key": { "$ref": "#/components/schemas/ApiKeyInfo" } },
        "required": ["rawKey", "key"]
      },
      "ExtensionSessionResponse": {
        "type": "object",
        "properties": {
          "access_token": { "type": "string" },
          "refresh_token": { "type": "string" },
          "expires_at": { "type": "integer" },
          "user": { "type": "object", "properties": { "id": { "type": "string" }, "email": { "type": "string" } }, "required": ["id", "email"] }
        },
        "required": ["access_token", "refresh_token", "expires_at", "user"]
      },
      "TranscriptionRecord": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "user_id": { "type": "string", "format": "uuid", "nullable": true },
          "youtube_url": { "type": "string", "nullable": true },
          "video_id": { "type": "string" },
          "video_title": { "type": "string", "nullable": true },
          "channel_name": { "type": "string", "nullable": true },
          "channel_id": { "type": "string", "nullable": true },
          "channel_handle": { "type": "string", "nullable": true },
          "transcript": { "type": "string", "nullable": true },
          "segments": { "type": "array", "nullable": true, "items": { "$ref": "#/components/schemas/TranscriptSegment" } },
          "summary": { "type": "string", "nullable": true },
          "formatted_transcript": { "type": "string", "nullable": true },
          "method": { "type": "string", "nullable": true },
          "lang": { "type": "string", "nullable": true },
          "available_languages": { "type": "array", "nullable": true, "items": { "$ref": "#/components/schemas/CaptionLanguage" } },
          "credits_used": { "type": "integer" },
          "ip_address": { "type": "string", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        },
        "additionalProperties": true
      },
      "TranscriptionsResponse": {
        "type": "object",
        "properties": {
          "transcriptions": { "type": "array", "items": { "$ref": "#/components/schemas/TranscriptionRecord" } },
          "total": { "type": "integer" }
        },
        "required": ["transcriptions", "total"]
      },
      "ChannelsResponse": {
        "type": "object",
        "properties": { "channels": { "type": "array", "items": { "type": "string" } } },
        "required": ["channels"]
      },
      "MigrateRequest": {
        "type": "object",
        "required": ["transcripts"],
        "properties": { "transcripts": { "type": "array", "maxItems": 100, "items": { "$ref": "#/components/schemas/LocalTranscript" } } }
      },
      "LocalTranscript": {
        "type": "object",
        "properties": {
          "videoId": { "type": "string" },
          "videoUrl": { "type": "string" },
          "videoTitle": { "type": "string", "nullable": true },
          "transcript": { "type": "string" },
          "summary": { "type": "string" },
          "method": { "type": "string" }
        },
        "required": ["videoId", "videoUrl", "transcript", "method"]
      },
      "MigrateResponse": {
        "type": "object",
        "properties": { "migrated": { "type": "integer" }, "total": { "type": "integer" } },
        "required": ["migrated", "total"]
      },
      "YouTubeSearchResult": {
        "type": "object",
        "properties": {
          "video_id": { "type": "string", "nullable": true },
          "channel_id": { "type": "string" },
          "title": { "type": "string" },
          "channel": { "type": "string" },
          "published_at": { "type": "string", "format": "date-time" },
          "thumbnail": { "type": "string", "nullable": true }
        },
        "required": ["video_id", "channel_id", "title", "channel", "published_at", "thumbnail"]
      },
      "YouTubeSearchResponse": {
        "type": "object",
        "properties": {
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/YouTubeSearchResult" } },
          "next_page_token": { "type": "string", "nullable": true },
          "credits_used": { "type": "integer" }
        },
        "required": ["results", "next_page_token", "credits_used"]
      },
      "ChannelResolveResponse": {
        "type": "object",
        "properties": {
          "channel_id": { "type": "string" },
          "name": { "type": "string" },
          "handle": { "type": "string", "nullable": true },
          "subscribers": { "type": "integer", "nullable": true },
          "thumbnail": { "type": "string", "nullable": true }
        },
        "required": ["channel_id", "name"]
      },
      "ChannelSearchResponse": {
        "type": "object",
        "properties": {
          "channel_id": { "type": "string" },
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/YouTubeSearchResult" } },
          "next_page_token": { "type": "string", "nullable": true },
          "credits_used": { "type": "integer" }
        },
        "required": ["channel_id", "results", "next_page_token", "credits_used"]
      },
      "VideoListItem": {
        "type": "object",
        "properties": {
          "video_id": { "type": "string" },
          "title": { "type": "string" },
          "published_at": { "type": "string", "format": "date-time" },
          "thumbnail": { "type": "string", "nullable": true }
        },
        "required": ["video_id", "title", "published_at"]
      },
      "ChannelLatestResponse": {
        "type": "object",
        "properties": {
          "channel_id": { "type": "string" },
          "videos": { "type": "array", "items": { "$ref": "#/components/schemas/VideoListItem" } }
        },
        "required": ["channel_id", "videos"]
      },
      "ChannelVideosResponse": {
        "type": "object",
        "properties": {
          "channel_id": { "type": "string" },
          "videos": { "type": "array", "items": { "$ref": "#/components/schemas/VideoListItem" } },
          "next_page_token": { "type": "string", "nullable": true },
          "total_results": { "type": "integer", "nullable": true },
          "credits_used": { "type": "integer" }
        },
        "required": ["channel_id", "videos", "next_page_token", "total_results", "credits_used"]
      },
      "PlaylistVideosResponse": {
        "type": "object",
        "properties": {
          "playlist_id": { "type": "string" },
          "videos": { "type": "array", "items": { "$ref": "#/components/schemas/VideoListItem" } },
          "next_page_token": { "type": "string", "nullable": true },
          "total_results": { "type": "integer", "nullable": true },
          "credits_used": { "type": "integer" }
        },
        "required": ["playlist_id", "videos", "next_page_token", "total_results", "credits_used"]
      },
      "Comment": {
        "type": "object",
        "properties": {
          "author": { "type": "string" },
          "text": { "type": "string" },
          "likes": { "type": "integer" },
          "published_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        },
        "required": ["author", "text", "likes", "published_at", "updated_at"]
      },
      "CommentThread": {
        "allOf": [
          { "$ref": "#/components/schemas/Comment" },
          {
            "type": "object",
            "properties": {
              "reply_count": { "type": "integer" },
              "replies": { "type": "array", "items": { "$ref": "#/components/schemas/Comment" } }
            },
            "required": ["reply_count", "replies"]
          }
        ]
      },
      "CommentsResponse": {
        "type": "object",
        "properties": {
          "video_id": { "type": "string" },
          "comments": { "type": "array", "items": { "$ref": "#/components/schemas/CommentThread" } },
          "next_page_token": { "type": "string", "nullable": true },
          "total_results": { "type": "integer", "nullable": true },
          "credits_used": { "type": "integer" }
        },
        "required": ["video_id", "comments", "next_page_token", "total_results", "credits_used"]
      },
      "AudioAuthorizeRequest": {
        "type": "object",
        "required": ["fileSize"],
        "properties": {
          "fileName": { "type": "string" },
          "fileSize": { "type": "integer", "description": "File size in bytes." },
          "diarize": { "type": "boolean", "default": false },
          "language": { "$ref": "#/components/schemas/TranscriptionLanguageCode" }
        }
      },
      "AudioAuthorizeResponse": {
        "type": "object",
        "properties": {
          "audioId": { "type": "string" },
          "uploadToken": { "type": "string" },
          "creditsCharged": { "type": "integer" },
          "uploadUrl": { "type": "string", "format": "uri" },
          "diarize": { "type": "boolean" }
        },
        "required": ["audioId", "uploadToken", "creditsCharged", "uploadUrl", "diarize"]
      },
      "AudioFinalizeRequest": {
        "type": "object",
        "required": ["audioId", "uploadToken", "transcriptionToken", "transcript"],
        "properties": {
          "audioId": { "type": "string" },
          "uploadToken": { "type": "string" },
          "transcriptionToken": { "type": "string" },
          "transcript": { "type": "string" },
          "fileName": { "type": "string" }
        }
      },
      "AudioFinalizeResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "videoId": { "type": "string" },
          "creditsUsed": { "type": "integer" },
          "creditsRefunded": { "type": "integer" },
          "additionalCreditsCharged": { "type": "integer" },
          "alreadySaved": { "type": "boolean" }
        },
        "required": ["success", "videoId", "creditsUsed", "creditsRefunded", "additionalCreditsCharged"]
      },
      "AudioRefundRequest": {
        "type": "object",
        "required": ["uploadToken"],
        "properties": { "uploadToken": { "type": "string" } }
      },
      "AudioRefundResponse": {
        "type": "object",
        "properties": { "success": { "type": "boolean" }, "refunded": { "type": "integer" }, "alreadyRefunded": { "type": "boolean" } },
        "required": ["success", "refunded"]
      },
      "TranscriptionLanguageCode": {
        "type": "string",
        "enum": ["en", "es", "hi", "zh", "ar", "fr", "ru", "pt", "ur", "de", "ja", "id", "vi", "ko", "tr", "it", "pl", "nl", "fa", "bg", "cs", "da", "fi", "el", "he", "hu", "ms", "no", "ro", "sk", "sv", "tl", "th", "uk"]
      },
      "Price": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "unit_amount": { "type": "integer", "nullable": true },
          "currency": { "type": "string" },
          "type": { "type": "string" },
          "interval": { "type": "string", "nullable": true },
          "product_id": { "type": "string" },
          "active": { "type": "boolean" },
          "products": { "type": "object", "nullable": true, "additionalProperties": true }
        },
        "additionalProperties": true
      },
      "PricesResponse": {
        "type": "object",
        "properties": { "prices": { "type": "array", "items": { "$ref": "#/components/schemas/Price" } } },
        "required": ["prices"]
      },
      "SubscriptionSummary": {
        "type": "object",
        "properties": { "id": { "type": "string" }, "status": { "type": "string" }, "priceId": { "type": "string" }, "productName": { "type": "string" } },
        "required": ["id", "status", "priceId", "productName"]
      },
      "SubscriptionsResponse": {
        "type": "object",
        "properties": { "subscriptions": { "type": "array", "items": { "$ref": "#/components/schemas/SubscriptionSummary" } } },
        "required": ["subscriptions"]
      },
      "PurchaseSummary": {
        "type": "object",
        "properties": { "id": { "type": "string" }, "amount": { "type": "integer", "nullable": true }, "currency": { "type": "string", "nullable": true }, "status": { "type": "string" }, "createdAt": { "type": "string", "format": "date-time" }, "productName": { "type": "string" } },
        "required": ["id", "status", "createdAt", "productName"]
      },
      "PurchasesResponse": {
        "type": "object",
        "properties": { "purchases": { "type": "array", "items": { "$ref": "#/components/schemas/PurchaseSummary" } } },
        "required": ["purchases"]
      },
      "CheckoutRequest": {
        "type": "object",
        "required": ["priceId", "mode"],
        "properties": {
          "priceId": { "type": "string" },
          "mode": { "type": "string", "enum": ["subscription", "payment"] }
        }
      },
      "ChangePlanRequest": {
        "type": "object",
        "required": ["newPriceId"],
        "properties": { "newPriceId": { "type": "string" } }
      },
      "UrlResponse": {
        "type": "object",
        "properties": { "url": { "type": "string", "format": "uri" } },
        "required": ["url"]
      },
      "StripeSyncResponse": {
        "type": "object",
        "properties": { "products": { "type": "integer" }, "prices": { "type": "integer" } },
        "required": ["products", "prices"]
      },
      "NotionParent": {
        "type": "object",
        "properties": { "id": { "type": "string" }, "type": { "type": "string", "enum": ["page_id", "database_id"] }, "title": { "type": "string", "nullable": true } },
        "required": ["id", "type"]
      },
      "NotionStatusResponse": {
        "type": "object",
        "properties": {
          "connected": { "type": "boolean" },
          "workspaceName": { "type": "string", "nullable": true },
          "workspaceIcon": { "type": "string", "nullable": true },
          "defaultParent": { "allOf": [{ "$ref": "#/components/schemas/NotionParent" }], "nullable": true },
          "error": { "type": "string" }
        },
        "required": ["connected"]
      },
      "NotionPagesResponse": {
        "type": "object",
        "properties": { "pages": { "type": "array", "items": { "$ref": "#/components/schemas/NotionParent" } } },
        "required": ["pages"]
      },
      "NotionDestinationRequest": {
        "allOf": [{ "$ref": "#/components/schemas/NotionParent" }]
      },
      "NotionDestinationResponse": {
        "type": "object",
        "properties": { "ok": { "type": "boolean" }, "defaultParent": { "$ref": "#/components/schemas/NotionParent" } },
        "required": ["ok", "defaultParent"]
      },
      "NotionExportRequest": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "videoUrl": { "type": "string" },
          "videoId": { "type": "string" },
          "summary": { "type": "string" },
          "transcript": { "type": "string" },
          "segments": { "type": "array", "items": { "$ref": "#/components/schemas/TranscriptSegment" } }
        },
        "required": ["summary"]
      },
      "DashboardPlaylistResponse": {
        "type": "object",
        "properties": {
          "playlist_id": { "type": "string" },
          "videos": { "type": "array", "items": { "$ref": "#/components/schemas/VideoListItem" } },
          "total_results": { "type": "integer", "nullable": true },
          "truncated": { "type": "boolean" }
        },
        "required": ["playlist_id", "videos", "total_results", "truncated"]
      },
      "ContactRequest": {
        "type": "object",
        "required": ["name", "email", "message", "captchaToken"],
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "subject": { "type": "string" },
          "message": { "type": "string" },
          "type": { "type": "string", "default": "general" },
          "captchaToken": { "type": "string" },
          "website": { "type": "string", "description": "Honeypot field. Bots that fill this receive a success response without email delivery." }
        }
      },
      "BackfillChannelsResponse": {
        "type": "object",
        "properties": {
          "message": { "type": "string" },
          "updated": { "type": "integer" },
          "total": { "type": "integer" },
          "errors": { "type": "array", "items": { "type": "string" } }
        },
        "required": ["message", "updated"]
      },
      "McpMovedResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "enum": ["moved"] },
              "message": { "type": "string" },
              "new_url": { "type": "string", "format": "uri" }
            },
            "required": ["code", "message", "new_url"]
          }
        },
        "required": ["error"]
      },
      "McpMessage": {
        "type": "object",
        "description": "MCP JSON-RPC message. Tool schemas are advertised by the MCP protocol after initialization.",
        "additionalProperties": true
      },
      "McpHealthResponse": {
        "type": "object",
        "properties": { "status": { "type": "string" }, "activeSessions": { "type": "integer" } },
        "required": ["status", "activeSessions"]
      },
      "YtDlpHealthResponse": {
        "type": "object",
        "properties": { "status": { "type": "string" }, "activeDownloads": { "type": "integer" } },
        "required": ["status", "activeDownloads"]
      },
      "YtDlpInfoResponse": {
        "type": "object",
        "properties": { "title": { "type": "string", "nullable": true }, "durationSeconds": { "type": "integer", "nullable": true } },
        "required": ["title", "durationSeconds"]
      },
      "YtDlpCaptionsResponse": {
        "type": "object",
        "properties": {
          "transcript": { "type": "string", "nullable": true },
          "segments": { "type": "array", "items": { "$ref": "#/components/schemas/TranscriptSegment" } },
          "title": { "type": "string", "nullable": true },
          "durationSeconds": { "type": "integer", "nullable": true },
          "channelName": { "type": "string", "nullable": true },
          "channelId": { "type": "string", "nullable": true },
          "channelHandle": { "type": "string", "nullable": true },
          "availableLanguages": { "type": "array", "nullable": true, "items": { "$ref": "#/components/schemas/CaptionLanguage" } },
          "resolvedLang": { "type": "string", "nullable": true }
        },
        "required": ["transcript", "title", "durationSeconds"]
      },
      "YtDlpTranscribeResponse": {
        "type": "object",
        "properties": {
          "transcript": { "type": "string" },
          "durationSeconds": { "type": "integer" },
          "transcriptionToken": { "type": "string" }
        },
        "required": ["transcript", "durationSeconds", "transcriptionToken"]
      }
    }
  }
}
