HomeBlog

YouTube Transcript in PHP: cURL on Cheap Shared Hosting

PHP script on shared hosting fetching a YouTube transcript via cURL

I tested this on the cheapest shared host I could find: PHP 8.1, no Composer, no shell access, files uploaded over FTP like it's 2009. It works, because fetching a YouTube transcript in PHP needs exactly two things every host has shipped for twenty years — the curl extension and json_decode. By the end of this post you'll have a single file that takes a YouTube URL and prints the title and transcript, with sane branches for every error the API can return.

That shared-hosting angle matters more than it sounds. Scraper libraries fetch captions from YouTube directly, and shared hosting IPs are among the first ranges YouTube blocks — you're sharing an IP with hundreds of other sites, some of them badly behaved. The full mess is documented in why transcript scrapers get blocked. Calling a hosted API dodges all of it: your host only ever talks to youtube2text.org.

The script

<?php
$videoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
$endpoint = "https://youtube2text.org/api/transcribe?url=" . urlencode($videoUrl);

$ch = curl_init($endpoint);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ["x-api-key: " . getenv("YT2TEXT_KEY")],
    CURLOPT_TIMEOUT        => 60,
]);
$raw    = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($raw === false) {
    exit("Network error\n");
}

$data = json_decode($raw, true);
if ($data === null) {
    exit("Response was not JSON (status $status)\n");
}

if ($status === 200) {
    echo $data["result"]["title"], "\n";
    echo $data["result"]["contentSize"], " characters\n";
    echo substr($data["result"]["content"], 0, 500), "\n";
} else {
    $err = $data["error"];
    if ($err["code"] === "RATE_LIMIT_EXCEEDED") {
        echo "Rate limited. Retry in ", $err["retryAfterSeconds"], " seconds.\n";
    } elseif ($err["code"] === "TRANSCRIPT_UNAVAILABLE") {
        echo "This video has no captions. Nothing to fetch.\n";
    } else {
        echo $err["code"], ": ", $err["message"], "\n";
    }
}

Set YT2TEXT_KEY in your host's control panel, or hardcode it if you must — just keep the file out of your repo if you do. The url parameter takes any YouTube URL format or a bare 11-character video id, and there's an optional maxChars parameter (default and max 150000) for capping output.

The json_decode guard is not optional

That $data === null check earns its keep on shared hosting specifically. When a host's proxy times out or a mod_security rule fires, you get an HTML error page, not JSON — and without the guard your script fatals on an array access three lines later with a useless message. Check the decode before touching the shape.

Getting a key without an account

$demo = json_decode(
    file_get_contents("https://youtube2text.org/api/demo-key"),
    true
);
echo $demo["apiKey"]; // yt_...

Honest fine print: the demo key is shared among everyone who requests it and allows 5 videos per month per IP. On shared hosting that IP is also shared with your server neighbors, so treat the demo key as a five-minute test, not a plan.

Also worth knowing: transcripts come from YouTube's caption tracks, manual or auto-generated. English is preferred, first available language otherwise — and a video with captions disabled returns TRANSCRIPT_UNAVAILABLE, permanently. Build your loop to skip those.

Where PHP folk take this

The most common request I get from PHP developers is WordPress-shaped: pull a transcript, hand it to an LLM, publish a draft. That whole pipeline is covered in turning a YouTube video into a blog post, and the podcast variant in generating show notes from YouTube.

When the demo key stops being enough, sign in with Google at youtube2text.org/app/keys — no card and no phone number, a free 5-videos-a-month key of your own, and paid tiers from $5.99. The script above stays byte-for-byte identical.