# Transcript API Start with the [main API guide](agent-api.md) for server setup, the shared key, local testing, the endpoint overview, and [error handling](agent-api.md#errors-and-retries). This page gives the full transcript fields and Colab call examples. All endpoints use the same `X-API-Key` header as the channel API. A video must already exist in MediaMatrix. Use its 11-character YouTube ID, not its URL. These endpoints do not import a missing video or create a channel. Apply `AddChannelExclusion` before using this version; see the [migration and setup instructions](agent-api.md#configure-once). ## Read saved transcripts ```bash curl 'https://mediamatrix.reglitch.com/api/videos/VIDEO_ID/transcripts' \ -H 'X-API-Key: YOUR_SHARED_API_KEY' ``` This returns a JSON array containing the video's saved transcripts, ordered by language. To read one exact language, add `?language=en`. Language matching is case-insensitive. A stored video with no matching transcript returns an empty array; a video missing from the database returns 404. GET reads the database only and never contacts YouTube or changes flags. Each transcript includes `status`, `id`, `videoId`, `language`, `content`, `isAutoGenerated`, `format`, `source`, `confidence`, `createdAt`, and `updatedAt`. GET returns `status: "exists"`. Example response (illustrative IDs and timestamps): ```json [ { "status": "exists", "id": 123, "videoId": "abcdefghijk", "language": "en", "content": "The saved transcript text.", "isAutoGenerated": true, "format": "text", "source": "youtube", "confidence": null, "createdAt": "2026-01-01T12:00:00Z", "updatedAt": null } ] ``` Transcript POST returns the same object directly, without the enclosing array: `status: "created"` for a new record or `status: "already_exists"` for an identical retry. Use `videoId` in the API read URL; `id` identifies the transcript row and is also returned as `transcriptId` in search results. Existing transcript reads and searches remain available for excluded channels. ## Search saved transcript text ```bash curl --get 'https://mediamatrix.reglitch.com/api/transcripts/search' \ -H 'X-API-Key: YOUR_SHARED_API_KEY' \ --data-urlencode 'query=climate change' \ --data-urlencode 'limit=20' ``` `query` is required (up to 200 characters). Surrounding spaces are trimmed. Search matches a literal word, part of a word, or a contiguous phrase in saved transcript content, ignoring case. For example, `climate change` matches `CLIMATE CHANGE`. Percent signs, underscores, apostrophes, and backslashes are literal search characters. Other character comparisons follow the database's collation. This does not search video titles or descriptions. ```json { "query": "climate change", "items": [ { "transcriptId": 123, "videoId": "abcdefghijk", "videoTitle": "Example discussion", "channelId": "UC_EXAMPLE_ID", "language": "en", "isAutoGenerated": false, "flag": null, "excerpt": "We discussed climate change and its effects...", "videoUrl": "https://www.youtube.com/watch?v=abcdefghijk", "websitePath": "/Transcripts/Details/123" } ], "nextAfterId": null } ``` Values above are examples. Each match represents one saved transcript. A video can appear more than once when multiple languages match. The excerpt contains up to 300 characters around the first match, with up to 80 characters of leading context. The API builds excerpts in the database instead of returning entire transcripts in search results. Use the saved-transcript GET endpoint to read the full text. Excerpts are plain text, without highlighting or timestamps. Stored transcripts remain searchable when the video has an unavailable or manual-work flag; the existing flag is included for context. Optional filters: | Parameter | Behavior | | --- | --- | | `channelId` | Search within one channel. | | `videoId` | Search within one video (11-character YouTube ID). | | `language` | Search a language and its regional variants; `en` includes `en-US`. | | `limit` | Results per page, default 20, range 1–100. | | `afterId` | Previous page's `nextAfterId` transcript ID. Omit or use 0 for the first page. | Filters can be combined. Results are ordered by transcript ID, with cursor pagination. Keep the same search and filters while paging. A null `nextAfterId` means there are no further results for that scan. No matches returns 200 with an empty `items` array. Invalid search fields return 400. Search reads saved database content only. YouTube fetching remains in Colab. This literal search needs no database migration; it scans matching transcript text, so larger collections can take longer. Channel, video, or language filters can narrow the work. ## Add transcript text ```bash curl -i --request POST 'https://mediamatrix.reglitch.com/api/videos/VIDEO_ID/transcripts' \ -H 'X-API-Key: YOUR_SHARED_API_KEY' \ -H 'Content-Type: application/json' \ --data '{"language":"en","content":"The full transcript text goes here.","isAutoGenerated":false,"source":"manual"}' ``` Required: nonempty `content`, up to 1,000,000 characters. `language` defaults to `en` and supports language codes up to 10 characters, such as `en`, `en-US`, and `zh-Hans`. Optional fields are `isAutoGenerated` (default false), `format` (default `text`, up to 20 characters), `source` (default `api`, up to 50 characters), and `confidence` (null or 0–1). A new transcript returns 201 with `status: "created"` and a Location header pointing to its language-filtered GET lookup. Submitting identical content for the same video/language returns 200 with `status: "already_exists"`, preserving the existing metadata and timestamps. Different content for a stored language returns 409; the API never silently replaces existing text. Use the website's transcript editor for intentional changes. New submissions for an excluded channel return 409 with the title `This channel is excluded from processing.` Include the channel again before submitting new text. An identical retry for already saved text still returns 200. A new saved transcript clears an existing caption/manual-work flag (`no_captions`, `captions_disabled`, `age_restricted`, or `manual_skip`). Custom flags and `youtube_unavailable` are preserved. The transcript insert and flag change are saved together; a failed insert does not clear the stored flag. For long text, quotes, or multiple lines, use your HTTP client's JSON serializer (such as `requests.post(..., json=payload)`) rather than constructing JSON by hand. For curl, put a UTF-8 JSON object in `transcript.json`, then submit it: ```json { "language": "en", "content": "First line.\nSecond line with a \"quotation\".", "source": "youtube", "isAutoGenerated": true } ``` ```bash curl -i --request POST 'https://mediamatrix.reglitch.com/api/videos/VIDEO_ID/transcripts' \ -H 'X-API-Key: YOUR_SHARED_API_KEY' \ -H 'Content-Type: application/json' \ --data-binary @transcript.json ``` Replace `VIDEO_ID` with a stored video's ID. The API accepts one video's transcript per POST; the channel bulk endpoint is not a transcript batch endpoint. ## List videos missing transcripts ```bash curl 'https://mediamatrix.reglitch.com/api/videos/missing-transcripts?limit=100' \ -H 'X-API-Key: YOUR_SHARED_API_KEY' ``` By default this lists videos with **no saved transcript in any language**, including flagged videos so their status is visible. It reads only the database. The response is paginated: ```json { "items": [ { "videoId": "abcdefghijk", "channelId": "UC_EXAMPLE_ID", "title": "Example video", "videoUrl": "https://www.youtube.com/watch?v=abcdefghijk", "flag": null, "channelIsExcluded": false, "channelExcludesTranscripts": false, "shouldFetch": true }, { "videoId": "lmnopqrstuv", "channelId": "UC_EXAMPLE_ID", "title": "Unavailable video", "videoUrl": "https://www.youtube.com/watch?v=lmnopqrstuv", "flag": "youtube_unavailable", "channelIsExcluded": false, "channelExcludesTranscripts": false, "shouldFetch": false } ], "nextAfterId": null } ``` Titles and IDs above are examples. `flag` is the video's existing stored label; `shouldFetch` is false for these known blockers, ignoring case and surrounding spaces: - `youtube_unavailable`: private, deleted, removed, or otherwise inaccessible. - `age_restricted` - `no_captions` - `captions_disabled` - `manual_skip` Other custom labels are returned unchanged and do not automatically block fetching. Flags describe the last saved state; this API does not check current YouTube availability. `channelIsExcluded` reports the parent channel's separate exclusion setting. If true, `shouldFetch` is false even when the video's `flag` is null. The `includeBlocked=false` queue excludes those videos too. Include the channel again on its Details page, or explicitly send `isExcluded: false` to the channel API, to make it eligible again. `channelExcludesTranscripts` reports the independent transcript-only channel setting. When true, all current and future channel videos have `shouldFetch: false` and are omitted by `includeBlocked=false`. Channel/video metadata updates and imports still run. Change it using **Skip transcripts / Allow transcripts** on channel Details or `excludeTranscripts: true/false` in `POST /api/channels`. Omit the field on ordinary add retries to preserve it. Full exclusion and video flags still apply after allowing channel transcripts. Saved transcript reads and searches remain available, as does manual transcript entry/editing on the website. New API submissions while transcript-only exclusion is enabled return 409 with title `Transcript fetching is excluded for this channel.` Identical existing text still returns 200; it does not create another transcript. This also protects against Colab queues loaded before the channel setting changed. For Colab's automatic queue, exclude known blockers on the server: ```text GET /api/videos/missing-transcripts?includeBlocked=false&limit=100 ``` Optional query parameters: | Parameter | Behavior | | --- | --- | | `includeBlocked` | Default true. Set false to return only `shouldFetch: true` videos. | | `language` | Omit for videos with no transcripts at all. `language=en` includes videos missing English, even if another language is saved; saved `en` or regional `en-*` tracks count as present. | | `channelId` | Limit the queue to one stored channel ID. | | `limit` | Page size, default 100, range 1–500. | | `afterId` | Pass the previous response's `nextAfterId` to continue. | When `nextAfterId` is null, that scan is finished. Keep the same filters while paging. Cursor pagination avoids skipping videos when transcripts saved by Colab remove earlier videos from the queue. Newly added videos before the cursor are picked up on the next scan starting without `afterId`. ## Use from Colab Keep YouTube fetching, failure classification, and cooldowns in your existing Colab code. The website supplies the queue and accepts successfully fetched text. All calls use the same shared API key. ```python import getpass import requests API_BASE = "https://mediamatrix.reglitch.com" HEADERS = {"X-API-Key": getpass.getpass("MediaMatrix API key: ")} def get_pending_videos(after_id=None): params = {"limit": 100, "includeBlocked": "false"} if after_id: params["afterId"] = after_id response = requests.get( f"{API_BASE}/api/videos/missing-transcripts", headers=HEADERS, params=params, timeout=60 ) response.raise_for_status() return response.json() def save_transcript(video_id, content, language="en", is_auto_generated=False): response = requests.post( f"{API_BASE}/api/videos/{video_id}/transcripts", headers=HEADERS, json={"language": language, "content": content, "source": "youtube", "isAutoGenerated": is_auto_generated, "format": "text"}, timeout=60 ) response.raise_for_status() return response.json() page = get_pending_videos() # Feed page["items"] to your existing fetching loop. # After a successful fetch, call save_transcript(...) with its text and language. # Finish that page, then pass page["nextAfterId"] to get_pending_videos(...). # Stop when nextAfterId is None. Do not call get_pending_videos(None) to continue. ``` This exposes existing flags; it does not add an API to change them. Your current Colab flag-writing workflow can continue handling newly discovered caption or availability failures. Temporary YouTube failures and waiting remain in Colab. A successful save removes a video from the corresponding missing-transcript queue automatically. Transcript submissions return 201 when saved, 200 for identical retries, or 409 if different text already exists. Inspect errors rather than repeatedly overwriting or retrying a conflict. ## Offline verification Run from the `MediaMatrix.Web` folder: ```bash dotnet run --project tests/MediaMatrix.AgentApiChecks ``` Checks use mocked channel responses and an in-memory database. They cover queue filtering, flags, cursor pagination while saving, and transcript submissions. They do not start the website, contact YouTube, or access the hosted database.