{"record":{"id":"d4dfd71987c18780","repo":"mem0ai/mem0","slug":"provide-at-least-one-of-user-id-agent-id-app-id","errorCode":null,"errorMessage":"Provide at least one of User ID, Agent ID, App ID, or Run ID","messagePattern":"Provide at least one of User ID, Agent ID, App ID, or Run ID","errorType":"exception","errorClass":"NodeOperationError","httpStatus":null,"severity":"info","filePath":"integrations/n8n-nodes-mem0/nodes/Mem0/Mem0.node.ts","lineNumber":587,"sourceCode":"\t\t\t\tthrow new NodeApiError(this.getNode(), error as JsonObject, { itemIndex: i });\n\t\t\t}\n\t\t}\n\n\t\treturn [returnData];\n\t}\n}\n\nfunction buildEntityFilters(\n\tids: Record<string, string>,\n\tctx: IExecuteFunctions,\n\titemIndex: number,\n): IDataObject {\n\tconst clauses: IDataObject[] = Object.entries(ids)\n\t\t.filter(([, value]) => value)\n\t\t.map(([key, value]) => ({ [key]: value }));\n\n\tif (clauses.length === 0) {\n\t\tthrow new NodeOperationError(\n\t\t\tctx.getNode(),\n\t\t\t'Provide at least one of User ID, Agent ID, App ID, or Run ID',\n\t\t\t{ itemIndex },\n\t\t);\n\t}\n\n\treturn clauses.length === 1 ? clauses[0] : { OR: clauses };\n}\n\n// Polls GET /v1/event/{id}/ until the memory-addition event resolves.\nasync function pollEvent(\n\trequest: (m: IHttpRequestMethods, u: string) => Promise<IDataObject>,\n\teventId: string,\n\tctx: IExecuteFunctions,\n\titemIndex: number,\n): Promise<IDataObject | IDataObject[]> {\n\tfor (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {\n\t\tconst event = await request('GET', `/v1/event/${encodeURIComponent(eventId)}/`);","sourceCodeStart":569,"sourceCodeEnd":605,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/integrations/n8n-nodes-mem0/nodes/Mem0/Mem0.node.ts#L569-L605","documentation":"DELETE /api-keys/{key_id} is idempotency-unfriendly on purpose: if the targeted key already has revoked_at set, further DELETEs return 400 'API key is already revoked' instead of silently succeeding. Revocation is a one-time state transition; a second attempt is treated as a client error.","triggerScenarios":"Clicking 'revoke' twice in a UI without a refresh in between; retrying a DELETE that already succeeded (e.g. response lost to a network error); scripts that reconcile state by re-revoking all keys each run.","commonSituations":"Eventual-consistency UI showing a stale 'active' key; automation that does not track that revocation already happened; retry logic that resends non-idempotent DELETEs after timeouts.","solutions":["Treat this 400 as success in reconciliation scripts: the desired end state (revoked) is already true.","Refresh the key list after revoking and disable the UI action for keys with revoked_at set.","Before deleting, check the key's status via GET /api-keys and skip if already revoked."],"exampleFix":"# before\nresp = requests.delete(f\"{BASE}/api-keys/{key_id}\", headers=h)\nresp.raise_for_status()  # raises on 'already revoked' 400\n\n# after\nresp = requests.delete(f\"{BASE}/api-keys/{key_id}\", headers=h)\nif resp.status_code == 400 and \"already revoked\" in resp.json().get(\"detail\", \"\"):\n    pass  # already in desired state\nelse:\n    resp.raise_for_status()","handlingStrategy":"try-catch","validationCode":"keys = requests.get(f\"{BASE}/api-keys\", headers=h).json()\nif any(k[\"id\"] == key_id and k.get(\"revoked_at\") for k in keys):\n    print(\"already revoked; skipping\")  # skip before calling DELETE","typeGuard":"def is_revoked(key_record: dict) -> bool:\n    return key_record.get(\"revoked_at\") is not None","tryCatchPattern":"resp = requests.delete(f\"{BASE}/api-keys/{key_id}\", headers=h)\nif resp.status_code == 400 and \"already revoked\" in resp.json().get(\"detail\", \"\"):\n    pass  # idempotent success: desired state reached\nelse:\n    resp.raise_for_status()","preventionTips":["Treat 'already revoked' as success in reconciliation logic.","Refresh UI state immediately after revoke actions.","Avoid blind retries of DELETE after network errors without checking current state."],"tags":["api-key","http-400","idempotency","rest-server"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}