SocialCrawl

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:

EndpointCoversCost
GET /v1/youtube/video/transcriptYouTube and Shorts3cr
GET /v1/tiktok/post/transcriptTikTok video10cr
GET /v1/instagram/media/transcriptInstagram reel or video10cr
GET /v1/facebook/post/transcriptFacebook post or reel10cr
GET /v1/twitter/tweet/transcriptX video tweet10cr
GET /v1/reddit/post/transcriptReddit video post10cr
GET /v1/rumble/video/transcriptRumble video10cr

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.transcript is 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

TypeScript
// 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

Response
// 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 youtube for any of the seven keys in ENDPOINT. 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.

On this page