{
  "openapi": "3.0.3",
  "info": {
    "title": "MediaMatrix Agent API",
    "version": "1.1.0",
    "description": "Personal agent API for channels and transcripts. Authenticate with X-API-Key. Colab fetches YouTube transcripts; the website lists missing transcripts with video flags, accepts text submissions, and reads saved text."
  },
  "servers": [
    {
      "url": "https://mediamatrix.reglitch.com",
      "description": "MediaMatrix website"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/api/channels": {
      "post": {
        "operationId": "addChannel",
        "summary": "Add a YouTube channel to MediaMatrix",
        "description": "Accepts a channel ID, @handle, or YouTube channel URL. A stored channel returns 200 already_exists. Optional video importing adds missing videos without overwriting existing data. Respect Retry-After on temporary YouTube failures. Optional isExcluded sets or clears channel exclusion; omitted preserves it on stored channels. Excluded channels never import videos. Optional excludeTranscripts controls transcript-only exclusion without preventing video imports; omission preserves the stored setting.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddChannelRequest"
              },
              "example": {
                "channel": "@GoogleDevelopers",
                "importVideos": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Channel created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelResponse"
                }
              }
            },
            "headers": {
              "Location": {
                "description": "Stored channel API URL",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "200": {
            "description": "Channel already exists; optional missing videos may have been added",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelResponse"
                }
              }
            }
          },
          "400": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "900 seconds for temporary YouTube failures; absent for configuration errors.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "504": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "900 seconds for temporary YouTube failures; absent for configuration errors.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/channels/{channelId}": {
      "get": {
        "operationId": "getStoredChannel",
        "summary": "Read a channel stored in MediaMatrix",
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "UC_x5XG1OV2P6uZZ5FSM9Ttw"
          }
        ],
        "responses": {
          "200": {
            "description": "Stored channel with status exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelResponse"
                }
              }
            }
          },
          "401": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/channels/bulk": {
      "post": {
        "operationId": "addChannelsBulk",
        "summary": "Add up to 50 YouTube channels in one request",
        "description": "Channels only, saved independently and processed sequentially. Invalid or missing channels do not stop the batch; server/upstream failures do. Retrying stored channels does not create duplicates. Optional isExcluded applies to every input; omitted preserves existing exclusion settings. Optional excludeTranscripts applies transcript-only exclusion to every input; omission preserves each stored setting.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddChannelsRequest"
              },
              "example": {
                "channels": [
                  "@GoogleDevelopers",
                  "https://www.youtube.com/@YouTube"
                ]
              }
            }
          }
        },
        "responses": {
          "400": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "JSON Problem Details. Validation errors may also contain an errors object.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "900 seconds for temporary YouTube failures; absent for configuration errors.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "200": {
            "description": "Batch summary with one result per input. Inspect every result: HTTP 200 can include failures. Server/upstream errors stop the queue and leave remaining channels not_processed. Earlier successful imports stay saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkChannelsResponse"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "900 seconds when YouTube rejects or times out; pause the queue before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/videos/{videoId}/transcripts": {
      "parameters": [
        {
          "name": "videoId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{11}$"
          },
          "description": "11-character YouTube ID of a video already stored in MediaMatrix."
        }
      ],
      "get": {
        "operationId": "getSavedTranscripts",
        "summary": "Read saved transcripts for a video",
        "description": "Database only. Returns an array; empty when the stored video has no matching transcripts. Does not contact YouTube.",
        "parameters": [
          {
            "name": "language",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 10,
              "pattern": "^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"
            },
            "description": "Optional exact language filter, case-insensitive. Omit to read all saved languages."
          }
        ],
        "responses": {
          "400": {
            "description": "Invalid input or JSON.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Missing or incorrect shared key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Video not stored.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Storage failure.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Shared key is not configured on the server.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "200": {
            "description": "Saved transcripts ordered by language.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TranscriptResponse"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "addTranscript",
        "summary": "Save transcript text for a stored video",
        "description": "One transcript per video/language. Identical retries are safe. Different stored content is never overwritten.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveTranscriptRequest"
              },
              "example": {
                "language": "en",
                "content": "Full transcript text.",
                "source": "manual",
                "isAutoGenerated": false
              }
            }
          }
        },
        "responses": {
          "400": {
            "description": "Invalid input or JSON.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Missing or incorrect shared key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Video not stored.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Storage failure.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Shared key is not configured on the server.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "200": {
            "description": "Identical content already exists; existing metadata preserved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptResponse"
                }
              }
            }
          },
          "201": {
            "description": "Transcript saved. Caption/manual-work flags cleared; custom and availability flags preserved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptResponse"
                }
              }
            },
            "headers": {
              "Location": {
                "description": "Language-filtered stored transcript lookup.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "Different text already exists, another request saved it, or the parent channel is fully excluded or excludes transcripts. Inspect the title and allow channel transcripts before submitting new text. Identical existing text returns 200.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/videos/missing-transcripts": {
      "get": {
        "operationId": "getVideosMissingTranscripts",
        "summary": "List videos missing transcripts, including their flags",
        "description": "Database only. By default lists videos with no transcript in any language, including blocked videos with shouldFetch=false. Set includeBlocked=false for the automatic Colab queue. Optional language counts matching regional transcripts as present. Use cursor pagination while saving transcripts; no YouTube requests are made. Excluded-channel videos are included with channelIsExcluded=true and shouldFetch=false by default, and omitted when includeBlocked=false. Transcript-only exclusion is reported as channelExcludesTranscripts=true with shouldFetch=false; these videos are also omitted when includeBlocked=false.",
        "parameters": [
          {
            "name": "language",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 10,
              "pattern": "^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"
            },
            "description": "Omit to require no transcript in any language. en means missing both en and en-* tracks, ignoring case."
          },
          {
            "name": "channelId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 50
            }
          },
          {
            "name": "includeBlocked",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "afterId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 50
            },
            "description": "Previous page nextAfterId, or omit for a fresh scan."
          }
        ],
        "responses": {
          "200": {
            "description": "Missing transcript page with flags and processing eligibility.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MissingTranscriptsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or JSON.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Missing or incorrect shared key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Storage failure.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Shared key is not configured on the server.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/transcripts/search": {
      "get": {
        "operationId": "searchTranscripts",
        "summary": "Search saved transcript text",
        "description": "Case-insensitive literal substring or contiguous phrase search, with surrounding query spaces trimmed. Punctuation is literal, not wildcard syntax; other comparisons follow database collation. Searches stored transcript content only. Returns one result per matching transcript with a 300-character excerpt around the first match. Does not contact YouTube or change records. Keep query and filters fixed while paging.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "climate change"
          },
          {
            "name": "channelId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 50
            }
          },
          {
            "name": "videoId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{11}$"
            }
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 10,
              "pattern": "^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$"
            },
            "description": "Includes regional variants, ignoring case."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "afterId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2147483647,
              "default": 0
            },
            "description": "Previous page nextAfterId, or 0 to start."
          }
        ],
        "responses": {
          "400": {
            "description": "Missing, blank or oversized query, or invalid filters/cursor/limit.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Missing or incorrect shared key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Transcript search failed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Shared key is not configured on the server.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "200": {
            "description": "Matching transcripts ordered by ID; empty items when none match. One result per transcript, with bounded excerpts and existing flags.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptSearchResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "AddChannelRequest": {
        "type": "object",
        "required": [
          "channel"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "YouTube channel ID, @handle, or /@handle, /channel/ID, or legacy /user/name YouTube URL."
          },
          "importVideos": {
            "type": "boolean",
            "default": false,
            "description": "Also fetch videos from the uploads playlist."
          },
          "maxVideos": {
            "type": "integer",
            "minimum": 1,
            "maximum": 500,
            "default": 50,
            "description": "Maximum videos to fetch when importVideos is true; existing videos are skipped."
          },
          "isExcluded": {
            "type": "boolean",
            "nullable": true,
            "description": "true remembers the channel but excludes updates, imports, and automatic transcript work. false explicitly reactivates it. Omit to preserve existing state; new channels default active. Exclusion overrides importVideos."
          },
          "excludeTranscripts": {
            "type": "boolean",
            "nullable": true,
            "description": "Skip automatic transcript work for all current and future videos, while allowing channel/video metadata updates and imports. Omit to preserve existing state; new channels default false. Explicit false allows transcripts again, subject to full exclusion and video flags."
          }
        }
      },
      "ChannelResponse": {
        "type": "object",
        "required": [
          "status",
          "channelId",
          "name",
          "youTubeUrl",
          "websitePath",
          "videosInDatabase",
          "videosAdded",
          "videosSkipped",
          "isExcluded",
          "excludeTranscripts"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "created",
              "already_exists",
              "exists"
            ]
          },
          "channelId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "youTubeUrl": {
            "type": "string",
            "format": "uri"
          },
          "websitePath": {
            "type": "string",
            "description": "Relative website page for this channel."
          },
          "videosInDatabase": {
            "type": "integer",
            "minimum": 0
          },
          "videosAdded": {
            "type": "integer",
            "minimum": 0
          },
          "videosSkipped": {
            "type": "integer",
            "minimum": 0
          },
          "subscriberCount": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          },
          "publicVideoCount": {
            "type": "integer",
            "nullable": true
          },
          "isExcluded": {
            "type": "boolean",
            "description": "The channel is remembered but excluded from automatic processing."
          },
          "excludeTranscripts": {
            "type": "boolean",
            "description": "Automatic transcript work is skipped for all channel videos. Metadata and video imports remain allowed unless isExcluded is true."
          }
        }
      },
      "Problem": {
        "type": "object",
        "properties": {
          "status": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      },
      "AddChannelsRequest": {
        "type": "object",
        "required": [
          "channels"
        ],
        "properties": {
          "channels": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "description": "Channel URLs, handles, or canonical IDs, processed sequentially. Channels only; no video imports.",
            "items": {
              "type": "string",
              "nullable": true,
              "maxLength": 500
            }
          },
          "isExcluded": {
            "type": "boolean",
            "nullable": true,
            "description": "Apply this exclusion setting to every channel in the batch. Omit to preserve each existing setting; new channels default active."
          },
          "excludeTranscripts": {
            "type": "boolean",
            "nullable": true,
            "description": "Skip automatic transcript work for all current and future videos, while allowing channel/video metadata updates and imports. Omit to preserve existing state; new channels default false. Explicit false allows transcripts again, subject to full exclusion and video flags."
          }
        }
      },
      "BulkChannelResult": {
        "type": "object",
        "required": [
          "channel",
          "status",
          "statusCode",
          "data",
          "error"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "already_exists",
              "failed",
              "not_processed"
            ]
          },
          "statusCode": {
            "type": "integer",
            "nullable": true,
            "description": "Per-channel HTTP status; null when not processed."
          },
          "data": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/ChannelResponse"
              }
            ]
          },
          "error": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/Problem"
              }
            ]
          }
        }
      },
      "BulkChannelsResponse": {
        "type": "object",
        "required": [
          "requested",
          "created",
          "alreadyExists",
          "failed",
          "notProcessed",
          "retryAfterSeconds",
          "results"
        ],
        "properties": {
          "requested": {
            "type": "integer",
            "minimum": 0
          },
          "created": {
            "type": "integer",
            "minimum": 0
          },
          "alreadyExists": {
            "type": "integer",
            "minimum": 0
          },
          "failed": {
            "type": "integer",
            "minimum": 0
          },
          "notProcessed": {
            "type": "integer",
            "minimum": 0
          },
          "retryAfterSeconds": {
            "type": "integer",
            "nullable": true,
            "description": "Pause this many seconds before retrying a stopped queue."
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BulkChannelResult"
            }
          }
        }
      },
      "SaveTranscriptRequest": {
        "type": "object",
        "required": [
          "content"
        ],
        "properties": {
          "language": {
            "type": "string",
            "minLength": 2,
            "maxLength": 10,
            "pattern": "^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$",
            "default": "en"
          },
          "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000000
          },
          "isAutoGenerated": {
            "type": "boolean",
            "default": false
          },
          "format": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "default": "text"
          },
          "source": {
            "type": "string",
            "maxLength": 50,
            "nullable": true,
            "default": "api"
          },
          "confidence": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "maximum": 1,
            "nullable": true
          }
        }
      },
      "TranscriptResponse": {
        "type": "object",
        "required": [
          "status",
          "id",
          "videoId",
          "language",
          "content",
          "isAutoGenerated",
          "createdAt"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "created",
              "already_exists",
              "exists"
            ]
          },
          "id": {
            "type": "integer"
          },
          "videoId": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "isAutoGenerated": {
            "type": "boolean"
          },
          "format": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "type": "string",
            "nullable": true
          },
          "confidence": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "MissingTranscriptVideo": {
        "type": "object",
        "required": [
          "videoId",
          "channelId",
          "title",
          "videoUrl",
          "flag",
          "shouldFetch",
          "channelIsExcluded",
          "channelExcludesTranscripts"
        ],
        "properties": {
          "videoId": {
            "type": "string"
          },
          "channelId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "videoUrl": {
            "type": "string"
          },
          "flag": {
            "type": "string",
            "nullable": true,
            "description": "Existing flag, unchanged. youtube_unavailable represents private/deleted/inaccessible videos."
          },
          "shouldFetch": {
            "type": "boolean",
            "description": "False when the parent channel is fully excluded, excludes transcripts, or the video has a known transcript/availability blocker."
          },
          "channelIsExcluded": {
            "type": "boolean",
            "description": "Parent channel is excluded. shouldFetch is false regardless of the video flag."
          },
          "channelExcludesTranscripts": {
            "type": "boolean",
            "description": "Parent channel skips transcripts only. shouldFetch is false even without a video flag. Independent of channelIsExcluded."
          }
        }
      },
      "MissingTranscriptsResponse": {
        "type": "object",
        "required": [
          "items",
          "nextAfterId"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MissingTranscriptVideo"
            }
          },
          "nextAfterId": {
            "type": "string",
            "nullable": true,
            "description": "Cursor for the next page; null means the scan is finished. Keep all other filters the same."
          }
        }
      },
      "TranscriptSearchMatch": {
        "type": "object",
        "required": [
          "transcriptId",
          "videoId",
          "videoTitle",
          "channelId",
          "language",
          "isAutoGenerated",
          "flag",
          "excerpt",
          "videoUrl",
          "websitePath"
        ],
        "properties": {
          "transcriptId": {
            "type": "integer",
            "minimum": 1
          },
          "videoId": {
            "type": "string"
          },
          "videoTitle": {
            "type": "string"
          },
          "channelId": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "isAutoGenerated": {
            "type": "boolean"
          },
          "flag": {
            "type": "string",
            "nullable": true
          },
          "excerpt": {
            "type": "string",
            "maxLength": 300,
            "description": "Plain text around the first match, with up to 80 leading context characters. No timestamps or highlighting."
          },
          "videoUrl": {
            "type": "string"
          },
          "websitePath": {
            "type": "string",
            "description": "Relative website transcript details page."
          }
        }
      },
      "TranscriptSearchResponse": {
        "type": "object",
        "required": [
          "query",
          "items",
          "nextAfterId"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "Trimmed search text."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TranscriptSearchMatch"
            }
          },
          "nextAfterId": {
            "type": "integer",
            "nullable": true,
            "description": "Last returned transcript ID when more results exist; null marks the end of the scan."
          }
        }
      }
    }
  }
}
