
openapi: 3.0.3
info:
  title: Toucora API
  version: 1.0.0
  description: "Extract structured YouTube transcripts, batches, playlists, channels, async jobs and AI results.\n\nAuthenticate with an API key via `Authorization: Bearer YOUR_API_KEY` (or `X-API-Key`). Account, key and webhook management use a session cookie created by `POST /api/auth/login`. Job webhooks deliver `job.completed` and `job.failed` events and are signed with `X-Toucora-Signature`."
  contact:
    name: Toucora
servers:
  - url: "https://toucora.com"
    description: This deployment
tags:
  - name: Transcripts
  - name: AI
  - name: Jobs
  - name: Playlists
  - name: Channels
  - name: Usage
  - name: Account
  - name: Auth
  - name: Meta
  - name: MCP
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "API key: Authorization: Bearer YOUR_API_KEY"
    sessionCookie:
      type: apiKey
      in: cookie
      name: session
      description: Session cookie set by /api/auth/login.
  schemas:
    Transcript:
      type: object
      properties:
        video_id:
          type: string
        title:
          type: string
          nullable: true
        channel:
          type: string
          nullable: true
        channel_id:
          type: string
          nullable: true
        url:
          type: string
        duration:
          type: integer
          nullable: true
        language:
          type: string
          nullable: true
        requested_language:
          type: string
          nullable: true
        source:
          type: string
          enum:
            - manual
            - youtube_auto
            - translated
            - asr
        segment_count:
          type: integer
        segments:
          type: array
          items:
            $ref: "#/components/schemas/Segment"
    Segment:
      type: object
      properties:
        text:
          type: string
        start:
          type: number
          description: Start time in seconds
        duration:
          type: number
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
            request_id:
              type: string
            details:
              type: object
              nullable: true
              additionalProperties: true
    Job:
      type: object
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - batch
            - playlist
            - channel
        status:
          type: string
          enum:
            - queued
            - processing
            - completed
            - completed_with_errors
            - failed
            - cancelled
        total_items:
          type: integer
        completed_items:
          type: integer
        failed_items:
          type: integer
        webhook_url:
          type: string
          nullable: true
        created_at:
          type: string
        updated_at:
          type: string
        items:
          type: array
          items:
            $ref: "#/components/schemas/JobItem"
    JobItem:
      type: object
      properties:
        id:
          type: string
        job_id:
          type: string
        video_id:
          type: string
          nullable: true
        url:
          type: string
          nullable: true
        status:
          type: string
          enum:
            - queued
            - processing
            - complete
            - failed
            - cancelled
        language:
          type: string
          nullable: true
        error_code:
          type: string
          nullable: true
        error_message:
          type: string
          nullable: true
        attempts:
          type: integer
    ApiKey:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        key_prefix:
          type: string
        created_at:
          type: string
        last_used_at:
          type: string
          nullable: true
        revoked_at:
          type: string
          nullable: true
        fullKey:
          type: string
          description: Only returned once, at creation time.
    Plan:
      type: object
      properties:
        key:
          type: string
        label:
          type: string
        monthly_price_cents:
          type: integer
        price_label:
          type: string
        credits:
          type: integer
        rate_limit_per_min:
          type: integer
        features:
          type: array
          items:
            type: string
    UsageSummary:
      type: object
      properties:
        requests:
          type: integer
        successful:
          type: integer
        failed:
          type: integer
        credits_used:
          type: integer
        by_kind:
          type: array
          items:
            type: object
            properties:
              kind:
                type: string
              count:
                type: integer
              credits:
                type: integer
        daily:
          type: array
          items:
            type: object
            properties:
              day:
                type: string
              count:
                type: integer
    Webhook:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
        events:
          type: array
          items:
            type: string
            enum:
              - job.completed
              - job.failed
        active:
          type: boolean
        created_at:
          type: string
security:
  - bearerAuth: []
paths:
  /v1/transcripts/{videoId}:
    get:
      tags:
        - Transcripts
      summary: Get a single transcript
      parameters:
        - name: videoId
          in: path
          required: true
          schema:
            type: string
        - name: lang
          in: query
          schema:
            type: string
          description: Language code, e.g. en, es, de
        - name: format
          in: query
          schema:
            type: string
            enum:
              - json
              - txt
              - srt
              - vtt
              - markdown
              - md
        - name: timestamps
          in: query
          schema:
            type: boolean
            default: false
          description: Prefix each txt line with its timestamp. txt drops timestamps by default.
        - name: clean
          in: query
          schema:
            type: boolean
            default: false
          description: "Strip musical note symbols and non-speech cues like [Music], (laughs)."
        - name: body
          in: query
          schema:
            type: boolean
          description: Set to true to force the formatted body.
      responses:
        200:
          description: Transcript as JSON (default) or the requested text format.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transcript"
            text/plain:
              schema:
                type: string
            application/x-subrip:
              schema:
                type: string
            text/vtt:
              schema:
                type: string
            text/markdown:
              schema:
                type: string
          headers:
            X-Transcript-Language:
              schema:
                type: string
            X-Transcript-Source:
              schema:
                type: string
            X-Credits-Charged:
              schema:
                type: integer
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        404:
          description: Not found or no transcript
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/transcripts/{videoId}/raw:
    get:
      tags:
        - Transcripts
      summary: Get the raw internal transcript object
      parameters:
        - name: videoId
          in: path
          required: true
          schema:
            type: string
        - name: lang
          in: query
          schema:
            type: string
          description: Language code, e.g. en, es, de
      responses:
        200:
          description: Raw transcript
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        404:
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/transcripts/{videoId}/languages:
    get:
      tags:
        - Transcripts
      summary: List available caption languages
      parameters:
        - name: videoId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: Available languages
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  available:
                    type: array
                    items:
                      type: string
                  default:
                    type: string
                    nullable: true
                  has_captions:
                    type: boolean
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/transcripts:
    post:
      tags:
        - Transcripts
      summary: Batch transcripts (max 50)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ids:
                  type: array
                  items:
                    type: string
                  maxItems: 50
                lang:
                  type: string
                clean:
                  type: boolean
                  description: Strip musical note symbols and non-speech cues from each transcript.
              required:
                - ids
      responses:
        200:
          description: Per-item results
          content:
            application/json:
              schema:
                type: object
                properties:
                  requested:
                    type: integer
                  succeeded:
                    type: integer
                  failed:
                    type: integer
                  credits_charged:
                    type: integer
                  results:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/ai/status:
    get:
      tags:
        - AI
      summary: Whether AI-backed responses are enabled
      responses:
        200:
          description: AI status
          content:
            application/json:
              schema:
                type: object
                properties:
                  ai_enabled:
                    type: boolean
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/ask:
    post:
      tags:
        - AI
      summary: Ask a question grounded in the transcript
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_id:
                  type: string
                  description: YouTube video id or URL.
                videoId:
                  type: string
                url:
                  type: string
                lang:
                  type: string
                language:
                  type: string
                question:
                  type: string
                output_lang:
                  type: string
                  description: Language to write the AI response in (mirrors the consumer site), e.g. en, es, de.
              required:
                - question
      responses:
        200:
          description: Answer with timestamp citations
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  question:
                    type: string
                  answer:
                    type: string
                  citations:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  grounded:
                    type: boolean
                  model:
                    type: string
                    nullable: true
                  fallback:
                    type: boolean
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/summarize:
    post:
      tags:
        - AI
      summary: Summarize a transcript
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_id:
                  type: string
                  description: YouTube video id or URL.
                videoId:
                  type: string
                url:
                  type: string
                lang:
                  type: string
                language:
                  type: string
                length:
                  type: string
                  enum:
                    - short
                    - medium
                    - detailed
                output_lang:
                  type: string
                  description: Language to write the AI response in (mirrors the consumer site), e.g. en, es, de.
              required: []
      responses:
        200:
          description: Summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  length:
                    type: string
                  summary:
                    type: string
                  model:
                    type: string
                    nullable: true
                  fallback:
                    type: boolean
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/chapters:
    post:
      tags:
        - AI
      summary: Generate timestamped chapters
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_id:
                  type: string
                  description: YouTube video id or URL.
                videoId:
                  type: string
                url:
                  type: string
                lang:
                  type: string
                language:
                  type: string
                output_lang:
                  type: string
                  description: Language to write the AI response in (mirrors the consumer site), e.g. en, es, de.
              required: []
      responses:
        200:
          description: Chapters
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  chapters:
                    type: array
                    items:
                      type: object
                      properties:
                        start:
                          type: number
                        timestamp:
                          type: string
                        title:
                          type: string
                  model:
                    type: string
                    nullable: true
                  fallback:
                    type: boolean
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/search:
    post:
      tags:
        - AI
      summary: Search within a transcript
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_id:
                  type: string
                  description: YouTube video id or URL.
                videoId:
                  type: string
                url:
                  type: string
                lang:
                  type: string
                language:
                  type: string
                query:
                  type: string
                limit:
                  type: integer
              required:
                - query
      responses:
        200:
          description: Timestamped matches
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  query:
                    type: string
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        start:
                          type: number
                        end:
                          type: number
                        timestamp:
                          type: string
                        text:
                          type: string
                        score:
                          type: number
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/quotes:
    post:
      tags:
        - AI
      summary: Extract notable quotes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_id:
                  type: string
                  description: YouTube video id or URL.
                videoId:
                  type: string
                url:
                  type: string
                lang:
                  type: string
                language:
                  type: string
              required: []
      responses:
        200:
          description: Quotes
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  quotes:
                    type: array
                    items:
                      type: object
                      properties:
                        start:
                          type: number
                        timestamp:
                          type: string
                        text:
                          type: string
                  model:
                    type: string
                    nullable: true
                  fallback:
                    type: boolean
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/repurpose:
    post:
      tags:
        - AI
      summary: Repurpose a transcript into another format
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_id:
                  type: string
                  description: YouTube video id or URL.
                videoId:
                  type: string
                url:
                  type: string
                lang:
                  type: string
                language:
                  type: string
                format:
                  type: string
                  enum:
                    - blog
                    - newsletter
                    - linkedin
                    - x_thread
                    - youtube_description
                    - show_notes
                output_lang:
                  type: string
                  description: Language to write the AI response in (mirrors the consumer site), e.g. en, es, de.
              required: []
      responses:
        200:
          description: Repurposed content
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  format:
                    type: string
                  content:
                    type: string
                  model:
                    type: string
                    nullable: true
                  fallback:
                    type: boolean
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/clips:
    post:
      tags:
        - AI
      summary: Find candidate short-form clips
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_id:
                  type: string
                  description: YouTube video id or URL.
                videoId:
                  type: string
                url:
                  type: string
                lang:
                  type: string
                language:
                  type: string
              required: []
      responses:
        200:
          description: Clips
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  clips:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  model:
                    type: string
                    nullable: true
                  fallback:
                    type: boolean
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/translate:
    post:
      tags:
        - AI
      summary: Translate a transcript and preserve segment timings
      description: "Translate a transcript into one of the supported languages. `mode`: \"original\" returns the source text, \"translated\" returns only the translation, and \"bilingual\" returns both aligned to the same timestamps. When no AI provider is configured the response is a clean FEATURE_UNAVAILABLE fallback carrying the original transcript."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_id:
                  type: string
                  description: YouTube video id or URL.
                videoId:
                  type: string
                url:
                  type: string
                lang:
                  type: string
                language:
                  type: string
                target_lang:
                  type: string
                  enum:
                    - en
                    - es
                    - de
                    - fr
                    - it
                    - pt
                    - ro
                    - ja
                    - zh
                    - ko
                mode:
                  type: string
                  enum:
                    - original
                    - translated
                    - bilingual
                  default: translated
              required:
                - video_id
                - target_lang
      responses:
        200:
          description: Timestamped translated segments, or a FEATURE_UNAVAILABLE fallback
          content:
            application/json:
              schema:
                type: object
                properties:
                  video_id:
                    type: string
                  translated:
                    type: boolean
                  mode:
                    type: string
                  target_lang:
                    type: string
                  source_language:
                    type: string
                    nullable: true
                  fallback:
                    type: boolean
                  code:
                    type: string
                  message:
                    type: string
                  segment_count:
                    type: integer
                  segments:
                    type: array
                    items:
                      $ref: "#/components/schemas/Segment"
                  model:
                    type: string
                    nullable: true
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/jobs:
    post:
      tags:
        - Jobs
      summary: Create an asynchronous job
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ids:
                  type: array
                  items:
                    type: string
                video_ids:
                  type: array
                  items:
                    type: string
                urls:
                  type: array
                  items:
                    type: string
                kind:
                  type: string
                  enum:
                    - batch
                    - playlist
                    - channel
                webhook_url:
                  type: string
                  format: uri
              required:
                - ids
      responses:
        202:
          description: Job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                  status:
                    type: string
                  total_items:
                    type: integer
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    get:
      tags:
        - Jobs
      summary: List jobs
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      responses:
        200:
          description: Jobs
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobs:
                    type: array
                    items:
                      $ref: "#/components/schemas/Job"
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/jobs/{jobId}:
    get:
      tags:
        - Jobs
      summary: Get job status
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: Job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Job"
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        404:
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/jobs/{jobId}/results:
    get:
      tags:
        - Jobs
      summary: Get job results
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: Results
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                  status:
                    type: string
                  completed_items:
                    type: integer
                  failed_items:
                    type: integer
                  results:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        404:
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/jobs/{jobId}/cancel:
    post:
      tags:
        - Jobs
      summary: Cancel a queued or processing job
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: Cancelled job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Job"
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        404:
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/jobs/{jobId}/retry:
    post:
      tags:
        - Jobs
      summary: Re-queue failed items in a job
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: Job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Job"
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        404:
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/playlists/{playlistId}:
    get:
      tags:
        - Playlists
      summary: List playlist videos
      parameters:
        - name: playlistId
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
      responses:
        200:
          description: Playlist videos
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/playlists/{playlistId}/transcripts:
    post:
      tags:
        - Playlists
      summary: Create a playlist job
      parameters:
        - name: playlistId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                limit:
                  type: integer
                webhook_url:
                  type: string
                  format: uri
      responses:
        202:
          description: Job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  playlist_id:
                    type: string
                  videos_found:
                    type: integer
                  job_id:
                    type: string
                  status:
                    type: string
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/channels/{channelId}:
    get:
      tags:
        - Channels
      summary: List channel videos
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      responses:
        200:
          description: Channel videos
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/channels/{channelId}/transcripts:
    post:
      tags:
        - Channels
      summary: Create a channel job
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                limit:
                  type: integer
                webhook_url:
                  type: string
                  format: uri
      responses:
        202:
          description: Job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  channel_id:
                    type: string
                  videos_found:
                    type: integer
                  job_id:
                    type: string
                  status:
                    type: string
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/usage:
    get:
      tags:
        - Usage
      summary: Get account usage
      parameters:
        - name: days
          in: query
          schema:
            type: integer
            default: 30
      responses:
        200:
          description: Usage summary
          content:
            application/json:
              schema:
                allOf:
                  - type: object
                    properties:
                      plan:
                        type: string
                      credits_remaining:
                        type: integer
                      rate_limit_per_min:
                        type: integer
                        nullable: true
                      period_days:
                        type: integer
                  - $ref: "#/components/schemas/UsageSummary"
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/auth/captcha:
    get:
      tags:
        - Auth
      summary: Issue a signup CAPTCHA challenge
      description: "Key-free by default: returns a short-lived signed `token` and a plain-language `question` to answer in `captcha_answer`, plus `min_solve_ms`. When hCaptcha/reCAPTCHA is configured, returns `provider` and `site_key` instead and expects the widget token in `captcha_response`."
      security: []
      responses:
        200:
          description: Challenge
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api/auth/register:
    post:
      tags:
        - Auth
      summary: Register a consumer account
      description: Requires a valid CAPTCHA (see GET /api/auth/captcha) and leaves the hidden honeypot fields empty. Registrations are rate-limited per IP, per email domain and per canonical mailbox; blocked requests return a generic 429.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  minLength: 8
                name:
                  type: string
                captcha_token:
                  type: string
                  description: Signed token from GET /api/auth/captcha (internal provider).
                captcha_answer:
                  type:
                    - string
                    - integer
                  description: Answer to the internal challenge question.
                captcha_response:
                  type: string
                  description: Widget token when hCaptcha/reCAPTCHA is enabled.
              required:
                - email
                - password
      responses:
        201:
          description: Registered
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        400:
          description: Invalid input or failed CAPTCHA
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Registration temporarily blocked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/auth/login:
    post:
      tags:
        - Auth
      summary: Log in and set a session cookie
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                password:
                  type: string
              required:
                - email
                - password
      responses:
        200:
          description: Logged in
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        401:
          description: Incorrect credentials
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/auth/logout:
    post:
      tags:
        - Auth
      summary: Log out
      security:
        - sessionCookie: []
      responses:
        204:
          description: Logged out
  /api/auth/me:
    get:
      tags:
        - Auth
      summary: Current session
      security: []
      responses:
        200:
          description: Session
          content:
            application/json:
              schema:
                type: object
                properties:
                  authenticated:
                    type: boolean
                  user:
                    type: object
                    nullable: true
                    additionalProperties: true
                  plan:
                    type: object
                    nullable: true
                    additionalProperties: true
  /api/account/me:
    get:
      tags:
        - Account
      summary: Current account
      security:
        - sessionCookie: []
      responses:
        200:
          description: Account
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        401:
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/account/keys:
    get:
      tags:
        - Account
      summary: List API keys
      security:
        - sessionCookie: []
      responses:
        200:
          description: Keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKey"
        401:
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags:
        - Account
      summary: Create an API key
      security:
        - sessionCookie: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        201:
          description: Created key (fullKey shown once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  key:
                    $ref: "#/components/schemas/ApiKey"
        401:
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/account/keys/{id}:
    delete:
      tags:
        - Account
      summary: Revoke an API key
      security:
        - sessionCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          description: Revoked
        401:
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/account/usage:
    get:
      tags:
        - Account
      summary: Account usage summary
      security:
        - sessionCookie: []
      parameters:
        - name: days
          in: query
          schema:
            type: integer
            default: 30
      responses:
        200:
          description: Usage
          content:
            application/json:
              schema:
                type: object
                properties:
                  plan:
                    type: object
                    nullable: true
                    additionalProperties: true
                  credits_remaining:
                    type: integer
                  credits:
                    type: object
                    description: Free-credit state used to drive the in-dashboard upgrade nudge.
                    properties:
                      included:
                        type: integer
                      remaining:
                        type: integer
                      threshold:
                        type: integer
                      low:
                        type: boolean
                      exhausted:
                        type: boolean
                      upgrade_available:
                        type: boolean
                      upgrade_nudge:
                        type: string
                        nullable: true
                additionalProperties: true
        401:
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/account/plans:
    get:
      tags:
        - Account
      summary: List plans and billing status
      security: []
      responses:
        200:
          description: Plans
          content:
            application/json:
              schema:
                type: object
                properties:
                  plans:
                    type: array
                    items:
                      $ref: "#/components/schemas/Plan"
                  billing_configured:
                    type: boolean
  /api/account/webhooks:
    get:
      tags:
        - Account
      summary: List registered webhooks
      security:
        - sessionCookie: []
      responses:
        200:
          description: Webhooks
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhooks:
                    type: array
                    items:
                      $ref: "#/components/schemas/Webhook"
        401:
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags:
        - Account
      summary: Register a webhook
      security:
        - sessionCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                    enum:
                      - job.completed
                      - job.failed
              required:
                - url
      responses:
        201:
          description: Webhook registered
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhook:
                    $ref: "#/components/schemas/Webhook"
        400:
          description: Invalid URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/account/webhooks/{id}:
    delete:
      tags:
        - Account
      summary: Delete a webhook
      security:
        - sessionCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          description: Deleted
        401:
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /api/plans:
    get:
      tags:
        - Meta
      summary: Plans, languages and formats
      security: []
      responses:
        200:
          description: Plans
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api/health:
    get:
      tags:
        - Meta
      summary: Health check
      security: []
      responses:
        200:
          description: Healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  service:
                    type: string
                  time:
                    type: string
  /api/config:
    get:
      tags:
        - Meta
      summary: Public runtime config
      security: []
      responses:
        200:
          description: Config
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /mcp:
    post:
      tags:
        - MCP
      summary: MCP Streamable HTTP endpoint
      description: "Model Context Protocol server (JSON-RPC 2.0 over HTTP). Authenticate with the same API key as the REST API (`Authorization: Bearer YOUR_API_KEY`). Supports `initialize`, `tools/list`, `tools/call` and `ping`. Tools: get_transcript, summarize, ask, chapters, search, quotes, clips, repurpose, translate. Connect from Claude Code, Codex or Claude Desktop using this URL."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                jsonrpc:
                  type: string
                  enum:
                    - 2.0
                id:
                  oneOf:
                    - type: string
                    - type: integer
                method:
                  type: string
                  enum:
                    - initialize
                    - tools/list
                    - tools/call
                    - ping
                    - notifications/initialized
                params:
                  type: object
                  additionalProperties: true
              required:
                - jsonrpc
                - method
      responses:
        200:
          description: JSON-RPC response
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        202:
          description: Notification accepted (no response body)
        400:
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        401:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        402:
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        429:
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    get:
      tags:
        - MCP
      summary: No SSE stream (405)
      responses:
        405:
          description: This stateless MCP server does not offer a server-sent event stream.