# SDKs



YTAPI has official clients for Python and for JavaScript/TypeScript. Both are open source under the MIT license and have no runtime dependencies. They wrap the same HTTP API documented here, so anything you can do with `curl` you can do with them.

|         | Python                                                                 | JavaScript / TypeScript                                           |
| :------ | :--------------------------------------------------------------------- | :---------------------------------------------------------------- |
| Package | [`ytapi-sdk`](https://pypi.org/project/ytapi-sdk/) on PyPI             | [`@ytapi/sdk`](https://www.npmjs.com/package/@ytapi/sdk) on npm   |
| Install | `pip install ytapi-sdk`                                                | `npm install @ytapi/sdk`                                          |
| Import  | `from ytapi import YTAPI`                                              | `import { YTAPI } from "@ytapi/sdk"`                              |
| Runtime | Python 3.10+                                                           | Node 18+, plus Bun, Deno and Cloudflare Workers; ESM and CommonJS |
| Source  | [github.com/ytapi/ytapi-python](https://github.com/ytapi/ytapi-python) | [github.com/ytapi/ytapi-js](https://github.com/ytapi/ytapi-js)    |

Quickstart [#quickstart]

Create a key in the [Developer Dashboard](https://ytapi.dev/app/api-keys?utm_source=docs) and set it as `YTAPI_API_KEY`. Both clients read it from the environment, or you can pass it in.

<Tabs items={["Python", "TypeScript"]}>
  <Tab value="Python">
    ```python
    from ytapi import YTAPI

    api = YTAPI()  # or YTAPI(api_key="sk_...")

    transcript = api.get_transcript("dQw4w9WgXcQ")
    for segment in transcript["segments"][:3]:
        print(segment["start"], segment["text"])

    # Text formats (markdown, text, srt, vtt) come back as a string.
    print(api.get_transcript("dQw4w9WgXcQ", format="markdown"))
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    import { YTAPI } from "@ytapi/sdk";

    const api = new YTAPI(); // or new YTAPI({ apiKey: "sk_..." })

    const transcript = await api.getTranscript("dQw4w9WgXcQ");
    if (typeof transcript !== "string") {
      for (const segment of transcript.segments?.slice(0, 3) ?? []) {
        console.log(segment.start, segment.text);
      }
    }

    // Text formats (markdown, text, srt, vtt) come back as a string.
    console.log(await api.getTranscript("dQw4w9WgXcQ", { format: "markdown" }));
    ```
  </Tab>
</Tabs>

<Callout type="warn" title="Server-side only">
  Use the JavaScript client from your server, a serverless function or a worker. An API key in browser code is visible to every visitor.
</Callout>

Methods [#methods]

Each method maps to one endpoint and costs what that endpoint costs (see [Credits](/credits)).

| Python                                             | TypeScript                                     | Endpoint                                                           |
| :------------------------------------------------- | :--------------------------------------------- | :----------------------------------------------------------------- |
| `get_transcript`                                   | `getTranscript`                                | [`POST /v1/transcripts`](/transcripts/extract)                     |
| `get_basic_info`                                   | `getBasicInfo`                                 | [`GET /v1/videos/{id}/basic-info`](/videos/basic-info)             |
| `get_video_info`                                   | `getVideoInfo`                                 | [`GET /v1/videos/{id}/video-info`](/videos/video-info)             |
| `get_playlist`, `iter_playlist_videos`             | `getPlaylist`, `iterPlaylistVideos`            | [`GET /v1/playlists/{id}`](/playlists/get)                         |
| `get_channel`                                      | `getChannel`                                   | [`GET /v1/channels/{id}`](/channels/get)                           |
| `get_channel_latest`                               | `getChannelLatest`                             | [`GET /v1/channels/{id}/latest`](/channels/latest)                 |
| `list_channel_videos`, `iter_channel_videos`       | `listChannelVideos`, `iterChannelVideos`       | [`GET /v1/channels/{id}/videos`](/channels/videos)                 |
| `list_channel_playlists`, `iter_channel_playlists` | `listChannelPlaylists`, `iterChannelPlaylists` | [`GET /v1/channels/{id}/playlists`](/channels/playlists)           |
| `search`, `iter_search`                            | `search`, `iterSearch`                         | [`GET /v1/search`](/search/query)                                  |
| `get_suggestions`                                  | `getSuggestions`                               | [`GET /v1/search/suggestions`](/search/suggestions)                |
| `create_batch`, `get_batch`, `poll_batch`          | `createBatch`, `getBatch`, `pollBatch`         | [`POST /v1/batch`](/batch/run), [`GET /v1/batch/{id}`](/batch/get) |

The `iter_*` / `iter*` methods follow `next_cursor` for you. Each page they fetch is one request and one credit, so stop iterating once you have what you need.

Errors [#errors]

A non-2xx response raises (Python) or throws (TypeScript) a typed error that carries the `status`, the error `code` from the [error envelope](/credits#error-response-format) and, for 429s, `retry_after` / `retryAfter`.

| Status                   | Error class                                                             |
| :----------------------- | :---------------------------------------------------------------------- |
| 401                      | `AuthError`                                                             |
| 402                      | `InsufficientCreditsError`                                              |
| 404                      | `NotFoundError`, for example `captions_disabled` or `video_unavailable` |
| 429                      | `RateLimitedError`: `rate_limited` or `daily_limit_exceeded`            |
| 5xx                      | `ServerError`                                                           |
| Network error or timeout | `YTAPIError` with `status` 0                                            |

All of them extend `YTAPIError`.

Retries [#retries]

By default the clients retry a 429, a 5xx or a network error twice, backing off from 0.5 seconds up to 8 seconds and honoring `Retry-After`. They do not retry:

* A 429 that asks for a wait longer than 60 seconds, such as the free tier's daily limit, which resets at 00:00 UTC. The error is raised at once so your program doesn't hang. Change the cutoff with `max_retry_wait` (Python, seconds) or `maxRetryWaitMs` (TypeScript).
* Creating a batch after a 5xx or a network error, because the job may already exist.

Set `max_retries=0` / `maxRetries: 0` to turn retries off.

Other languages [#other-languages]

For Go, Ruby, PHP or anything else, call the API over HTTPS as shown in the [Quickstart](/). The [OpenAPI spec](https://ytapi.dev/openapi.json) lists the endpoints and schemas.
