Skip to content

Instantly share code, notes, and snippets.

@mehd-io
Last active April 23, 2026 15:29
Show Gist options
  • Select an option

  • Save mehd-io/a82a37ba5b90c3541430ee617e39f708 to your computer and use it in GitHub Desktop.

Select an option

Save mehd-io/a82a37ba5b90c3541430ee617e39f708 to your computer and use it in GitHub Desktop.
Build an LLM Key Minter via OpenRouter

Build: workshop LLM-key minter

Goal

A small web app that lets workshop attendees sign in and claim a personal, budget-capped, model-restricted LLM API key in under a minute. One YAML file per workshop. Admin page for visibility. Auto-expiring keys. No cron.

Required reading before you write code

Read this blog post end-to-end — it explains the why for most design choices below and the gotchas that will otherwise burn your afternoon: https://blog.mehdio.com/p/running-an-ai-workshop-build-a-llm?r=1g8h3p&utm_campaign=post&utm_medium=web&triedRedirect=true

Especially internalise:

  • OpenRouter Management API = mint sub-keys with limit + expires_at
  • Model allowlist is NOT a per-key setting — it lives on a Guardrail
  • POST /keys silently ignores guardrail_ids; you MUST call POST /guardrails/{id}/assignments/keys separately
  • Store the minted key plaintext in the DB — OpenRouter shows it once, and blast radius is bounded by budget + expiry + guardrail
  • Save usage to the DB periodically; live-pulling is inconsistent between OpenRouter's UI and API endpoints

Decisions I'm leaving to you (pick one and justify in the README)

  • Auth provider: Auth0, Clerk, Supabase Auth, WorkOS, NextAuth + plain OIDC, etc. Must support Google + GitHub and expose a stable user id + verified email.
  • Database: Postgres anywhere (Supabase, Neon, PlanetScale, RDS, local). SQLite is fine for dev. The schema is trivial (2 tables).
  • Hosting: Vercel, Fly, Railway, Cloudflare Workers — whatever matches your stack.
  • Framework: Next.js App Router is the blog's reference, but SvelteKit, Remix, or Nuxt are all fine. Server-side route/action for the mint call is non-negotiable (the management key never touches the browser).

Non-negotiable design (don't re-litigate these)

  1. Workshop config = YAML file in the repo (workshops/<slug>.yaml). PR + merge + deploy = new workshop. No admin CRUD.
  2. Three-call mint flow on the server: ensureGuardrailcreateKeyassignKeysToGuardrail. If call 3 fails, surface it in the admin page as "unbound" with a retry button — don't fail the user-facing flow.
  3. One Guardrail per workshop, named workshop-<slug>. Fields: allowed_models (from YAML), limit_usd (aggregate workshop cap), monthly reset.
  4. Claim window enforced server-side via claims_open_at / claims_close_at in the YAML. No cron to toggle.
  5. Schema (adapt names/types to your DB; keep the shape):
    attendees(user_id PK, email, email_normalized, display_name, auth_provider,
              marketing_opted_in, marketing_opted_in_at, marketing_opted_in_workshop,
              first_seen_at, last_seen_at)
    workshop_attendance(user_id, workshop_slug, or_key_hash, or_key_plaintext,
                        key_label, limit_usd, expires_at, or_guardrail_id,
                        guardrail_attached_at, usage_usd, usage_synced_at, attended_at,
                        PK (user_id, workshop_slug))
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment