mem0ai/mem0 · warning · AuthError

Authentication failed. Your API key may be invalid or expire

Error message

Authentication failed. Your API key may be invalid or expired.

What it means

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.

Source

Thrown at integrations/openclaw/backend/platform.ts:59

    let url = `${this.baseUrl}${path}`;
    if (opts?.params) {
      const qs = new URLSearchParams(opts.params).toString();
      url += `?${qs}`;
    }

    const fetchOpts: RequestInit = {
      method,
      headers: this.headers,
      signal: AbortSignal.timeout(30_000),
    };
    if (opts?.json) {
      fetchOpts.body = JSON.stringify(opts.json);
    }

    const resp = await fetch(url, fetchOpts);

    if (resp.status === 401) {
      throw new AuthError();
    }
    if (resp.status === 404) {
      throw new NotFoundError(path);
    }
    if (resp.status === 400) {
      let detail: string;
      try {
        const body = (await resp.json()) as Record<string, unknown>;
        detail =
          ((body.detail ?? body.message ?? JSON.stringify(body)) as string) ??
          resp.statusText;
      } catch {
        detail = resp.statusText;
      }
      throw new APIError(path, detail);
    }
    if (!resp.ok) {
      let detail: string = resp.statusText;

View on GitHub (pinned to 001c235229)

Solutions

  1. 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).
  2. Disable the submit button / debounce the setup form to prevent double POSTs.
  3. In provisioning scripts, follow up any register failure with GET /auth/setup-status and login if needsSetup is false.

Example fix

# before
resp = register(name, email, password)
resp.raise_for_status()

# after
resp = register(name, email, password)
if resp.status_code == 403 and not setup_status()["needsSetup"]:
    resp = login(email, password)  # first writer won the race
else:
    resp.raise_for_status()
Defensive patterns

Strategy: try-catch

Validate before calling

status = requests.get(f"{base}/auth/setup-status").json()
if not status["needsSetup"]:
    skip_register = True  # someone may be registering concurrently; avoid the race entirely

Try / catch

try:
    resp = register(base, payload)
    if resp.status_code == 403 and "Registration is closed" in resp.text:
        resp = login(base, payload["email"], payload["password"])  # first writer won
    resp.raise_for_status()
except IntegrityRace:
    login(base, payload["email"], payload["password"])

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/77d2a40f1841e11b. Report an issue: GitHub.