ka
Khairul Azharsenior software engineer
All writing
TechnicalAug 16, 2026 · 4 min

Thirteen AI Providers, One Menu Bar, Zero Standards

I built a macOS menu bar app that tracks quota across 13 AI providers. There is no standard: OAuth next to bare session cookies, HTTP 200s that lie after a plan lapses, and quota bars that freeze for eighteen days looking perfectly healthy. Lessons from surviving all thirteen.

KA
Khai
Senior Software Engineer
Thirteen AI Providers, One Menu Bar, Zero Standards

I use a lot of AI subscriptions. Claude, ChatGPT, Copilot, Codex, DeepSeek, Grok, Kimi, GLM, MiniMax, OpenRouter, and a few more. Each one has a quota, each quota resets on its own schedule, and none of them will tell you about the others. So I built AIMeter, a macOS menu bar app that signs into all of them and shows every remaining quota in one place.

Thirteen providers later, I can report the finding: there is no standard. Not for auth, not for quotas, not for what an error even means. Every provider is its own small civilization with its own physics. Here is what surviving all thirteen taught me.


Auth is a spectrum, and the far end is “just log in again”

Some providers do proper OAuth with refresh tokens. Lovely. Others hand you a session cookie and nothing else: no refresh flow, no API, nothing. When that cookie dies, the only correct behavior is to ask the human to log in again. I spent days trying to engineer around that before accepting the truth: if the provider gives you no renewal path, re-login is not a bug in your app, it is a feature of theirs.

The rule that came out of it: classify every auth failure as transient or definitive, and only destroy stored credentials on a definitive signal. A 401 means the session is dead, wipe it and ask for login. A timeout or a 500 means nothing at all, keep the credentials and retry later. Wipe on the wrong one and you log your user out on every flaky network. Keep the wrong one and you show a corpse as a live session.


An HTTP 200 can still be a lie

My favorite specimen: a provider whose API, after a paid plan lapses, keeps returning 200 with a perfectly shaped response. Same fields, same structure. Except the plan type quietly says free and the four billing windows have collapsed into one. My app happily rendered quota windows that no longer existed, phantom limits from a subscription that was already gone.

Status codes tell you the request worked. They tell you nothing about whether the answer still means what your code assumes it means. Every response now goes through semantic checks, not just shape checks: does the plan match what we stored, do the windows make sense together, did a field that should move actually move.


The deadliest bug is data that stops moving

The worst defect class in the whole app was never a crash. It was staleness. A provider stops updating, silently, and the app keeps rendering the last known number with full confidence. A quota bar frozen at 62% for eighteen days looks exactly like a quota bar that is fine.

Two rules fixed the class. First, connected must mean “data is flowing,” never “data exists.” Deriving a live status from the mere presence of a cached blob is how you show a corpse as healthy; derive it from the age of the data instead, with a soft threshold that shows a stale marker and a hard threshold that stops rendering the number at all. Second, when a provider stalls, the diagnosis starts with one question: what is the timestamp on the last persisted blob. That single check separates “the provider stopped answering” from “we stopped asking” faster than any log dive.


What I would beg API designers to do

  • Expose quota as an endpoint, with a reset timestamp. If your users are rationing calls, they will build this anyway, worse, by scraping.

  • Make auth failures explicit. One error for “token expired, refresh it,” a different one for “session dead, human required.” Do not make clients guess from a generic 401.

  • When an account changes state, change the shape or version the response. A 200 that silently means something new is a trap you set for every client that ever cached your schema.

  • Timestamps on everything. The single most useful field in any response is “as of when.”

Thirteen providers, one menu bar, zero standards. The app is live at ai-meter.cc if you ration AI quotas across subscriptions like I do. The app works now, and the quota bars move, and when one of them freezes I find out from a stale marker instead of from eighteen days of confident fiction. In this economy, that counts as victory.

Next essay

Enable Dart/Flutter LSP in Claude Code for Lightning-Fast Code Navigation

Claude Code uses grep by default, slow and imprecise for large Flutter codebases. Enable LSP to connect it to Dart's built-in analysis server and get go-to-definition, find-references, and real-time diagnostics in milliseconds. No extra install needed, the analysis server ships with Flutter SDK.

ka

I design and ship resilient mobile platforms and the backends that keep them honest.

© 2026 · Privacy · v4.2.0 · commit 8a3f12c
Thirteen AI Providers, One Menu Bar, Zero Standards | Muhammad Khairul Azhar — Khairul (keroldev)