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

  1. Verify the API key value sent (header/query) matches a valid, non-revoked key in the Postgres api_keys table.
  2. Check the key's scopes in the database include the requiredScopes for the endpoint being called (e.g. write scope for POST routes).
  3. Regenerate a new API key with the needed scopes and update the client's env/config.
  4. 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

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

Related errors


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)