mem0ai/mem0 · warning · NodeOperationError

Add requires at least one of User ID, Agent ID, Run ID, or A

Error message

Add requires at least one of User ID, Agent ID, Run ID, or App ID

What it means

POST /memories (add_memory) requires the new memory to be attributable to at least one scope: user_id, agent_id, or run_id. The MemoryCreate body may legitimately omit several fields, but if all three identifiers are empty/None the server rejects with 400 before calling memory.add, because unscoped memories cannot be retrieved or filtered later.

Source

Thrown at integrations/n8n-nodes-mem0/nodes/Mem0/Mem0.node.ts:456

							body.custom_categories =
								typeof addFields.custom_categories === 'string'
									? JSON.parse(addFields.custom_categories as string)
									: addFields.custom_categories;
						} catch {
							throw new NodeOperationError(
								this.getNode(),
								'Invalid JSON in "Custom Categories" field',
								{ itemIndex: i },
							);
						}
					}

					if (addFields.includes) body.includes = addFields.includes;
					if (addFields.excludes) body.excludes = addFields.excludes;

					// API requires at least one entity id — fail clearly instead of a raw 4xx.
					if (!body.user_id && !body.agent_id && !body.run_id && !body.app_id) {
						throw new NodeOperationError(
							this.getNode(),
							'Add requires at least one of User ID, Agent ID, Run ID, or App ID',
							{ itemIndex: i },
						);
					}

					const addResp = await request('POST', '/v3/memories/add/', body);
					const waitForCompletion = this.getNodeParameter('waitForCompletion', i, true) as boolean;
					const addStatus = addResp.status as string | undefined;
					const isTerminal = addStatus === 'SUCCEEDED' || addStatus === 'FAILED';

					// Add returns {event_id, status:PENDING|RUNNING}; poll until terminal when asked to wait.
					if (waitForCompletion && addResp.event_id && !isTerminal) {
						responseData = await pollEvent(request, addResp.event_id as string, this, i);
					} else if (addStatus === 'FAILED') {
						throw new NodeOperationError(
							this.getNode(),
							`Mem0 memory add failed: ${(addResp.message as string) || 'unknown error'}`,

View on GitHub (pinned to 001c235229)

Solutions

  1. Include at least one of user_id, agent_id, or run_id in the JSON body.
  2. If your app has a single tenant, adopt a constant default (e.g. user_id="default") for all writes.
  3. Validate the payload client-side before sending so the failure is caught locally.

Example fix

# before
curl -X POST http://localhost:3000/memories -H 'Authorization: Bearer t' \
  -H 'Content-Type: application/json' -d '{"messages": [{"role": "user", "content": "prefers tea"}]}'

# after
curl -X POST http://localhost:3000/memories -H 'Authorization: Bearer t' \
  -H 'Content-Type: application/json' \
  -d '{"messages": [{"role": "user", "content": "prefers tea"}], "user_id": "alice"}'
Defensive patterns

Strategy: validation

Validate before calling

def validate_memory_payload(payload: dict) -> dict:
    if not any(payload.get(k) for k in ("user_id", "agent_id", "run_id")):
        payload["user_id"] = payload.get("user_id") or "default"
    assert any(payload.get(k) for k in ("user_id", "agent_id", "run_id"))
    return payload

Type guard

def has_scope(payload: dict) -> bool:
    return any(bool(payload.get(k)) for k in ("user_id", "agent_id", "run_id"))

Prevention

When it happens

Trigger: POST /memories with a body containing only messages and metadata (no user_id/agent_id/run_id); passing empty strings for all identifiers; a client that builds the payload dynamically and the identifier variables happened to be undefined/None.

Common situations: Porting code from the SDK where a default user_id was set elsewhere; frontend form where the user/agent field is optional and was left blank; batch scripts templating payloads with variables that resolve to empty strings.

Related errors


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