mem0ai/mem0 · info · NodeOperationError
Provide at least one of User ID, Agent ID, App ID, or Run ID
Error message
Provide at least one of User ID, Agent ID, App ID, or Run ID
What it means
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.
Source
Thrown at integrations/n8n-nodes-mem0/nodes/Mem0/Mem0.node.ts:587
throw new NodeApiError(this.getNode(), error as JsonObject, { itemIndex: i });
}
}
return [returnData];
}
}
function buildEntityFilters(
ids: Record<string, string>,
ctx: IExecuteFunctions,
itemIndex: number,
): IDataObject {
const clauses: IDataObject[] = Object.entries(ids)
.filter(([, value]) => value)
.map(([key, value]) => ({ [key]: value }));
if (clauses.length === 0) {
throw new NodeOperationError(
ctx.getNode(),
'Provide at least one of User ID, Agent ID, App ID, or Run ID',
{ itemIndex },
);
}
return clauses.length === 1 ? clauses[0] : { OR: clauses };
}
// Polls GET /v1/event/{id}/ until the memory-addition event resolves.
async function pollEvent(
request: (m: IHttpRequestMethods, u: string) => Promise<IDataObject>,
eventId: string,
ctx: IExecuteFunctions,
itemIndex: number,
): Promise<IDataObject | IDataObject[]> {
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
const event = await request('GET', `/v1/event/${encodeURIComponent(eventId)}/`);View on GitHub (pinned to 001c235229)
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.
Example fix
# before
resp = requests.delete(f"{BASE}/api-keys/{key_id}", headers=h)
resp.raise_for_status() # raises on 'already revoked' 400
# after
resp = requests.delete(f"{BASE}/api-keys/{key_id}", headers=h)
if resp.status_code == 400 and "already revoked" in resp.json().get("detail", ""):
pass # already in desired state
else:
resp.raise_for_status() Defensive patterns
Strategy: try-catch
Validate before calling
keys = requests.get(f"{BASE}/api-keys", headers=h).json()
if any(k["id"] == key_id and k.get("revoked_at") for k in keys):
print("already revoked; skipping") # skip before calling DELETE Type guard
def is_revoked(key_record: dict) -> bool:
return key_record.get("revoked_at") is not None Try / catch
resp = requests.delete(f"{BASE}/api-keys/{key_id}", headers=h)
if resp.status_code == 400 and "already revoked" in resp.json().get("detail", ""):
pass # idempotent success: desired state reached
else:
resp.raise_for_status() Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Authentication failed. Your API key may be invalid or expire
- Invalid JSON in "Metadata" field
- Invalid JSON in "Custom Categories" field
- Add requires at least one of User ID, Agent ID, Run ID, or A
- Provide text or metadata to update
AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15).
Data as JSON: /api/errors/d4dfd71987c18780.
Report an issue: GitHub.