Live demo — shared accounts, sample data, reset regularly. Don't enter anything real.Get the source

Public API & API Keys

Public API & API Keys

What it does

A versioned /api/v1 route group, authenticated via a Bearer API key instead of the session cookie every other route uses. Users create/revoke their own keys at /settings/api-keys; each key is scoped to that user (not an organization) and per-key rate-limited by better-auth's own @better-auth/api-key plugin.

Two routes ship as the worked example:

  • GET /api/v1/me — the authenticated user's profile.
  • GET /api/v1/organizations — the organizations they're a member of, with their role in each.

How to configure it

Nothing to configure — the plugin is always on, no env var gate (unlike most optional integrations in this starter, there's no external service here to be unconfigured; the DB is the storage). lib/auth.ts's apiKey() call sets a launchpad_ key prefix and a default per-key rate limit of 120 requests/hour, both adjustable there.

How it works

  • lib/auth.ts's apiKey() plugin call (@better-auth/api-key — a separate package from better-auth itself; this Next version's environment ships it split out, see AGENTS.md). references: "user" (the default) scopes every key to a single user — no org-scoped keys.
  • lib/db/schema/auth.ts's apikey table — hand-written to match the plugin's expected schema exactly, same @better-auth/cli generate can't introspect lib/auth.ts limitation as every other plugin table in this starter (see organizations.md). key stores the hashed value; the plaintext is only ever returned once, at creation.
  • app/(app)/settings/api-keys/page.tsx + components/settings/ api-keys-manager.tsx — lists the caller's own keys (auth.api.listApiKeys, session-scoped automatically via the plugin's own sessionMiddleware) and lets them create (authClient.apiKey.create) / revoke (authClient.apiKey.delete) directly from the client, same pattern as components/settings/linked-accounts.tsx — no server action needed, the plugin's own client methods are the RPC layer.
  • lib/api-auth.ts's verifyApiRequest(request) — the shared guard for app/api/v1/* route handlers. Extracts the Bearer token from Authorization, calls auth.api.verifyApiKey({ body: { key } }) directly rather than relying on the plugin's own request-mocking hook (enableSessionForAPIKeys, off by default and explicitly "not recommended for production" per the plugin itself) — that hook only intercepts requests that go through better-auth's own dispatcher (auth.api.* calls, the /api/auth/[...all] catch-all), not arbitrary custom route handlers like these. Maps a disabled/unknown/expired key to 401, the plugin's own per-key rate limit being exceeded to 429.
  • app/api/v1/me/route.ts / app/api/v1/organizations/route.ts — call verifyApiRequest, then a plain Drizzle query scoped to auth.userId. These two are the worked examples to copy: me for "something about the caller", organizations for "a list of resources the caller owns".

How to extend it

  • A new /api/v1/* route — call verifyApiRequest(request) first, return its error/status on !ok, otherwise use auth.userId to scope your query. Same shape as the two existing routes.
  • Org-scoped keys instead of user-scoped — pass references: "organization" to apiKey() in lib/auth.ts; you'll also need organizationId on createApiKey calls and to update components/settings/api-keys-manager.tsx's create form.
  • Different rate limits per key — createApiKey's body accepts rateLimitMax/rateLimitTimeWindow per-key (server-only fields); expose them in the create form if you want users to choose.
  • Permissions/scopes — the plugin supports a permissions field per key and defaultPermissions in apiKey()'s config; unused here (every key can hit every /api/v1/* route) but available if you need finer-grained access.

How to remove it

Drop the apiKey() plugin and its import from lib/auth.ts, apiKeyClient() from lib/auth-client.ts, the apikey table from lib/db/schema/auth.ts (regenerate a migration), lib/api-auth.ts, app/api/v1/, app/(app)/settings/api-keys/, components/settings/api-keys-manager.tsx, and the "API keys" sidebar link in app/(app)/layout.tsx.