Authentication
NullSpend uses two authentication modes — API key for proxy and SDK requests, session cookies for the dashboard. Includes key lifecycle, caching, and validation details.
NullSpend uses two authentication modes:
- API key — For the proxy and SDK. Pass
X-NullSpend-Keyin the request header. - Session — For the dashboard. Uses a browser session cookie.
For the full endpoint auth breakdown, see the API Overview.
API Key Authentication
Header: X-NullSpend-Key
Key format: ns_live_sk_ + 32 hex characters (43 characters total). See Custom Headers for validation rules and examples.
Key Lifecycle
Creating Keys
Create keys in the dashboard under Settings → API Keys, or programmatically via POST /api/keys (see API Keys API).
The raw key is shown once at creation — store it immediately. Free tier: 10 keys per organization. Pro and above: unlimited.
Key-Level Settings
Each key can have:
- API version — Overrides the default version. Can be overridden per-request by the
NullSpend-Versionheader. See Versioning. - Name — Human-readable label (1–50 characters).
- Default tags — Tags automatically applied to every request made with this key.
Revoking Keys
Revoke keys in the dashboard or via DELETE /api/keys/:id. Revocation is a soft delete (revoked_at timestamp).
Propagation: near-instant when revoked through the dashboard or DELETE /api/keys/:id — the API fires an invalidateProxyCache hook that flushes the proxy's positive cache entry across all isolates. If a key is revoked out-of-band (e.g., direct database update), propagation can take up to 120 seconds while the positive-cache TTL expires.
How Key Validation Works
When a request arrives at the proxy:
- Read the
x-nullspend-keyheader - SHA-256 hash the raw key
- Check the positive cache (256 entries, 120s TTL with ±10s jitter to prevent thundering herd on isolate recycle) — if found and not expired, return the cached identity
- Check the negative cache (2,048 entries, 30s TTL) — if found and not expired, reject immediately
- If both miss, query the database:
WHERE key_hash = $1 AND revoked_at IS NULL - The DB query also loads webhooks state, default tags, allowed models/providers, plan-tier limits, and the subscription billing period
- On success: populate the positive cache. On "not found": populate the negative cache. On DB error: throw (caller returns 503 — never negative-cached)
Dashboard mutations (PATCH /api/keys/:id, DELETE /api/keys/:id) fire an invalidateProxyCache hook so the next request hits a fresh DB lookup.
Timing-safe comparison prevents timing attacks. Database connection timeout: 5,000ms.
Session Authentication
The dashboard uses browser session cookies from Supabase Auth. Session-authenticated endpoints power the dashboard UI and are not callable from external scripts.
For the full list of which endpoints use session vs API key auth, see the API Overview.
Error Responses
| Scenario | HTTP | Code |
|---|---|---|
Missing X-NullSpend-Key header | 401 | unauthorized |
| Malformed or invalid key | 401 | unauthorized |
| Revoked key (after cache expiry) | 401 | unauthorized |
See Errors for the full error format.
Related
- API Overview — endpoint auth summary
- Custom Headers — header format and validation
- API Keys API — create, list, and revoke keys
- Errors — error codes and response format