mem0ai/mem0 · warning · NodeOperationError

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

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.

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.

Appendix: 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)