All guides

November 9, 2026 · 4 min read

How to Get YouTube Transcripts in Rust

In short

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 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 (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.

Common questions

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.

Try the YouTube Transcript API

Get transcripts as JSON, text, SRT, or VTT. 100 free credits, no card required.

More guides