DEV Community

Cover image for 8 YouTube Data API Lessons From Building a vidIQ Alternative
Arindam Majumder
Arindam Majumder Subscriber

Posted on Edited on Originally published at arindamm.dev

8 YouTube Data API Lessons From Building a vidIQ Alternative

Since October I've been building Vid0, a YouTube research tool for small and mid-size channels.

It finds videos that are beating their channel's usual views, checks whether a topic is winnable at your channel's size, and puts those numbers on YouTube itself through a Chrome extension.

Almost all of it runs on the YouTube Data API.

I assumed the hard part would be the scoring. It wasn't.

The hard part was everything the docs mention once and never again: a quota shared by every user, numbers that are quietly rounded, a field that doesn't exist, and a policy that decides whether a score built from API data is allowed at all.

Here are the eight things I wish I'd known on day one.


1. search.list Is the Scarcest Thing You Have

Most read endpoints (videos.list, channels.list, playlistItems.list) cost 1 unit from a default budget of 10,000 units a day.

Search is different. It used to cost 100 units a call. Now it has its own bucket instead: 100 calls a day by default.

That's 100 searches for your whole Google Cloud project. Not per user. For everyone.

So the first rule is simple: never use search for a question the API can answer another way.

The classic mistake is listing a channel's videos with search.list and a channelId. Every channel already has an uploads playlist, and reading it costs 1 unit per 50 videos:

// A channel's latest 50 videos with stats: 3 units, zero searches.
const { data: ch } = await yt.channels.list({
  part: ["contentDetails"],
  id: [channelId],
});
const uploads = ch.items[0].contentDetails.relatedPlaylists.uploads;

const { data: page } = await yt.playlistItems.list({
  part: ["contentDetails"],
  playlistId: uploads,
  maxResults: 50,
});
const ids = page.items.map((item) => item.contentDetails.videoId);

const { data: videos } = await yt.videos.list({
  part: ["snippet", "statistics", "contentDetails"],
  id: ids,
});
Enter fullscreen mode Exit fullscreen mode

Vid0 only calls search when the question really is "what ranks for this keyword?", and it caches each keyword's results for seven days.


2. A Big View Count Tells You Nothing on Its Own

200,000 views is a flop on a channel that usually gets 2 million.

It's a breakout on a channel that usually gets 20,000.

So the number Vid0 is built around isn't views. It's an outlier score: a video's views divided by what that channel's videos usually have at the same age.

"Usually" takes some work:

  • Take the channel's recent uploads of the same format.
  • Rescale each one to this video's age with an age curve, because a 3-day-old video and a 3-month-old video aren't comparable.
  • Take the median.

A 2× score counts as an outlier. 5× is a breakout.

Those are the videos worth studying, because the channel's audience didn't change. The topic or the packaging did.

If you want to see it on real data, paste any channel into Vid0's free YouTube outlier finder. It doesn't need an account.


3. Use the Median. Always.

Averages are useless on YouTube.

One viral video drags the mean so far up that every other upload looks like a failure.

Every baseline in Vid0 is a median, including the ones you wouldn't expect. When it estimates how many views a keyword's results typically get, it ignores anything younger than 7 days (those videos are still in their launch spike) and takes the median of results up to 90 days old.

One viral result can't flip the answer.

If you're tempted to use a mean anywhere near view counts, don't.


4. The API Can't Tell You If a Video Is a Short

There's no isShort field. Not in videos.list, not anywhere in the Data API.

And you need to know. A Short with 50,000 views and a 20-minute video with 50,000 views are very different results. Mixing them breaks every baseline from lesson 2.

The workable heuristic is duration. Shorts can be up to 3 minutes for uploads since 15 October 2024, and up to 60 seconds before that:

const SHORTS_3_MIN_FROM = Date.parse("2024-10-15T00:00:00Z");

function looksLikeShort(durationSec: number, publishedAt: string) {
  const max = Date.parse(publishedAt) >= SHORTS_3_MIN_FROM ? 180 : 60;
  return durationSec <= max;
}
Enter fullscreen mode Exit fullscreen mode

It isn't perfect. A 2-minute horizontal video passes as a Short.

When the Chrome extension is on the page, it can also check the player's aspect ratio, which settles most of those cases.


5. Public Numbers Are Rounded, Hidden, and Sometimes Redefined

Three things that broke my charts.

Subscriber counts are rounded down to three significant figures. A channel with 4,236,118 subscribers shows 4,230,000, and it stays there until it crosses 4,240,000.

Vid0 refreshes public stats every six hours for the channels on its YouTube stats pages, and a big channel can sit on the same number for days. Growth from public data comes in steps, not curves.

Some channels hide the count entirely. hiddenSubscriberCount is true and there's no number. Your code needs a real "unknown", not a zero.

View counts can change meaning. Since 24 August 2026, YouTube counts a public view as soon as playback starts, for every format. Shorts already worked that way; long-form used to need some watch time first. Old views weren't recounted, so newer long-form videos look stronger than older ones next to them. Vid0 only compares a video with uploads from the same counting era.

For exact numbers you need the creator's own analytics, which means OAuth and the YouTube Analytics API. That's why Vid0 asks creators to connect their channel (read-only) to see their own stats.


6. Read the Developer Policies Before You Design Your Database

This one costs the most if you learn it late.

YouTube's Developer Policies don't allow metrics derived from API data by default. The policy's own example of what's not allowed is a score based on API data.

That describes most of what a YouTube analytics tool does.

The way through is the derived metrics policy. It allows custom scores, earnings estimates and leaderboards, and lets you keep statistics and derived metrics for up to 36 months. It's for developers who pass YouTube's compliance audit and opt in.

Even then, content is on a short leash. Titles, descriptions, thumbnails, channel names and comment text must be refreshed or deleted within 30 days.

I'd built several things that kept video titles indefinitely: saved ideas with the videos that inspired them, chat history with tool results, and view snapshots and thumbnail history inside the extension. Each one needed a 30-day expiry or a refresh job after the fact.

If your product depends on a score built from YouTube data, plan for the audit from day one. Google doesn't publish a turnaround time, so start it early, alongside OAuth verification.


7. Every User Shares One Quota

Quota belongs to your Google Cloud project, not to your users.

One heavy user or one badly written cron job can lock everyone out until the buckets reset at midnight Pacific.

So budget quota like money:

  • Per user. Each plan gets its own daily allowance. Anonymous visitors on the free tools share a small fixed pool (2,000 units a day in Vid0's case).
  • Per kind of work. A person waiting on a page always comes first. Scheduled refreshes and history backfills stop early, so they never eat what's left for people.
type Priority = "interactive" | "tracking" | "backfill";

// Share of the day's quota each kind of work has to leave untouched.
const KEEP_FREE: Record<Priority, number> = {
  interactive: 0, // someone is waiting
  tracking: 0.2, // scheduled refreshes stop with 20% left
  backfill: 0.4, // history backfill stops with 40% left
};

function canSpend(used: number, limit: number, cost: number, priority: Priority) {
  return used + cost <= limit * (1 - KEEP_FREE[priority]);
}
Enter fullscreen mode Exit fullscreen mode

And log every unit you spend, by bucket and by day. When you ask Google for more quota, the form wants your expected usage, and real numbers make a far better case than guesses. You can't rebuild them afterwards.


8. Put the Scoring in One Pure Package

Vid0 shows the same numbers in three places: the web app, the Chrome extension on youtube.com, and inside YouTube Studio.

If each of those computes a score its own way, sooner or later they disagree. Then users trust none of them.

So all of Vid0's scoring lives in one package of pure functions. No network calls, no database, no model.

The API imports it for server-side scoring. The Vid0 Chrome extension imports it and runs it on-device. The same function scores a video's packaging in YouTube Studio and on vid0.in, so the number can't disagree with itself.

Keeping the LLM out of the score helped too. AI title and thumbnail ratings exist, but they're separate and only run when someone asks. The score itself is deterministic, testable and free to compute.


What I'd Do Differently

If I started again, I'd do three things in week one:

  1. Write the quota budget before writing any feature.
  2. Read the developer policies and the derived metrics policy, then design the database around the 30-day rule.
  3. Start the compliance audit and OAuth verification on day one, because both take longer than the code.

Everything else on this list I learned by running into it.

If you make YouTube videos, Vid0 is free to start. And if you're building on the YouTube API yourself, I'd love to compare notes.

Top comments (0)