# How to Get YouTube Transcripts in Rust

Source: https://transcript-yt.com/blog/youtube-transcript-api-rust · Updated 2026-11-09 · TranscriptYT

> Fetch YouTube transcripts in Rust with reqwest and serde: typed structs for the response, SRT and VTT downloads, translation, and error handling.

With reqwest and serde, one async GET to the TranscriptYT [YouTube Transcript API](https://transcript-yt.com/api) decodes straight into Rust structs: title, language, and timestamped segments for any public video, with no YouTube scraping in your binary.

## Dependencies

Create a free key in the [dashboard](https://transcript-yt.com/dashboard/keys) (100 transcripts, no card) and export it as TRANSCRIPTYT_KEY.

```
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
```

## Fetch and decode a transcript

```
use serde::Deserialize;
use std::time::Duration;

#[derive(Deserialize)]
struct Segment {
    start: f64,
    text: String,
}

#[derive(Deserialize)]
struct Transcript {
    title: Option<String>,
    language: String,
    source: String,
    segments: Vec<Segment>,
}

#[derive(Deserialize)]
struct ApiError {
    error: ErrorBody,
}

#[derive(Deserialize)]
struct ErrorBody {
    code: String,
    message: String,
}

async fn get_transcript(client: &reqwest::Client, video: &str) -> Result<Transcript, Box<dyn std::error::Error>> {
    let res = client
        .get("https://transcript-yt.com/v1/transcript")
        .query(&[("url", video)])
        .bearer_auth(std::env::var("TRANSCRIPTYT_KEY")?)
        .send()
        .await?;
    if !res.status().is_success() {
        let e: ApiError = res.json().await?;
        return Err(format!("{}: {}", e.error.code, e.error.message).into());
    }
    Ok(res.json().await?)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::builder().timeout(Duration::from_secs(120)).build()?;
    let t = get_transcript(&client, "https://youtu.be/5f3Pn-N6yLc").await?;
    println!("{} {} {}", t.title.unwrap_or_default(), t.language, t.source);
    for s in t.segments.iter().take(3) {
        println!("{:>7.1}s  {}", s.start, s.text);
    }
    Ok(())
}
```

## Subtitle files and translation

Add format=srt (or vtt, text, csv, md) and read the body as text. Add translate_to to translate into one of 150+ languages with the timings kept.

```
let srt = client
    .get("https://transcript-yt.com/v1/transcript")
    .query(&[("url", "5f3Pn-N6yLc"), ("format", "srt"), ("translate_to", "es")])
    .bearer_auth(std::env::var("TRANSCRIPTYT_KEY")?)
    .send()
    .await?
    .error_for_status()?
    .text()
    .await?;
std::fs::write("captions.es.srt", srt)?;
```

## Errors and retries

BLOCKED, RATE_LIMITED, and UPSTREAM_ERROR are temporary; retry them with backoff. VIDEO_UNAVAILABLE, NO_CAPTIONS, and LANGUAGE_NOT_AVAILABLE are final, and a 402 means you're out of credits. Failed requests are never billed. Every code is in the [docs](https://transcript-yt.com/docs).

## FAQ

### Is there a Rust crate for YouTube transcripts?

You don't need one. The API is a single GET request; reqwest sends it and serde decodes the JSON into your own structs.

### Can I use it without async?

Yes. Enable reqwest's blocking feature and use reqwest::blocking::Client with the same calls minus .await.

### How do I fetch many videos at once?

Run requests concurrently with futures::stream and buffer_unordered, staying under your plan's rate limit (200 a minute on paid plans), or use the batch endpoint.
