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 identification
  • last_used_at — updated on every successful auth
  • is_active — revoke flag; inactive keys return 401

Using it

Dashboard

Go to /projects. From there you can:

  1. Create a new project
  2. Create an API key inside a project
  3. Copy the new key (shown once — won't be displayed again)
  4. 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)
bash

Tagging 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-slug
bash

...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_active to 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.