Skip to content

AOSSIE-Org/BringYourOwnKey

Repository files navigation

AOSSIE Logo

Static Badge

Telegram Badge X Badge Discord Badge Medium Badge LinkedIn Badge Youtube Badge


BringYourOwnKey

BringYourOwnKey (BYOK) is a lightweight, framework-agnostic library that lets users supply their own LLM API keys directly in your app β€” no proxy, no extra backend service required. A browser-side widget collects and stores the key locally; a one-function backend helper reads it from request headers. Your existing frontend + backend architecture stays exactly as it is.

Browser (your frontend)                      Your FastAPI / Starlette backend
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  byok.getHeaders()       │──your fetch()──>β”‚  creds = extract_byok(req)   β”‚
β”‚  {                       β”‚                 β”‚  # creds.api_key             β”‚
β”‚    X-BYOK-Api-Key,       β”‚                 β”‚  # creds.provider            β”‚
β”‚    X-BYOK-Provider,      β”‚                 β”‚  # creds.model               β”‚
β”‚    X-BYOK-Model          β”‚                 β”‚                              β”‚
β”‚  }                       β”‚                 β”‚  client = Groq(creds.api_key)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   Key stored in localStorage only.            You create the LLM client.
   Library never makes requests for you.        Library never touches LLMs.

πŸš€ Features

  • Zero extra services β€” no LiteLLM proxy, no auth middleware backend. Drop it into any existing project.
  • You own the request β€” getHeaders() returns a plain object you spread into your own fetch(). The library never calls your API on your behalf.
  • Framework agnostic β€” frontend is a standard Web Component (<byok-settings>); works in React, Vue, Svelte, Next.js, or plain HTML. Backend is pure Starlette; works with FastAPI or bare Starlette.
  • Secure by design β€” keys live only in the user's browser (localStorage). They never touch your server's storage or logs.
  • Built-in provider presets β€” Groq (free tier), OpenAI, and Google Gemini ship out of the box, each with model lists and key validation.
  • Minimal surface β€” 4 Python exports, 6 TypeScript exports. Nothing else to learn.

πŸ’» Tech Stack

Frontend

  • TypeScript (Web Components / Custom Elements)
  • No framework dependency β€” works with React, Vue, Svelte, Next.js, or plain HTML

Backend

  • Python 3.9+
  • Starlette β‰₯ 0.27 (FastAPI compatible β€” FastAPI is built on Starlette)

βœ… Project Checklist

  • The AI/ML components:
    • LLM provider selection and model configuration are documented
    • API keys are managed client-side only β€” never stored server-side
    • Key format validation is implemented per provider before submission
    • Rate limit handling is delegated to the caller (you control the request)

πŸ”— Repository Links

  1. Main Repository
  2. Frontend source
  3. Python backend source

πŸ—οΈ Architecture Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         User's Browser                              β”‚
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    saves     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  <byok-settings>β”‚ ──────────> β”‚  localStorage                β”‚  β”‚
β”‚  β”‚  Web Component  β”‚             β”‚  { activeProvider, keys, ... }β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚           β–² opens                          β”‚ reads                  β”‚
β”‚           β”‚                               β–Ό                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Your App UI    β”‚            β”‚  byok.getHeaders()           β”‚   β”‚
β”‚  β”‚  (settings btn) β”‚            β”‚  β†’ X-BYOK-Api-Key            β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚  β†’ X-BYOK-Provider           β”‚   β”‚
β”‚                                 β”‚  β†’ X-BYOK-Model              β”‚   β”‚
β”‚                                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                 β”‚ spread into fetch()
                                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Your FastAPI / Starlette Backend               β”‚
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  @app.post("/api/chat")                                       β”‚  β”‚
β”‚  β”‚  async def chat(request: Request):                           β”‚  β”‚
β”‚  β”‚      creds = extract_byok(request)   ← reads the 3 headers  β”‚  β”‚
β”‚  β”‚      client = Groq(api_key=creds.api_key)                   β”‚  β”‚
β”‚  β”‚      # ... call LLM with user's own key                      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”„ User Flow

App loads
    β”‚
    β–Ό
byok.guardFirstRun()
    β”‚
    β”œβ”€ Key already saved? ──Yes──> App ready, proceed normally
    β”‚
    └─ No key yet
           β”‚
           β–Ό
    <byok-settings> modal opens (first-run banner shown)
           β”‚
           β”œβ”€ User picks provider + model + pastes key
           β”‚
           β”œβ”€ Key validated client-side (prefix + length check)
           β”‚
           └─ Saved to localStorage ──> modal closes ──> App ready
                                                              β”‚
                                                              β–Ό
                                                   User triggers an AI action
                                                              β”‚
                                                              β–Ό
                                                   byok.getHeaders() called
                                                              β”‚
                                                              β–Ό
                                                   fetch("/api/...", { headers: {...headers} })
                                                              β”‚
                                                              β–Ό
                                                   Backend: extract_byok(request)
                                                              β”‚
                                                              β–Ό
                                                   LLM called with user's own key

Key User Journeys

  1. First-time setup β€” User lands on the app, no key configured. guardFirstRun() auto-opens the settings modal. User picks Groq (free), pastes their key, clicks Save. App proceeds immediately β€” no page reload needed.

  2. Changing provider or model β€” User clicks the βš™οΈ settings button in your app. openSettings() opens the modal. User switches from Groq to Gemini, saves. All subsequent getHeaders() calls return the new provider's key automatically.

  3. Calling the backend β€” Your frontend calls byok.getHeaders(), spreads the three headers into any fetch() call. The backend calls extract_byok(request) to get a typed BYOKCredentials object and immediately creates the LLM client with the user's key.


πŸ€ Getting Started

Prerequisites

  • Frontend: Node.js 18+ and npm (only needed if using a bundler; plain HTML works without a build step)
  • Backend: Python 3.9+, pip

Backend Installation

# Minimal (Starlette only):
pip install -e .

# With FastAPI:
pip install -e ".[fastapi]"

# Development (pytest, uvicorn, fastapi):
pip install -e ".[dev]"

Option A β€” Per-route (recommended)

from fastapi import FastAPI, Request
from byok import extract_byok
from groq import Groq

app = FastAPI()

@app.post("/api/analyze")
async def analyze(request: Request, body: dict):
    creds = extract_byok(request)
    # creds.api_key  β†’ "gsk_..."
    # creds.provider β†’ "groq"
    # creds.model    β†’ "groq/llama-3.3-70b-versatile"

    client = Groq(api_key=creds.api_key)
    response = client.chat.completions.create(
        model=creds.model or "llama-3.3-70b-versatile",
        messages=[{"role": "user", "content": body["text"]}],
    )
    return {"result": response.choices[0].message.content}

Option B β€” Middleware (protects all routes at once)

from fastapi import FastAPI
from byok import BYOKMiddleware, get_byok

app = FastAPI()
app.add_middleware(BYOKMiddleware)

@app.post("/api/analyze")
async def analyze(body: dict):
    creds = get_byok()   # no request param needed
    ...

Frontend Installation

# Using npm / bundler:
cd frontend
npm install
npm run build       # outputs to frontend/dist/

# OR import directly in plain HTML (no build step):
# <script type="module">
#   import { createBYOK, PROVIDERS } from "./frontend/dist/byok.js";
# </script>

Step 1 β€” Create a BYOK instance

import { createBYOK, PROVIDERS } from "@byok-lib/frontend";

const byok = createBYOK({
  projectId: "my-app",
  providers: [PROVIDERS.groq, PROVIDERS.openai, PROVIDERS.gemini],
  accentColor: "#6366f1",
});

Step 2 β€” Guard first run

await byok.guardFirstRun();
// User now definitely has a key saved

Step 3 β€” Add a settings button

document.getElementById("settings-btn")!.addEventListener("click", () => {
  byok.openSettings({ onSave: (provider, key, model) => console.log("Saved") });
});

Step 4 β€” Pass headers in every API call

const res = await fetch("/api/analyze", {
  method: "POST",
  headers: { "Content-Type": "application/json", ...byok.getHeaders() },
  body: JSON.stringify({ text }),
});

πŸ“± App Screenshots

Settings modal β€” first-run state

BYOK settings modal β€” first run

Settings modal β€” returning user

BYOK settings modal β€” returning user


πŸ“¦ Available Providers & Models

Provider ID Key prefix Free tier Models
Groq PROVIDERS.groq gsk_ βœ… Yes Llama 3.3 70B, Llama 3.1 8B, Compound, Compound Mini, Qwen3 32B, Kimi K2, GPT OSS 120B/20B
OpenAI PROVIDERS.openai sk- ❌ No GPT-4o, GPT-4o Mini
Google Gemini PROVIDERS.gemini AIza βœ… Yes Gemini 2.5 Flash, Flash-Lite, Pro

πŸ“– API Reference

Python (from byok import ...)

Export Signature Description
extract_byok (request: Request) β†’ BYOKCredentials Reads the 3 BYOK headers. Raises HTTP 401 if key or provider is missing.
BYOKMiddleware app.add_middleware(BYOKMiddleware, skip_paths={...}) Auto-extracts headers on every request. Skips /, /health, /docs, /openapi.json, /redoc by default.
get_byok () β†’ BYOKCredentials Returns credentials stored by BYOKMiddleware. Raises HTTP 401 if called outside middleware context.
BYOKCredentials dataclass(api_key, provider, model?) Immutable, frozen credentials container.

TypeScript (from "@byok-lib/frontend")

Export Type Description
createBYOK(config) Function Creates a BYOK instance. Config accepts projectId, providers[], optional accentColor.
getHeaders() () β†’ BYOKHeaders | null Returns the 3 headers ready to spread. Returns null if no key is configured.
guardFirstRun() () β†’ Promise<boolean> Opens the modal if unconfigured. Resolves true if already set, false after the user saves.
openSettings(opts) ({ onSave?, onCancel? }) β†’ SettingsUI Opens the settings modal programmatically.
PROVIDERS Object Pre-built provider presets: PROVIDERS.groq, PROVIDERS.openai, PROVIDERS.gemini.
KeyManager Class Direct localStorage access. Use keyManager.clearAll() to reset a user's saved key.

πŸ™Œ Contributing

⭐ Don't forget to star this repository if you find it useful! ⭐

Thank you for considering contributing to this project! Contributions are highly appreciated and welcomed. To ensure smooth collaboration, please refer to our Contribution Guidelines.


✨ Maintainers


πŸ“ License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for details.


πŸ’ͺ Thanks To All Contributors

Thanks a lot for spending your time helping BringYourOwnKey grow. Keep rocking πŸ₯‚

Contributors

Β© 2026 AOSSIE

About

Component to let users input their own API keys

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors