All guides
Updated
Reading
7 min
By
Anlyon Team

Deleting a user from your AI agent: connections, limit counters and receipts

What endUsers.erase removes when a user deletes their account: OAuth grants revoked, approvals cancelled, counters cleared, receipts kept under a hash.

  • end users
  • deletion
  • privacy
  • oauth
  • receipts

When a user deletes their account, call anlyon.endUsers.erase(userId) from your deletion flow. It revokes their OAuth grants at the provider, deletes every connection, cancels their pending approvals and clears their per-user limit counters, and it keeps the record of what your agent did with the user's id replaced by a keyed one-way hash.

This guide covers what erase deletes, what it keeps, how to wire it into account deletion in TypeScript and Python, how to remove one connection, and what is still yours to handle. It describes what the call does. Whether that meets an obligation you have under a particular law is for you and your counsel to decide.

Why deletion is harder for an agent

A user who connected HubSpot to your agent left three things behind: a live OAuth grant in HubSpot, a token in whatever stores it, and a trail of actions under their id. Deleting your users row removes none of them. The grant still works until someone revokes it, and the token still decrypts.

What erase does

import { Client } from '@anlyonhq/sdk';

const anlyon = new Client({ apiKey: process.env.ANLYON_API_KEY! });

const { data } = await anlyon.endUsers.erase('user_42');
// { connectionsDeleted: 2, approvalsCancelled: 1, countersCleared: 3, erasedAs: 'erased~5c0f...' }
FieldWhat it counts
connectionsDeletedConnections revoked at the provider where it supports that, then deleted
approvalsCancelledPending approvals cancelled. Their calls were never sent
countersClearedSettled per-user limit counters cleared
erasedAsThe keyed one-way hash the kept records now carry in place of the id

The key needs the end-users:write scope, which no key gets by default.

What is deleted and what is kept

RecordWhat happens
OAuth connectionsRevoked at the provider, then deleted with their encrypted tokens
API-key connectionsDeleted with the encrypted key
Pending approvalsDenied by the system with the note "Cancelled: the end user was erased." The parked call ends denied with decision code END_USER_ERASED, and nothing is sent
Per-user limit countersSettled counters deleted. A period still holding a reservation keeps its row until the effect behind it settles, and stops applying to anyone
Browser session tokensTokens from createSession issued before the erasure are refused
Invocations, receipts, previews, approvals, function runs, audit eventsKept, with the user id replaced by erasedAs
Audit records already in the archiveExported under erasedAs, rewritten as they are read

The erasure itself is recorded as an end_user.erased audit record and event, under the hash.

The hash is an HMAC keyed with a server secret and scoped to your workspace. An id that is easy to guess, such as an email address, cannot be recovered by hashing candidates, and an erased id cannot collide with a live one. It is not reversible by you or by Anlyon's API, and endUsers.list no longer shows the user.

If the same id signs up again later, it starts as a new end user. Records made after the erasure carry the id as normal.

Revocation at each provider

Revocation is what turns the grant off in the user's own account. Anlyon revokes the way each provider expects:

ProviderWhat Anlyon revokes
HubSpotThe refresh token, at HubSpot's token revoke endpoint
SlackThe access token, with auth.revoke
LinearThe refresh token, at Linear's revoke endpoint
GitHubThat one token, by DELETE on the application token endpoint, so other grants the person gave the app stay
NotionThe access token, at Notion's revoke endpoint
API-key integrations (Attio, Asana, Jira, Stripe and the rest)Nothing at the provider. The user created the key, so Anlyon deletes its copy

Revocation is best effort. If the provider does not answer, the token is deleted anyway, so it can no longer be used through Anlyon. A connection already marked needs_reconnect is deleted without a revocation call, because the provider has already refused it.

Wire it into your account deletion flow

Erase with Anlyon before you delete your own user row, because the user id is what names them.

// The user asked to delete their account.
export async function deleteAccount(userId: string) {
  // One client per environment the user was active in: erase is scoped to the key's environment.
  const environments = [
    new Client({ apiKey: process.env.ANLYON_PRODUCTION_KEY! }),
    new Client({ apiKey: process.env.ANLYON_STAGING_KEY! }),
  ];

  for (const anlyon of environments) {
    const { data } = await anlyon.endUsers.erase(userId);
    await recordDeletionStep('anlyon', {
      erasedAs: data!.erasedAs, // store the hash, not next to the user id
      connectionsDeleted: data!.connectionsDeleted,
      approvalsCancelled: data!.approvalsCancelled,
    });
  }

  await db.users.delete(userId);
}

Run it from a job that retries on failure. Calling erase again for the same id is safe: a second run finds no connection or approval left, and returns the same erasedAs.

The same flow in Python:

import os
from anlyon import Client

ENVIRONMENTS = [
    Client(api_key=os.environ["ANLYON_PRODUCTION_KEY"]),
    Client(api_key=os.environ["ANLYON_STAGING_KEY"]),
]

def delete_account(user_id: str) -> None:
    for anlyon in ENVIRONMENTS:
        result = anlyon.end_users.erase(user_id).data
        record_deletion_step(
            "anlyon",
            erased_as=result["erasedAs"],
            connections_deleted=result["connectionsDeleted"],
            approvals_cancelled=result["approvalsCancelled"],
        )
    db.users.delete(user_id)

If you keep a deletion log, decide whether storing erasedAs there is right for you. It lets you find the kept records later, which is useful for an audit and is a link back to the person if the log also names them.

Delete one connection, not the user

When a user disconnects HubSpot in your settings page but keeps their account, remove that one connection:

import { NotFoundError } from '@anlyonhq/sdk';

try {
  await anlyon.endUsers.deleteConnection(user.id, 'hubspot'); // revoked at HubSpot, then deleted
} catch (error) {
  if (!(error instanceof NotFoundError)) throw error; // 404: no HubSpot connection for this user
}
anlyon.end_users.delete_connection(user.id, "hubspot")

The user's history, approvals and limit counters stay as they were. Their next HubSpot call answers 409 END_USER_NOT_CONNECTED with a fresh connect link, and nothing is sent. The connection flow is covered in OAuth for AI agents.

What you still own

Erase covers what Anlyon holds. These are yours:

Where the data isWho handles it
Your own database, logs and traces, including any tool-call logging that recorded inputsYou
Records you kept from Anlyon webhooks, which named the user before the erasureYou
Notes, deals, messages and issues the agent already wrote into HubSpot, Slack, Linear, GitHub or NotionThe user's account at that provider. Erase does not delete them
An API key the user created at a providerThe user, who can delete it in the provider's settings
Prompts and outputs sent to your model providerYour model provider's retention terms
The content of kept Anlyon recordsErase replaces the user id. The request a call sent, its target, the provider's response and an approval's title and payload keep what they held

That last row matters if your agent passed personal data as action input, such as a contact's email in a hubspot-create-contact call. The receipt keeps the request it sent, under the hash. If that content must go too, keep personal data out of action inputs where you can, and pass your own record ids instead.

Limits

  • Erase is per environment. Call it with each environment's key the user was active in.
  • Revocation at the provider is best effort. The token is deleted either way.
  • API-key connections have nothing to revoke at the provider. The key keeps working there until the user deletes it.
  • Kept records keep their content, with the user id replaced.
  • Erase does not make your product compliant with any law. It is one step in your deletion process, and it tells you exactly what it removed.

To find what to expect before you erase, run the per-user lookup in how to see what your AI agent did for one user.

Next step: add endUsers.erase to your account deletion job and run it against a test user in staging, then check the console's Users page no longer lists them. The act for your users docs guide has the full end-user reference.

Frequently asked questions

How do I revoke a user's OAuth tokens when they delete their account?
Call the provider's revocation endpoint for each grant, then delete the token from your database. With Anlyon, anlyon.endUsers.erase(userId) revokes the user's HubSpot, Slack, Linear, GitHub and Notion grants at the provider and deletes every connection in one call.
Do I have to delete an AI agent's action logs when a user asks to be erased?
That depends on your legal basis and retention policy, which is a question for your counsel. What Anlyon does is keep invocations, receipts, approvals and audit records with the user's id replaced by a keyed one-way hash, so the history of what the agent did stays without naming the person.
What does Anlyon's erase return?
It returns connectionsDeleted, approvalsCancelled, countersCleared and erasedAs. erasedAs is the keyed one-way hash the kept records now carry in place of the user's id.
Does erasing a user in Anlyon delete data the agent wrote into HubSpot or Slack?
No. A note the agent added in HubSpot or a message it posted in Slack belongs to the user's account at that provider. Erase revokes Anlyon's access to that account; it does not delete what is already there.

Let your agent act for your users, safely.

A few lines of server code, then send a user a connect link. Nothing to host.

$ claude mcp add --transport http anlyon https://api.anlyon.com/mcp