{"record":{"id":"0aea116e058a79ee","repo":"mem0ai/mem0","slug":"mem0-memory-add-failed-addresp-message-as-stri","errorCode":null,"errorMessage":"Mem0 memory add failed: ${(addResp.message as string) || 'unknown error'}","messagePattern":"Mem0 memory add failed: (.+?)","errorType":"exception","errorClass":"NodeOperationError","httpStatus":null,"severity":"warning","filePath":"integrations/n8n-nodes-mem0/nodes/Mem0/Mem0.node.ts","lineNumber":472,"sourceCode":"\t\t\t\t\t// API requires at least one entity id — fail clearly instead of a raw 4xx.\n\t\t\t\t\tif (!body.user_id && !body.agent_id && !body.run_id && !body.app_id) {\n\t\t\t\t\t\tthrow new NodeOperationError(\n\t\t\t\t\t\t\tthis.getNode(),\n\t\t\t\t\t\t\t'Add requires at least one of User ID, Agent ID, Run ID, or App ID',\n\t\t\t\t\t\t\t{ itemIndex: i },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst addResp = await request('POST', '/v3/memories/add/', body);\n\t\t\t\t\tconst waitForCompletion = this.getNodeParameter('waitForCompletion', i, true) as boolean;\n\t\t\t\t\tconst addStatus = addResp.status as string | undefined;\n\t\t\t\t\tconst isTerminal = addStatus === 'SUCCEEDED' || addStatus === 'FAILED';\n\n\t\t\t\t\t// Add returns {event_id, status:PENDING|RUNNING}; poll until terminal when asked to wait.\n\t\t\t\t\tif (waitForCompletion && addResp.event_id && !isTerminal) {\n\t\t\t\t\t\tresponseData = await pollEvent(request, addResp.event_id as string, this, i);\n\t\t\t\t\t} else if (addStatus === 'FAILED') {\n\t\t\t\t\t\tthrow new NodeOperationError(\n\t\t\t\t\t\t\tthis.getNode(),\n\t\t\t\t\t\t\t`Mem0 memory add failed: ${(addResp.message as string) || 'unknown error'}`,\n\t\t\t\t\t\t\t{ itemIndex: i },\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the response is already terminal, unwrap results; otherwise return as-is.\n\t\t\t\t\t\tresponseData = Array.isArray(addResp.results)\n\t\t\t\t\t\t\t? (addResp.results as IDataObject[])\n\t\t\t\t\t\t\t: addResp;\n\t\t\t\t\t}\n\t\t\t\t} else if (operation === 'search') {\n\t\t\t\t\tconst body: IDataObject = {\n\t\t\t\t\t\tquery: this.getNodeParameter('query', i) as string,\n\t\t\t\t\t\toutput_format: 'v1.1',\n\t\t\t\t\t\ttop_k: this.getNodeParameter('limit', i, 50) as number,\n\t\t\t\t\t};\n\t\t\t\t\tbody.filters = buildEntityFilters(\n\t\t\t\t\t\t{","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/integrations/n8n-nodes-mem0/nodes/Mem0/Mem0.node.ts#L454-L490","documentation":"GET /memories with no user_id/run_id/agent_id means 'list ALL memories in the store', which the server treats as an admin-only operation. If the caller is authenticated but their role is not admin (and auth is not via ADMIN_API_KEY/AUTH_DISABLED), the request is rejected with 403. Providing any single identifier scopes the query and avoids the check entirely.","triggerScenarios":"A member-role user or their personal API key calling GET /memories with no query params; a client that 'lists everything' on startup by omitting filters; admin-key auth not actually matching ADMIN_API_KEY so the caller fell into the API-key user path.","commonSituations":"Dashboard-style clients enumerating all memories for a demo; multi-user deployments where non-admin users share one memory store; scripts written under AUTH_DISABLED that later run against an authenticated server.","solutions":["Add at least one scope query parameter: GET /memories?user_id=alice (or agent_id/run_id).","Or authenticate as an admin-role user / use ADMIN_API_KEY for the unscoped listing.","Fix the client to always derive and pass the current user's identifier instead of issuing unscoped GETs."],"exampleFix":"# before\nrequests.get(f\"{BASE}/memories\", headers=headers)  # 403 for member user\n\n# after\nrequests.get(f\"{BASE}/memories\", params={\"user_id\": \"alice\"}, headers=headers)","handlingStrategy":"validation","validationCode":"def list_memories(base: str, headers: dict, user_id: str | None) -> dict:\n    params = {\"user_id\": user_id} if user_id else None\n    if params is None:\n        assert headers.get(\"Authorization\") and is_admin(headers), \\\n            \"Unscoped GET /memories requires admin credentials\"\n    return requests.get(f\"{base}/memories\", params=params, headers=headers).json()","typeGuard":"def is_admin(headers: dict) -> bool:\n    # decode own JWT payload (middle segment) and inspect role claim\n    import base64, json\n    tok = headers.get(\"Authorization\", \"\").removeprefix(\"Bearer \")\n    if not tok:\n        return False\n    payload = tok.split(\".\")[1]\n    claims = json.loads(base64.urlsafe_b64decode(payload + \"=\" * (-len(payload) % 4)))\n    return claims.get(\"role\") == \"admin\"","tryCatchPattern":"if resp.status_code == 403 and \"Admin role required\" in resp.text:\n    raise PermissionError(\"Scope the request (user_id/agent_id/run_id) or use admin credentials\")","preventionTips":["Default clients to always pass a scope parameter.","Reserve unscoped listing for admin tooling.","Teach dashboards to page through scoped queries per actor."],"tags":["authorization","http-403","memories","rest-server"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}