---
title: "API Key Gating & Registration — Deployment Guide"
description: "Desktop cloud fallback is gated on a WORLDMONITOR_API_KEY. Without a valid key, the desktop app operates local-only (sidecar). A registration form collects emails via Convex DB for future key distribution."
---
## Overview

Desktop cloud fallback is gated on a `WORLDMONITOR_API_KEY`. Without a valid key, the desktop app operates local-only (sidecar). A registration form collects emails via Convex DB for future key distribution.

## Architecture

```
Desktop App                          Cloud (Vercel)
┌──────────────────┐                ┌──────────────────────┐
│ fetch('/api/...')│                │ api/[domain]/v1/[rpc]│
│        │         │                │        │              │
│ ┌──────▼───────┐ │                │ ┌──────▼───────┐      │
│ │ sidecar try  │ │                │ │ validateApiKey│      │
│ │ (local-first)│ │                │ │ (origin-aware)│      │
│ └──────┬───────┘ │                │ └──────┬───────┘      │
│   fail │         │                │   401 if invalid      │
│ ┌──────▼───────┐ │   fallback    │                       │
│ │ WM key check │─┼──────────────►│ ┌──────────────┐      │
│ │ (gate)       │ │  +header      │ │ route handler │      │
│ └──────────────┘ │               │ └──────────────┘      │
└──────────────────┘               └──────────────────────┘
```

## Required Environment Variables

### Vercel

| Variable | Description | Example |
|----------|-------------|---------|
| `WORLDMONITOR_VALID_KEYS` | Comma-separated list of valid API keys | `wm_abc123def456,wm_xyz789` |
| `CONVEX_URL` | Convex deployment URL (from `npx convex deploy`) | `https://xyz-123.convex.cloud` |

### Generating API keys

Keys must be at least 16 characters (validated client-side). Recommended format:

```bash
# Generate a key
openssl rand -hex 24 | sed 's/^/wm_/'
# Example output: wm_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6
```

Add to `WORLDMONITOR_VALID_KEYS` in Vercel dashboard (comma-separated, no spaces).

## Convex Setup

### First-time deployment

```bash
# 1. Install (already in package.json)
npm install

# 2. Login to Convex
npx convex login

# 3. Initialize project (creates .env.local with CONVEX_URL)
npx convex init

# 4. Deploy schema and functions
npx convex deploy

# 5. Copy the deployment URL to Vercel env vars
# The URL is printed by `npx convex deploy` and saved in .env.local
```

### Verify Convex deployment

```bash
# Typecheck Convex functions
npx convex dev --typecheck

# Open Convex dashboard to see registrations
npx convex dashboard
```

### Schema

The `registrations` table stores:

| Field | Type | Description |
|-------|------|-------------|
| `email` | string | Original email (for display) |
| `normalizedEmail` | string | Lowercased email (for dedup) |
| `registeredAt` | number | Unix timestamp |
| `source` | string? | Where the registration came from |
| `appVersion` | string? | Desktop app version |

Indexed by `normalizedEmail` for duplicate detection.

## Security Model

### Client-side (desktop app)

- `installRuntimeFetchPatch()` checks `WORLDMONITOR_API_KEY` before allowing cloud fallback
- Key must be present AND valid (min 16 chars)
- `secretsReady` promise ensures secrets are loaded before first fetch (2s timeout)
- Fail-closed: any error in key check blocks cloud fallback

### Server-side (Vercel edge)

- `api/_api-key.js` validates `X-WorldMonitor-Key` header on sebuf routes
- **Origin-aware**: desktop origins (`tauri.localhost`, `tauri://`, `asset://`) require a key
- Web origins (`worldmonitor.app`) pass through without a key
- Non-desktop origin with key header: key is still validated
- Invalid key returns `401 { error: "Invalid API key" }`

### CORS

`X-WorldMonitor-Key` is allowed in both `server/cors.ts` and `api/_cors.js`.

### Local Vercel env dumps

Do not keep Vercel env exports in the repository root. `.env.vercel-backup`
and `.env.vercel-export` are ignored by Git, but they are still plaintext
production secret dumps that local tools, editor agents, backup software, or
dependency install scripts can read.

The pre-push hook fails when either file exists. Pull environment values only
when needed, work from a short-lived local env file, and delete the file after
use. Secret rotation and deletion from developer machines are operational
tasks; rotate exposed keys through the owning vendor dashboards, prioritizing
LLM, payment, auth, Redis, and Convex credentials.

## Verification Checklist

After deployment:

- [ ] Set `WORLDMONITOR_VALID_KEYS` in Vercel
- [ ] Set `CONVEX_URL` in Vercel
- [ ] Run `npx convex deploy` to push schema
- [ ] Desktop without key: cloud fallback blocked (console shows `cloud fallback blocked`)
- [ ] Desktop with invalid key: sebuf requests get `401`
- [ ] Desktop with valid key: cloud fallback works as before
- [ ] Web access: no key required, works normally
- [ ] Registration form: submit email, check Convex dashboard
- [ ] Duplicate email: shows "already registered"
- [ ] Existing settings tabs (LLMs, API Keys, Debug) unchanged

## Files Reference

| File | Role |
|------|------|
| `src/services/runtime.ts` | Client-side key gate + header attachment |
| `src/services/runtime-config.ts` | `WORLDMONITOR_API_KEY` type, validation, `secretsReady` |
| `api/_api-key.js` | Server-side key validation (origin-aware) |
| `api/[domain]/v1/[rpc].ts` | Sebuf gateway — calls `validateApiKey` |
| `api/register-interest.js` | Registration endpoint → Convex |
| `server/cors.ts` / `api/_cors.js` | CORS headers with `X-WorldMonitor-Key` |
| `src/components/WorldMonitorTab.ts` | Settings UI for key + registration |
| `convex/schema.ts` | Convex DB schema |
| `convex/registerInterest.ts` | Convex mutation |
