Video transcription
One function that returns the spoken content of any social video (TikTok, Instagram, YouTube, Facebook, X, Reddit, or Rumble) for 10 credits per video.
You will build one transcribe(platform, url) function that returns the spoken content of any social video, regardless of which platform it lives on. Your application code stays platform-agnostic; a 20-line adapter absorbs the differences.
Cost per run: 3 credits for a YouTube video (standard tier), 10 credits per video on the other six platforms (premium tier). You call exactly one of these per video, because each platform only hosts its own content.
How do you get a transcript of a social media video?
Pass the video URL to that platform's transcript endpoint: GET /v1/tiktok/post/transcript, /v1/youtube/video/transcript, /v1/instagram/media/transcript, and four more. Each returns the spoken content as text.
Transcripts are the highest-density signal in social video. Captions lie and thumbnails bait, but the spoken word is what the creator actually said.
What you need
Seven transcript endpoints behind one auth model:
| Endpoint | Covers | Cost |
|---|---|---|
GET /v1/youtube/video/transcript | YouTube and Shorts | 3cr |
GET /v1/tiktok/post/transcript | TikTok video | 10cr |
GET /v1/instagram/media/transcript | Instagram reel or video | 10cr |
GET /v1/facebook/post/transcript | Facebook post or reel | 10cr |
GET /v1/twitter/tweet/transcript | X video tweet | 10cr |
GET /v1/reddit/post/transcript | Reddit video post | 10cr |
GET /v1/rumble/video/transcript | Rumble video | 10cr |
Every one of them takes a url param. The YouTube transcript is standard tier because it was re-sourced to a cheaper upstream; the other six are premium tier.
The response shapes differ
Transcript endpoints are upstream-pass-through: the canonical Transcript archetype keeps the upstream shape so you do not lose platform-specific fields. Three shapes are verified live:
- YouTube:
data.transcript[](array of{text, startMs, endMs, startTimeText}segments) +data.transcript_only_text(string) +data.language(string) - TikTok:
data.transcriptis a string (the full text directly) - Instagram:
data.transcripts[](note the plural) of{id, shortcode, text}
Facebook, X, Reddit, and Rumble follow the same upstream-pass-through pattern. The extractText() adapter below handles the variance.
The code
// recipe-transcribe.ts
// Returns the transcript of any social video URL.
// Run with: SOCIALCRAWL_KEY=sc_... npx tsx recipe-transcribe.ts
const KEY = process.env.SOCIALCRAWL_KEY;
if (!KEY) throw new Error("Set SOCIALCRAWL_KEY");
type Platform =
| "tiktok"
| "instagram"
| "youtube"
| "facebook"
| "twitter"
| "reddit"
| "rumble";
const ENDPOINT: Record<Platform, string> = {
tiktok: "tiktok/post/transcript",
instagram: "instagram/media/transcript",
youtube: "youtube/video/transcript",
facebook: "facebook/post/transcript",
twitter: "twitter/tweet/transcript",
reddit: "reddit/post/transcript",
rumble: "rumble/video/transcript",
};
// Adapter that normalises the per-platform shape into a single text string.
// Each branch reflects the upstream's actual response (verified live).
function extractText(
platform: Platform,
data: Record<string, unknown>,
): string {
if (platform === "youtube") {
return (data.transcript_only_text as string) ?? "";
}
if (platform === "tiktok") {
return (data.transcript as string) ?? "";
}
if (platform === "instagram") {
const t = data.transcripts as Array<{ text?: string }> | undefined;
return t?.[0]?.text ?? "";
}
// Facebook / Twitter / Reddit / Rumble: same upstream-pass-through pattern.
// Inspect the response on first call and extend this switch as needed.
return (
(data.transcript_only_text as string) ?? (data.transcript as string) ?? ""
);
}
async function transcribe(platform: Platform, videoUrl: string) {
const url = new URL(`https://www.socialcrawl.dev/v1/${ENDPOINT[platform]}`);
url.searchParams.set("url", videoUrl);
const res = await fetch(url, { headers: { "x-api-key": KEY! } });
const json = (await res.json()) as {
success: boolean;
data?: Record<string, unknown>;
credits_remaining: number | null;
error?: { message: string };
};
if (!json.success)
throw new Error(json.error?.message ?? "transcript failed");
return {
platform,
text: extractText(platform, json.data!),
credits_remaining: json.credits_remaining,
};
}
// Same input shape, same output shape, three different platforms.
const a = await transcribe(
"youtube",
"https://www.youtube.com/watch?v=erLbbextvlY",
);
const b = await transcribe(
"tiktok",
"https://www.tiktok.com/@mrbeast/video/7283145247503961371",
);
const c = await transcribe(
"rumble",
"https://rumble.com/v5o1eum-the-rubin-report-with-elon-musk.html",
);
for (const t of [a, b, c]) {
console.log(`[${t.platform}] ${t.text.length} chars`);
console.log(t.text.slice(0, 200), "…");
console.log("---");
}What you get back
// YouTube, segments + full string + ISO language code
{
"data": {
"transcript": [ // <-- array of segments
{ "text": "we are now stranded on this deserted", "startMs": "80", "endMs": "4000", "startTimeText": "0:00" }
// ... 620 more
],
"transcript_only_text": "we are now stranded on this deserted island...", // <-- concatenated string
"language": "en-US"
}
}
// TikTok, transcript is the string directly
{
"data": {
"id": "7647161577057258775",
"url": "https://www.tiktok.com/@...",
"transcript": "claude is shockingly good at refactors..." // <-- full text as string
}
}
// Instagram, note the PLURAL key
{
"data": {
"transcripts": [ // <-- plural, array
{ "id": "DY7SXBFtEpC", "shortcode": "DY7SXBFtEpC", "text": "..." }
]
}
}What to change
- The platform key: swap
youtubefor any of the seven keys inENDPOINT. The function signature does not change. - The video URLs: any public video URL on the matching platform.
- The adapter: the fallback branch covers Facebook, X, Reddit, and Rumble generically. Inspect the response on your first call for one of those and add an explicit branch if the upstream carries a field you want.
- Route by URL instead of by argument: parse the hostname to pick the platform key, and
transcribe()becomes a single-argument function.
Related
Hybrid search-then-enrich
Wire transcribe() to universal search and build a corpus.
Which endpoint should I use?
Why YouTube has two transcript options and the others have one.
Computed fields
The language-detection logic behind transcript.language.
TikTok API
Every TikTok endpoint, including the transcript route.
Quickstart
New here? Get a key and make your first request.
