DEV Community

Shlok Madhekar
Shlok Madhekar

Posted on

I'm 15 and I built "OAuth for AI subscriptions". Here's the full architecture

Monet is a credential broker for AI. End users connect their existing ChatGPT Plus or Claude Pro account, or paste their own provider API key, and third-party apps route inference through their credential instead of paying for it themselves. The app never sees the credential. The user can revoke it anytime. I have been building it solo since June, it is live and free through the beta, and there is a demo app running the entire flow at demo.monet.gg.

This post is the architecture, plus the reasoning behind the three or four decisions that actually mattered.

The problem with "paste your key here"

BYOK (bring your own key) is usually implemented as a password input and a database column. That design has four separate problems:

  1. Trust. The user hands a live billing credential to an app they just met, with no visibility into how it is stored.
  2. Blast radius. If the app's database leaks, every user's provider account leaks with it.
  3. Revocation. The only undo is rotating the key at the provider, which breaks every other place the user pasted it.
  4. Attribution. The app cannot tell users what their usage cost, because it never sees provider-side accounting per user.

OAuth solved this exact shape of problem for account access years ago: delegate capability, never share the credential, revoke centrally. Monet applies that shape to AI credentials.

The flow, end to end

1. Consent. The app redirects the user to a hosted consent page. The user connects a provider account or pastes an API key, and approves linking it to that specific app. The page says plainly: usage counts against your subscription. Allow or deny.

2. Vault write. The credential is encrypted with AES-256-GCM into a vault row. The integrating app never receives it, at any point, in any form. The dashboard will show the user which apps hold connections, never the credential itself.

3. Token issue. The app completes a standard OAuth authorization-code exchange and gets back an opaque bearer token. Monet stores only a SHA-256 hash of that token server-side.

4. Inference. The app calls one OpenAI-compatible endpoint with that bearer:

curl https://monet.gg/api/v1/chat/completions \
  -H "Authorization: Bearer <monet_access_token>" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "hi"}]}'
Enter fullscreen mode Exit fullscreen mode

5. Proxy. Monet resolves the token hash to the right user's vaulted credential, decrypts it in-process solely to make the upstream call, streams the provider's response back, and writes a usage event with the token counts the provider itself reported.

6. Revocation. The user flips a switch, the vault row is cleared, and the token stops resolving at the proxy. Nothing to rotate, nothing else breaks.

Decision 1: opaque tokens, not JWTs

The bearer tokens carry zero information. This was deliberate.

A JWT is valid until it expires. If you need to kill one early, you build a denylist that every request checks, at which point you have rebuilt opaque tokens with extra steps and a bigger attack surface. Revocation is the single most important promise a credential broker makes, so the token had to be a pure database pointer: if the row is gone, the token is dead, mid-session, no grace period.

Hashing the stored side means a full dump of Monet's own token table contains nothing spendable. The same logic providers use for API keys, applied one layer up.

Decision 2: one OpenAI-compatible endpoint

Monet's entire integration surface is POST /api/v1/chat/completions. No SDK, no wrapper library.

Every AI SDK in every language already supports overriding base_url. That means adopting Monet is a two-line diff in most codebases:

client = OpenAI(
    base_url="https://monet.gg/api/v1",
    api_key=user_connection_token,
)
Enter fullscreen mode Exit fullscreen mode

I considered a nicer, Monet-specific API. Every hour spent on it would have been an hour spent making integration harder. The boring shape is the feature.

Decision 3: usage numbers come from upstream, never from an estimator

Each proxied request writes a usage event with prompt and completion token counts as reported by the provider, per user, per app.

The tempting shortcut is counting tokens client-side with a tokenizer. It drifts. Tokenizers version, providers bill for things client-side counting cannot see, and streaming makes people estimate. The moment a developer uses usage data for metering or cost passthrough, an estimate becomes a billing dispute. If the number is not the provider's number, it is wrong in a way that costs someone money, so Monet only records what upstream actually said.

Decision 4: the app is untrusted by design

The security model assumes the integrating app will get breached eventually, because some of them will. So the design goal was: a fully compromised app leaks no credentials.

The app's database holds an opaque token. Stolen, it can spend inference on connected users until they revoke, which is one click and immediate. It cannot read the key, exfiltrate the credential to use elsewhere, or survive revocation. Compare that to plaintext BYOK, where the same breach hands over every user's actual provider key.

That is also why the consent page is hosted by Monet rather than embedded in the app: credentials are typed into exactly one origin, ever.

Where it stands

Live, free through the beta, a few dozen developers on it. Chat completions with streaming works across connected GPT and Claude accounts and raw provider keys. The near-term list is more provider surface and framework presets, informed by whoever shows up with a real integration.

I'm 15, this is my main project, and the fastest way to make it better is people poking holes in the design above. If you maintain something with a BYOK issue open in its tracker, I would genuinely like to read it.

Dashboard: monet.gg ยท Demo: demo.monet.gg

Related: How to let users bring their own OpenAI or Anthropic API keys (without storing them in plaintext) covers the build-it-yourself checklist, and Stop paying for your users' AI usage covers why the economics force this design.


I'm Shlok. I read every reply: shlokmadhekar88@gmail.com, github.com/shlok-madhekar, @shlokbuilds.

Top comments (1)

Collapse
 
devjp profile image
Justin

This is well presented - your website is very nice too. At your age, this is a solid project, and you've approached this well - good paradigms and perspective, and you take security seriously.

Now, drilling into some tension points, you mention a couple dozen devs so I think you can handle the workload. I'd like pass on the following:

  • Model your end-users (wedges); most customers turn to APIs for their scalability. Subscription-based would have it's niches - cut into these, when you mention SaaS users, I'd narrow that to internal usage/agents.
  • Access/audit trails are important since you're implicitly federating platforms.
  • Discuss your routing model. Is it a pool of subscriptions? Does if failover if the subscription is exhausted?
  • You mention holding an opaque token in the DB to protect against severe breaches - I'm curious where is the revocation triggered from?
  • This smells like an open-source project, yet appears to be a SaaS, I'd suggest settling some truly unique features which push users to you.

Stay frosty!