thedotmack/claude-mem · error
Forbidden
Forbidden
Error message
Invalid API key or insufficient scope
What it means
authenticatePostgresRequest returns 403 Forbidden when verifyPostgresApiKey fails, meaning either the presented Postgres API key does not exist/verify against the database, or it exists but lacks one of the scopes required for the request (options.requiredScopes). Authentication itself succeeded at the transport level; the key is the problem.
Solutions
- Verify the API key value sent (header/query) matches a valid, non-revoked key in the Postgres api_keys table.
- Check the key's scopes in the database include the requiredScopes for the endpoint being called (e.g. write scope for POST routes).
- Regenerate a new API key with the needed scopes and update the client's env/config.
- Confirm no whitespace, quotes, or truncation in the configured key string.
Example fix
// before curl -H 'Authorization: Bearer old-readonly-key' -X POST .../v1/events // after # issue a key with write scope, then curl -H 'Authorization: Bearer <new-key-with-write-scope>' -X POST .../v1/events
Defensive patterns
Strategy: validation
Validate before calling
function isValidApiKey(key) {
return typeof key === 'string' && key.trim().length > 0 && !/\s/.test(key.trim());
} Type guard
function hasApiKey(cfg): cfg is typeof cfg & { apiKey: string } {
return typeof (cfg as { apiKey?: unknown }).apiKey === 'string' && cfg.apiKey.length > 0;
} Try / catch
if (res.status === 403) {
const body = await res.json();
console.error('Auth rejected:', body.message, '— check API key and its scopes');
} Prevention
- Store the API key in a single env var and validate it non-empty at client startup.
- Track key scopes: use a write-scoped key for POST routes, read-scoped only for GETs.
- Rotate keys through a documented process and update all consumers at once.
- Trim and de-quote the key when loading from config files.
When it happens
Trigger: Any request to Postgres-backed routes where the raw key passed to verifyPostgresApiKey (pool, rawKey, requiredScopes) returns false: unknown key, revoked key, or a key whose stored scopes do not include a required scope (e.g. write endpoints requiring a write scope with a read-only key).
Common situations: Developer set CLAUDE_MEM/Postgres API key env var to a stale or placeholder value; key was rotated server-side; using a read-only ingestion key against a write route; copy-paste dropped a character or included quotes/whitespace.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- auth_invalid
- auth_invalid
- Forbidden
- Invalid API key: check your API key configuration in…
- missing_api_key
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/35ef40e2e27d2a2f.
Report an issue: GitHub.
Appendix: source
Thrown at src/server/middleware/postgres-auth.ts:100
apiKeyId: null,
mode: 'local-dev',
};
req.authContext = ctx;
next();
return;
}
if (!rawKey) {
res.status(401).json({
error: 'Unauthorized',
message: 'Missing API key (Authorization: Bearer <key> or X-Api-Key: <key>)',
});
return;
}
const verified = await verifyPostgresApiKey(pool, rawKey, options.requiredScopes ?? []);
if (!verified) {
res.status(403).json({ error: 'Forbidden', message: 'Invalid API key or insufficient scope' });
return;
}
const ctx: AuthContext = {
userId: null,
organizationId: null,
teamId: verified.teamId,
projectId: verified.projectId,
scopes: verified.scopes,
apiKeyId: verified.apiKeyId,
mode: 'api-key',
};
req.authContext = ctx;
next();
}
interface VerifiedPostgresApiKey {
apiKeyId: string;View on GitHub (pinned to d8bc9755e7)