{"record":{"id":"454f0f3657f4ec79","repo":"mem0ai/mem0","slug":"authentication-failed-your-api-key-may-be-invalid","errorCode":null,"errorMessage":"Authentication failed. Your API key may be invalid or expired.","messagePattern":"Authentication failed\\. Your API key may be invalid or expired\\.","errorType":"http","errorClass":"AuthError","httpStatus":401,"severity":"error","filePath":"cli/node/src/backend/platform.ts","lineNumber":68,"sourceCode":"\n\t\tconst headers = {\n\t\t\t...this.headers,\n\t\t\t\"X-Mem0-Caller-Type\": isAgentMode() ? \"agent\" : \"user\",\n\t\t};\n\n\t\tconst fetchOpts: RequestInit = {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tsignal: AbortSignal.timeout(30_000),\n\t\t};\n\t\tif (opts?.json) {\n\t\t\tfetchOpts.body = JSON.stringify(opts.json);\n\t\t}\n\n\t\tconst resp = await fetch(url, fetchOpts);\n\n\t\tif (resp.status === 401) {\n\t\t\tthrow new AuthError();\n\t\t}\n\t\tif (resp.status === 404) {\n\t\t\tthrow new NotFoundError(path);\n\t\t}\n\t\tif (resp.status === 400) {\n\t\t\tlet detail: string;\n\t\t\ttry {\n\t\t\t\tconst body = (await resp.json()) as Record<string, unknown>;\n\t\t\t\tdetail =\n\t\t\t\t\t((body.detail ?? body.message ?? JSON.stringify(body)) as string) ??\n\t\t\t\t\tresp.statusText;\n\t\t\t} catch {\n\t\t\t\tdetail = resp.statusText;\n\t\t\t}\n\t\t\tthrow new APIError(path, detail);\n\t\t}\n\t\tif (!resp.ok) {\n\t\t\tlet detail: string = resp.statusText;","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/cli/node/src/backend/platform.ts#L50-L86","documentation":"Thrown by the Mem0 REST server when an X-API-Key header was presented but no active, non-revoked API key row matches it. The server looks up candidates by key prefix, then verifies the full key against each stored hash; if none verify, the request is rejected with 401 before any DB user is resolved. Note that the presented key may exist but be revoked (revoked_at IS NULL filter), which yields the same error.","triggerScenarios":"Any request with an X-API-Key header whose value is wrong, truncated, revoked, or belongs to another deployment. Concretely: calling GET /memories with a key you just revoked via DELETE /api-keys/{id}; copying only the visible prefix instead of the full key returned at creation; using a key from a different server/database; sending the key when ADMIN_API_KEY is set but the value does not match it (it then falls through to the API-key table lookup).","commonSituations":"Key was revoked but the client cached it; the full key was only shown once at creation and was lost, so the client uses a placeholder; the server database was reset/redeployed so old keys no longer exist; a typo or whitespace/newline introduced when copying the key into an env var or CI secret.","solutions":["Generate a new API key via POST /api-keys while authenticated (Bearer token or ADMIN_API_KEY) and use the full returned key value — it is only shown once.","If the key should still be valid, check it was not revoked: inspect api_keys.revoked_at in the server DB or the key listing endpoint.","Verify the key belongs to this deployment's database (keys are per-instance, not global) and that ADMIN_API_KEY, if set, is being sent verbatim when you intend to use the admin path.","Strip whitespace/newlines from the header value in the client; confirm you send the raw key, not its prefix or a hashed form."],"exampleFix":"# before\nresponse = requests.get(\"http://server:3000/memories\", headers={\"X-API-Key\": key_prefix})\n\n# after\nresponse = requests.get(\"http://server:3000/memories\", headers={\"X-API-Key\": full_key.strip()})","handlingStrategy":"try-catch","validationCode":"import re\n\ndef is_plausible_api_key(key: str) -> bool:\n    # full keys are long; prefixes alone will never authenticate\n    return bool(key) and len(key.strip()) >= 20 and not key.startswith(\"Bearer \")","typeGuard":"def assert_valid_api_key(key: str | None) -> None:\n    assert isinstance(key, str) and len(key.strip()) >= 20, \\\n        \"expected full API key (not the prefix); generate one via POST /api-keys\"","tryCatchPattern":"resp = requests.get(url, headers={\"X-API-Key\": key})\nif resp.status_code == 401 and resp.json().get(\"detail\") == \"Invalid API key.\":\n    # regenerate the key via an authenticated admin session; do NOT blind-retry\n    raise CredentialsError(\"API key invalid or revoked; rotate it\")","preventionTips":["Persist the full key at creation time — it is displayed only once.","Store keys in a secret manager, not hard-coded, so rotation is a config change.","Monitor key revocations and remove them from client config in the same change."],"tags":["auth","api-key","http-401","rest-server"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}