VicroCode
Make Code Create Value
VicroCode is a lightweight online platform for publishing, running, sharing, and monetizing code projects. Launch HTML, Python, SQLite, AI agents, management tools, games, and more without server setup.
Please wait while VicroCode loads. You can also explore the AI programming guide
Loading...

AI MARKET GUIDE

The quota ledger your AI-written fetch loop forgot to build

A YouTube Data API debrief, reframed: the fetch loop is easy, the unit accounting is the durable half. Build it as a hosted Python counter over a SQLite ledger.

A developer posted a clean little field report about building a YouTube comment tool, and buried in it is a lesson that has almost nothing to do with YouTube. They listed the quota math for the Data API v3: 10,000 units a day, resetting at Pacific midnight. `commentThreads.list` costs 1 unit and returns up to 100 top-level comments per page. `comments.list` costs 1 unit for replies. `search.list` costs 100 units a call. On paper that reads like abundance, 1 unit for 100 comments, a million comments a day in theory.

Then the catch: `search.list` has its own independent bucket, capped at 100 calls per day. So if you discover videos by keyword, your quota detonates there first, long before you get anywhere near the comment-pulling budget. The expensive bucket blows up before the cheap one even warms up. That's the sentence worth tattooing on the inside of your eyelids, because it generalizes to nearly every metered external API you'll ever wire up.

The pattern underneath the post

Look across what builders are actually griping about right now and it's all the same shape. One person measures a video model eating 50% of a five-hour subscription window. Another notes a new tool "doesn't seem to consume quota." Whole threads orbit account resets and when the weekly allowance refills. The metered bucket is the quiet governor on all of this work, and the accounting around it is where projects quietly bleed.

Here's the uncomfortable part. When you let an assistant scaffold the integration, it writes the fetch loop beautifully and fast. Pagination via `nextPageToken`, the happy-path retry, the JSON parsing, all there in seconds. What it does not write, because you didn't ask and the API docs don't force you to, is the ledger that tells you which bucket you're about to empty. That's the half you have to build on purpose. The speed of modern AI coding is exactly why the gap exists: the boring, durable accounting layer never shows up unless you treat it as a first-class requirement.

What the durable half looks like

Strip away the specific vendor and the job is this: before every outbound call, know which bucket it draws from, know that bucket's remaining headroom today, decide whether to make the call, and record what it actually cost. Four verbs. None of them live in the fetch loop.

The cleanest place to put it is a small hosted Python layer that sits between your code and the external API. You can run Python online as a wrapper endpoint, so instead of calling YouTube directly, your tool calls your own counter, which does the pre-flight check, forwards the request, and logs the result. The fetch logic stays simple; the policy lives in one place you control.

Back it with a SQLite ledger. One row per call, nothing clever:

CREATE TABLE call_log (
  id          INTEGER PRIMARY KEY,
  ts_utc      TEXT NOT NULL,
  quota_day   TEXT NOT NULL,   -- the Pacific-midnight day this counts against
  method      TEXT NOT NULL,   -- 'search.list', 'commentThreads.list', 'comments.list'
  bucket      TEXT NOT NULL,   -- 'search' or 'general'
  unit_cost   INTEGER NOT NULL,
  http_status INTEGER,
  note        TEXT
);

The two independent buckets are the whole point, so model them as independent. `search.list` draws down a `search` bucket you treat as a 100-call-a-day ceiling. Everything else draws down the `general` 10,000-unit pool. A pre-flight check is then just a sum against the right bucket for the current quota day:

COST = {"search.list": 100, "commentThreads.list": 1, "comments.list": 1}
BUCKET = {"search.list": "search", "commentThreads.list": "general", "comments.list": "general"}
LIMIT = {"search": 100, "general": 10000}  # search measured in calls, general in units

def may_call(conn, method, quota_day):
    bucket = BUCKET[method]
    unit = "COUNT(*)" if bucket == "search" else "COALESCE(SUM(unit_cost), 0)"
    used = conn.execute(
        f"SELECT {unit} FROM call_log WHERE quota_day=? AND bucket=?",
        (quota_day, bucket),
    ).fetchone()[0]
    need = 1 if bucket == "search" else COST[method]
    return used + need <= LIMIT[bucket], used

Now the expensive bucket can't ambush you, because you refuse the 101st `search.list` yourself instead of learning about it from a 403.

Stop filtering on the client

The same post flags a second trap that doubles as a quota saver. The instinct is to pull comments down and filter them in the frontend. But `commentThreads.list` takes a `searchTerms` parameter that searches the whole thread server-side, not just the page you've already loaded. Pushing the filter to the vendor means fewer pages fetched, which means fewer units spent. Fewer units is the actual goal, not just less bandwidth.

Two honest limits come with it, both from the field report. `searchTerms` can't be combined with the `id` parameter, only with `videoId` or `allThreadsRelatedToChannelId`. And it only matches top-level comment text, so anything buried in replies won't surface unless you go pull those separately through `comments.list`. Which feeds back into the ledger: a video's real request count is roughly the pages of top-level comments plus the number of comments that have replies, not the page count. If you budget by pages, you'll under-count every time.

One more detail that keeps the design small: reading public comments only needs an API key, not OAuth. Write operations need the full flow, but a read-only analysis tool skips a whole category of auth plumbing.

Where the honest boundary sits

Your SQLite ledger is an estimate, and you should say so out loud in your own head before a customer says it to you. The vendor's own counter stays the source of truth. A call that errors can still cost units. A retry you forgot to log drifts your number low. A clock-skew bug on the Pacific-midnight rollover smears calls across two quota days. So treat the ledger as a budget you enforce proactively, and reconcile against the vendor's reported usage when you can, rather than pretending your count is authoritative.

That reconciliation is where the inspectable store earns its keep. When your number and the vendor's number disagree, you open the ledger in a SQLite editor, group by method and quota day, and the drift tells you its own story, which bucket, which hour, which status codes. A ledger you can read by hand beats a counter you can only trust.

Two more boundaries worth stating plainly. The external quota rules themselves can change; the 100-units-and-its-own-bucket shape is what the evidence describes today, not a permanent contract, so keep the cost table in data, not hard-coded in the loop. And this pattern wraps an external API you supply the key for; VicroCode hosts the Python counter, the endpoint, and the ledger, but it does not provide the YouTube quota or stand in for the vendor's accounting. The original builder reached for Redis as the hot counter; here the counter is SQLite, which trades some concurrency headroom for a store you can open and audit directly. For a single-operator tool pacing one API key, that trade usually lands on the right side.

The reusable takeaway

Swap YouTube for any metered API and the recipe holds. Enumerate the buckets, find the one with the smallest ceiling relative to how you'll hit it, and build the counter around that bucket specifically. Keep the cost table as data. Log every call before you trust any total. Push filters to the server when the API lets you, because the cheapest call is the one you never make. The fetch loop was never the hard part. The ledger that stops the expensive bucket from blowing up first is the thing you'll still be glad you built six months from now.