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
- 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.
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
- 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.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Timed out waiting for memory event ${eventId} to complete
- Bad request to ${path}: ${detail}
- HTTP ${resp.status}: ${detail}
- At least one entity ID is required for deleteEntities.
- Authentication failed. Your API key may be invalid or expire
AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15).
Data as JSON: /api/errors/77d2a40f1841e11b.
Report an issue: GitHub.