Projects & API keys
A Spanlens organization contains one or more projects. Each project owns its own API keys, which are what you paste into your .env as SPANLENS_API_KEY. Projects let you separate dev/staging/prod, or per-service, or per-team — whatever scoping makes sense.
Why projects
Without projects, every request from every service you own shows up in one giant stream. That's fine for a solo side project; painful for anything beyond. Projects let you:
- Filter /requests by source service
- Compute cost per team for chargeback
- Apply different alert rules to prod vs staging
- Revoke one service's keys without affecting others
Why API keys (not JWT)
Traffic hitting /proxy/* uses API keys (not Supabase JWT) because those requests come from your backend servers — no user session, no cookie. API keys are:
- Long-lived (no expiry by default)
- Revocable (one button in the dashboard)
- Scoped to one project
- Hashed server-side (we never store the plaintext)
How it works
Key format
Keys are sl_live_ + 40 random base62 chars. At creation time the plaintext is shown once in the dashboard — copy it immediately. On the server we compute SHA-256 over the key and store only the hash in api_keys.key_hash.
Incoming proxy requests present Authorization: Bearer sl_live_.... The authApiKey middleware hashes the presented key and looks it up. No plaintext comparison, no plaintext storage.
Per-key metadata tracked
name— human label (e.g. “prod-backend”)key_prefix— first 10 chars, shown in UI for identificationlast_used_at— updated on every successful authis_active— revoke flag; inactive keys return 401
Using it
Dashboard
Go to /projects. From there you can:
- Create a new project
- Create an API key inside a project
- Copy the new key (shown once — won't be displayed again)
- Revoke any existing key
The page also shows a wizard hint: npx @spanlens/cli init will prompt for the key and wire up .env.local + SDK imports automatically.
API
# Projects
GET /api/v1/projects
POST /api/v1/projects { "name": "backend-prod" }
DELETE /api/v1/projects/:id
# API keys
GET /api/v1/api-keys
POST /api/v1/api-keys { "name": "ci-key", "projectId": "<uuid>" }
# → { "key": "sl_live_...", ...other metadata } — shown ONCE
DELETE /api/v1/api-keys/:id # revoke (sets is_active=false, preserves audit history)bashTagging requests with a project from client code
By default, a request's project is determined by which API key was used. One key = one project. If you want to override per-request, pass:
X-Spanlens-Project: my-project-slugbash...in the proxy request headers. The SDK also accepts a project option on createOpenAI() / createAnthropic() / createGemini().
Security design
- No recovery. Lose a plaintext key, create a new one. We can't retrieve it from the hash — that's the point.
- Revocation is instantaneous. Flipping
is_activeto false blocks all subsequent traffic. No key cache to invalidate. - Rate limits and quotas are enforced per-org, not per-key. A leaked key can be revoked without breaking your other keys, but while active it shares the org's quota. Rotate regularly for defense-in-depth.
Limitations
- No per-key rate limits yet. Enterprise ask — on roadmap.
- No automatic rotation. You create + revoke manually. Automated rotation API and a “rotate now” button are deferred to Phase 5.
- No IP allowlisting. Keys work from anywhere that presents them. Network-level allowlisting is an Enterprise request we're tracking.
- One active plaintext shown once. No “reveal existing key” escape hatch — by design. If you need CI to have access, regenerate and update the secret in your CI settings.
Related: Provider keys (AES-256-GCM), Quick start, /projects dashboard.