{"record":{"id":"77d2a40f1841e11b","repo":"mem0ai/mem0","slug":"authentication-failed-your-api-key-may-be-invalid-77d2a4","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":"warning","filePath":"integrations/openclaw/backend/platform.ts","lineNumber":59,"sourceCode":"    let url = `${this.baseUrl}${path}`;\n    if (opts?.params) {\n      const qs = new URLSearchParams(opts.params).toString();\n      url += `?${qs}`;\n    }\n\n    const fetchOpts: RequestInit = {\n      method,\n      headers: this.headers,\n      signal: AbortSignal.timeout(30_000),\n    };\n    if (opts?.json) {\n      fetchOpts.body = JSON.stringify(opts.json);\n    }\n\n    const resp = await fetch(url, fetchOpts);\n\n    if (resp.status === 401) {\n      throw new AuthError();\n    }\n    if (resp.status === 404) {\n      throw new NotFoundError(path);\n    }\n    if (resp.status === 400) {\n      let detail: string;\n      try {\n        const body = (await resp.json()) as Record<string, unknown>;\n        detail =\n          ((body.detail ?? body.message ?? JSON.stringify(body)) as string) ??\n          resp.statusText;\n      } catch {\n        detail = resp.statusText;\n      }\n      throw new APIError(path, detail);\n    }\n    if (!resp.ok) {\n      let detail: string = resp.statusText;","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/integrations/openclaw/backend/platform.ts#L41-L77","documentation":"The IntegrityError branch of POST /auth/register: two concurrent registrations can both pass the COUNT==0 check, and the loser hits a database uniqueness constraint (unique email / users not empty) at commit. The race window is small, but the handling is deliberate — the loser gets 403 'Registration is closed' rather than a 500, preserving the invariant that only one bootstrap admin exists.","triggerScenarios":"Two browsers or scripts POST /auth/register nearly simultaneously during initial setup; double-submit of the setup form (button double-click with slow network); parallel CI jobs bootstrapping the same fresh server.","commonSituations":"Setup page double-submission before the UI disables the button; automated provisioning where two workers race; retry after a timeout when the first request actually succeeded.","solutions":["Treat this 403 on a race as benign: the admin now exists — proceed to POST /auth/login with the credentials that won (or yours, if emails matched).","Disable the submit button / debounce the setup form to prevent double POSTs.","In provisioning scripts, follow up any register failure with GET /auth/setup-status and login if needsSetup is false."],"exampleFix":"# before\nresp = register(name, email, password)\nresp.raise_for_status()\n\n# after\nresp = register(name, email, password)\nif resp.status_code == 403 and not setup_status()[\"needsSetup\"]:\n    resp = login(email, password)  # first writer won the race\nelse:\n    resp.raise_for_status()","handlingStrategy":"try-catch","validationCode":"status = requests.get(f\"{base}/auth/setup-status\").json()\nif not status[\"needsSetup\"]:\n    skip_register = True  # someone may be registering concurrently; avoid the race entirely","typeGuard":null,"tryCatchPattern":"try:\n    resp = register(base, payload)\n    if resp.status_code == 403 and \"Registration is closed\" in resp.text:\n        resp = login(base, payload[\"email\"], payload[\"password\"])  # first writer won\n    resp.raise_for_status()\nexcept IntegrityRace:\n    login(base, payload[\"email\"], payload[\"password\"])","preventionTips":["Disable the setup submit button after first click.","Serialize first-run provisioning with a lock (one worker bootstraps).","Treat 403-after-register as 'initialized', not as an error requiring manual fixes."],"tags":["auth","bootstrap","http-403","race-condition","rest-server"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}