# YouTube Transcript & Subtitles Scraper API (`zovo-one/tubetext-youtube-transcript-scraper`) Actor

Get YouTube transcripts and captions from videos, playlists and channels as timed segments, plain text, SRT or VTT. Many languages, an auto-generated fallback and video metadata. Each failed video gets a clear error row and the run never hangs. Built for AI and RAG pipelines.

- **URL**: https://apify.com/zovo-one/tubetext-youtube-transcript-scraper.md
- **Developed by:** [Michael Lip](https://apify.com/zovo-one) (community)
- **Categories:** Videos, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.50 / 1,000 transcripts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## TubeText YouTube Transcript Scraper

Get YouTube transcripts from videos, playlists and channels as plain text, timestamped segments, SRT or WebVTT, for $0.0025 per saved transcript. Videos that fail cost nothing.

Paste any mix of watch URLs, youtu.be links, Shorts, playlist URLs and `@handles`, one per line. You get one row per video with the transcript and the video's title, channel, publish date, duration and view count. When a video can't be read (private, age-restricted, no captions, blocked), you get a free row with a clear error code and the run keeps going.

People use it to feed transcripts into RAG pipelines and LLM prompts, to search what a channel has said about a topic, and to make subtitle files.

### What you get

One dataset row per video. Every successful row has the full plain text in `text`. The output format adds segments, SRT or WebVTT on top.

| Field | Type | What it holds |
|---|---|---|
| `status` | string | `ok` (transcript saved, charged) or `error` (free) |
| `videoId`, `url` | string | The 11-character video id and the watch URL |
| `title` | string | Video title |
| `channelName`, `channelId`, `channelUrl` | string | The channel that uploaded the video |
| `publishDate` | string | ISO 8601 date with YouTube's timezone offset |
| `durationSeconds` | integer | Video length in seconds |
| `viewCount` | integer | Views when the run read the video |
| `isLiveContent` | boolean | `true` for past live streams |
| `category` | string | YouTube category, for example `Music` |
| `languageCode`, `languageName` | string | The caption track that was used |
| `isGenerated` | boolean | `true` means YouTube's speech-recognition captions |
| `languageMatched` | boolean | `false` when none of your languages existed and the original language came back |
| `availableLanguages` | array | Every caption track the video has, auto tracks marked `(auto)` |
| `segmentCount`, `wordCount` | integer | Number of caption lines and words |
| `transcriptEndSeconds` | number | Where the last caption ends |
| `outputFormat` | string | `segments`, `text`, `srt` or `vtt` |
| `text` | string | The full transcript as plain text |
| `segments` | array | `{ start, duration, text }` per caption line, in seconds (segments format) |
| `srt`, `vtt` | string | A ready subtitle file (srt or vtt format) |
| `source` | object | Which input produced the row. Playlist and channel rows add `title` and `position` |
| `errorCode`, `errorMessage` | string | Why a video failed (error rows only) |

The title, channel, publish date, duration, views and category fields are filled when "Include video metadata" is on, which is the default. The dataset has three views in Console: Transcripts, Errors and Languages. The `OUTPUT` record in the key-value store holds a run summary with counts per error code.

### Example output

Here is a real row from a test run on 2026-11-05 with the prefilled input. The `segments` list is cut to 2 of its 6 entries. Nothing else is changed.

```json
{
  "status": "ok",
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Me at the zoo",
  "channelName": "jawed",
  "channelId": "UC4QobU6STFB0P71PMvOGN5A",
  "channelUrl": "https://www.youtube.com/channel/UC4QobU6STFB0P71PMvOGN5A",
  "publishDate": "2005-04-23T20:31:52-07:00",
  "durationSeconds": 19,
  "viewCount": 447802112,
  "isLiveContent": false,
  "category": "Film & Animation",
  "languageCode": "en",
  "languageName": "English",
  "isGenerated": false,
  "languageMatched": true,
  "availableLanguages": ["en", "de"],
  "segmentCount": 6,
  "wordCount": 39,
  "transcriptEndSeconds": 18.881,
  "outputFormat": "segments",
  "text": "All right, so here we are, in front of the elephants the cool thing about these guys is that they have really... really really long trunks and that's cool (baaaaaaaaaaahhh!!) and that's pretty much all there is to say",
  "segments": [
    { "start": 1.2, "duration": 2.16, "text": "All right, so here we are, in front of the elephants" },
    { "start": 5.318, "duration": 2.656, "text": "the cool thing about these guys is that they have really..." }
  ],
  "source": { "type": "video", "input": "https://www.youtube.com/watch?v=jNQXAC9IVRw", "id": "jNQXAC9IVRw" },
  "errorCode": null,
  "errorMessage": null
}
```

An error row looks like this (shape from the code, values shown for a private video).

```json
{
  "status": "error",
  "videoId": "<video id>",
  "url": "https://www.youtube.com/watch?v=<video id>",
  "source": { "type": "video", "input": "<your input line>", "id": "<video id>" },
  "errorCode": "VIDEO_PRIVATE",
  "errorMessage": "<message from YouTube>"
}
```

### Pricing

Pay per event, one event.

| Event | When it's charged | Price |
|---|---|---|
| Transcript (`video`) | Once per video whose transcript is saved to the dataset | $0.0025 |

Error rows are pushed without an event, so they're free. Listing a playlist or channel is free too. A video from a playlist or channel costs the same as one you paste directly.

Some worked examples.

| Run | Transcripts saved | Cost |
|---|---|---|
| The prefilled input (2 videos) | 2 | $0.005 |
| A playlist of 100 videos, all with captions, max videos per playlist set to 100 | 100 | $0.25 |
| A channel's latest 50 uploads (max videos per channel set to 50), 40 with captions and 10 `NO_CAPTIONS` | 40 | $0.10 |
| 1,000 videos for a RAG index | 1,000 | $2.50 |

To cap your spend, set a maximum cost per run (the max total charge option in Console, or `maxTotalChargeUsd` in the API). The Actor starts a video only when its $0.0025 still fits. When the budget is used up, the run stops cleanly, and `OUTPUT.videosNotStarted` tells you how many videos were left. Videos run in input order, so put the ones you care about first.

### Quick start in Console

1. Open the Actor and click Start. The input is prefilled with two short public videos that have English captions.
2. Our local test of this input took about 12 seconds. It costs $0.005. Open the Transcripts view, or export the dataset as JSON, CSV or Excel.
3. Replace the URLs with your own videos, playlists or channels and run again.

### Use from the API

One call runs the Actor and returns the dataset rows.

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/zovo-one~tubetext-youtube-transcript-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://www.youtube.com/watch?v=jNQXAC9IVRw",
      "https://www.youtube.com/@TheCodingTrain"
    ],
    "languages": ["en"],
    "outputFormat": "text",
    "maxVideosPerSource": 10
  }'
```

The sync endpoint waits up to 5 minutes. For large playlists or channels, start the run with `POST /v2/acts/zovo-one~tubetext-youtube-transcript-scraper/runs` and read the dataset when it finishes.

### Use from Python

```bash
pip install apify-client
```

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("zovo-one/tubetext-youtube-transcript-scraper").call(run_input={
    "urls": ["https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH"],
    "languages": ["en"],
    "outputFormat": "segments",
    "maxVideosPerSource": 20,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["status"] == "ok":
        print(item["title"], item["wordCount"])
    else:
        print("skipped", item["videoId"], item["errorCode"])
```

### Use from Node.js

```bash
npm install apify-client
```

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('zovo-one/tubetext-youtube-transcript-scraper').call({
    urls: ['https://www.youtube.com/@TheCodingTrain'],
    languages: ['en', 'de'],
    outputFormat: 'srt',
    maxVideosPerSource: 5,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
    if (item.status === 'ok') console.log(item.title, item.srt.length);
}
```

### Use from AI agents through MCP

The Apify MCP server at [mcp.apify.com](https://mcp.apify.com) exposes this Actor as a tool to Claude, Cursor, VS Code and other MCP clients. Each client adds servers in its own way. VS Code and Claude Desktop, for example, don't take the JSON below as written. For your client, open the [MCP configurator](https://mcp.apify.com), pick this Actor and copy the setup it gives you. A client that reads an `mcpServers` block with a `url` key, such as Cursor, can use this config.

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=zovo-one/tubetext-youtube-transcript-scraper"
    }
  }
}
```

On first connect your browser opens so you can sign in to Apify. If your client can't do that, add a `"headers": { "Authorization": "Bearer <APIFY_TOKEN>" }` entry instead. Agents connected to the default server can also find the Actor with `search-actors`, run it with `call-actor` and read the rows with `get-dataset-items`. Pass `maxTotalChargeUsd` in the call options to cap what one agent call can spend.

Here's a prompt that works well.

> Get the English transcripts of the latest 10 videos on @TheCodingTrain. For each video, give me the title and a two-line summary, and list every video that mentions p5.js.

Transcripts are a good fit for agents. The `text` field is one clean string per video, and `wordCount` lets the agent check the size before it puts a transcript into context.

### Input reference

Only `urls` is required. Everything else has a default.

| Field | What it does | Default |
|---|---|---|
| `urls` | Videos, playlists or channels, one per line | prefilled with 2 videos |
| `languages` | Preferred language codes, in order. `en` also matches `en-US` and `en-GB` | `["en"]` |
| `allowAutoGenerated` | Use YouTube's automatic captions when no human-made track exists in your languages | `true` |
| `fallbackToAnyLanguage` | Return the original-language transcript when none of your languages exists | `true` |
| `outputFormat` | `segments`, `text`, `srt` or `vtt`. The `text` field is always included | `segments` |
| `includeMetadata` | Add title, channel, publish date, duration, views and category | `true` |
| `maxVideosPerSource` | Playlists: the first N videos. Channels: the latest N uploads | `20` (max 5,000) |
| `maxTotalVideos` | Hard cap for the whole run, after duplicates are removed | `1000` (max 10,000) |
| `proxyConfiguration` | Proxy settings | Apify Proxy, residential |
| `maxConcurrency` | Videos read in parallel | `3` (max 10) |
| `maxRetries` | Extra attempts per video after a block, rate limit or network error | `2` (max 5) |

Accepted formats, one per line.

- Videos. `https://www.youtube.com/watch?v=ID`, `https://youtu.be/ID`, `/shorts/ID`, `/embed/ID`, `/live/ID`, `m.` and `music.` hosts, or a bare 11-character id. A watch URL with `&list=` is read as that one video.
- Playlists. `https://www.youtube.com/playlist?list=PL...` or a bare playlist id (`PL...`, `UU...`, `OLAK5uy_...`).
- Channels. `https://www.youtube.com/@handle`, `@handle`, `/channel/UC...`, `UC...`, `/c/name` or `/user/name`. The Actor takes the uploads, newest first. Shorts and past live streams count as uploads.

### Limits

The Actor reads public captions only. It never signs in, so private, age-restricted and members-only videos give error rows.

It doesn't transcribe audio. A video with no caption track gives `NO_CAPTIONS`. It doesn't translate either, so if the video lacks your language, you get the original language (or a free error row with the fallback off). Chapters, comments and descriptions aren't included.

One run takes up to 5,000 input lines and 10,000 videos. A run uses at most 10 languages and 10 parallel videos. YouTube Mix playlists (`RD...`) are made per viewer and can't be listed, so they give `INVALID_INPUT`.

YouTube blocks many datacenter IPs. The default proxy is Apify residential. If the proxy you pick can't be set up, the Actor logs a warning and runs without one. A blocked video is retried through a new proxy session, up to `maxRetries` times. Each request times out after 30 seconds.

A traffic guard protects the run. It counts compressed response bodies, the same number as `OUTPUT.responseBodyKBPerVideo`. Once 3 videos are done, the run stops if the average passes 225 KB per video. In our tests a video used about 30 to 165 KB on the wire, and the 165 KB one was 6 hours long. So the guard mostly catches large fallback pages from YouTube, but a run made only of very long videos (about 10 hours each) can hit it too.

YouTube's endpoints are undocumented and can change. When they do, you'll see error rows with a code, not a crashed run.

#### Error codes

| `errorCode` | Meaning | Retried |
|---|---|---|
| `VIDEO_UNAVAILABLE` | Deleted, removed or wrong id | no |
| `VIDEO_PRIVATE` | Private video | no |
| `AGE_RESTRICTED` | Needs a signed-in adult account | no |
| `LOGIN_REQUIRED` | Members-only or other sign-in content | no |
| `VIDEO_UNPLAYABLE` | YouTube won't play it, for example region-blocked or still processing. A past live stream whose recording YouTube did not keep gives this code with the message "This live stream recording is not available." | no |
| `LIVE_NOT_FINISHED` | An upcoming stream, or one YouTube reports as live or not yet processed. Captions come after the stream ends | no |
| `NO_CAPTIONS` | The video has no caption tracks | no |
| `NO_TRANSCRIPT_IN_LANGUAGE` | None of your languages exists (fallback off), or only auto captions exist and you turned them off. The message lists the tracks that do exist | no |
| `EMPTY_TRANSCRIPT` | The caption track exists but is empty | no |
| `BLOCKED` | YouTube rate-limited or bot-checked the IP | yes, new proxy session |
| `PO_TOKEN_REQUIRED` | YouTube sent an empty caption file (retried) or a track that needs a proof-of-origin token (not retried) | empty file only |
| `NETWORK_ERROR`, `PARSE_ERROR`, `CONSENT_FAILED` | Temporary problems | yes |
| `HTTP_ERROR` | A request to YouTube got an HTTP error | only for 408, 429, 5xx or no status code |
| `PLAYLIST_NOT_FOUND`, `CHANNEL_NOT_FOUND` | The playlist or channel doesn't exist or is private | no |
| `INVALID_INPUT` | The line isn't a YouTube video, playlist or channel | no |

### FAQ

#### Do I pay for videos without captions?

No. Any video that ends as an error row is free, including videos from a playlist or channel. You pay $0.0025 only when a transcript is saved.

#### Why did I get a transcript in another language?

None of your languages existed, so the Actor returned the video's original language and set `languageMatched` to `false`. Turn off "Fall back to the video's own language" to get a free `NO_TRANSCRIPT_IN_LANGUAGE` row instead.

#### Can I get only captions written by a person?

Yes. Turn off "Use auto-generated captions". Auto captions have `isGenerated: true`, have no punctuation and can contain mistakes.

#### Do Shorts work?

Yes, if the Short has captions. Many Shorts don't, and those give `NO_CAPTIONS`.

#### What is the `tubetext-state` store in my account?

A small key-value store with one record, `PLAYER_API_KEY`. It's the public web API key that YouTube puts in every watch page, not your key. The Actor saves it so a later run doesn't have to download a 340 KB watch page again. You can delete it any time.

#### Is it legal to scrape YouTube transcripts?

The Actor reads only publicly available captions and never signs in. You're responsible for how you use the data, including YouTube's Terms of Service and the video owner's copyright.

### Changelog

See [CHANGELOG.md](./CHANGELOG.md).

# Changelog

This Actor's version history is a separate document: https://apify.com/zovo-one/tubetext-youtube-transcript-scraper/changelog.md

# Actor input Schema

## `urls` (type: `array`):

Paste YouTube videos, playlists or channels, one per line, in any mix. Videos: watch URL, youtu.be link, Shorts URL or an 11-character video id (for example jNQXAC9IVRw). Playlists: https://www.youtube.com/playlist?list=PL... or the playlist id. Channels: https://www.youtube.com/@handle, @handle, /channel/UC..., /c/name or a UC... id (the latest uploads are used). A watch URL that also has \&list= is read as the single video. YouTube Mix playlists (RD...) are not supported. Duplicates are removed. The prefilled two videos make a quick test run of about $0.005.

## `languages` (type: `array`):

Language codes in order of preference, for example en, de, es, pt-BR, ja. For each language, a caption track written by a person is used before an auto-generated one. "en" also accepts regional tracks such as en-US or en-GB. Up to 10 codes.

## `allowAutoGenerated` (type: `boolean`):

Use YouTube's automatic (speech-recognition) captions when no caption track written by a person exists in your languages. Most videos only have auto-generated captions, so leave this on unless you need human-made captions only.

## `fallbackToAnyLanguage` (type: `boolean`):

If none of your preferred languages exists, return the transcript in the video's original language (languageMatched = false on the row). Turn off to get an error row (NO_TRANSCRIPT_IN_LANGUAGE) instead, which is not charged.

## `outputFormat` (type: `string`):

Every row has the full plain text in "text". Choose what else to add: timestamped segments (start and duration in seconds, good for search and citations), an SRT subtitle file or a WebVTT subtitle file. Pick "Plain text only" for the smallest rows when you feed transcripts to an LLM.

## `includeMetadata` (type: `boolean`):

Adds title, channel name and id, publish date, duration, view count and category to each row. Turn off for slightly faster runs (one request less per video).

## `maxVideosPerSource` (type: `integer`):

Playlists: the first N videos in playlist order. Channels: the latest N uploads (Shorts and past live streams count as uploads). Listing is free; each video is charged only if its transcript is saved. Example: 20 videos with captions cost $0.05.

## `maxTotalVideos` (type: `integer`):

Hard cap on the number of videos in one run, across all inputs, after duplicates are removed. Use it as a safety limit for large channels.

## `proxyConfiguration` (type: `object`):

YouTube blocks many datacenter IPs, so the residential group is the reliable choice. If a request is blocked, the Actor retries the video through a new proxy session. If the proxy can't be set up, the run continues without one and logs a warning.

## `maxConcurrency` (type: `integer`):

How many videos are read in parallel (1 to 10). Keep it low (1 to 5) to avoid YouTube rate limits.

## `maxRetries` (type: `integer`):

Extra attempts for a video after a block, rate limit or network error, each through a new proxy session when a proxy is on. Permanent errors such as private or age-restricted videos are not retried.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "languages": [
    "en",
    "de"
  ],
  "allowAutoGenerated": true,
  "fallbackToAnyLanguage": true,
  "outputFormat": "segments",
  "includeMetadata": true,
  "maxVideosPerSource": 20,
  "maxTotalVideos": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 3,
  "maxRetries": 2
}
```

# Actor output Schema

## `transcripts` (type: `string`):

No description

## `allFields` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ],
    "languages": [
        "en"
    ],
    "maxVideosPerSource": 20,
    "maxTotalVideos": 1000,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zovo-one/tubetext-youtube-transcript-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    ],
    "languages": ["en"],
    "maxVideosPerSource": 20,
    "maxTotalVideos": 1000,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("zovo-one/tubetext-youtube-transcript-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "languages": [
    "en"
  ],
  "maxVideosPerSource": 20,
  "maxTotalVideos": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call zovo-one/tubetext-youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zovo-one/tubetext-youtube-transcript-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/K7U8sgbp3q7sOfvU8/builds/24H4K4oRLEeiPq3BV/openapi.json
