Source: https://help.spamfoo.com/api/personal-data
Last updated: 2026-10-27

***

# Manage Personal Data

Delete a user, or count or delete the corrections SpamFoo Client has stored for one user. Delete the user when a mailbox is removed. Delete only the corrections when a user asks to reset their personalization.

SpamFoo Client keeps two separate memories for each user.

- **Spam memory:** filled by [Report Spam or Not Spam](https://help.spamfoo.com/api/feedback).
- **Classification memory:** filled by [Report a Classification Fix](https://help.spamfoo.com/api/feedback-tabs).

All of these endpoints require the proxy secret, even from loopback, and have no license check. The user ID must match the `X-User-ID` value you send when classifying. Case doesn't matter.

## Delete a User

`DELETE /integration/users/{userId}`

Deletes everything SpamFoo Client keeps for one user: both memories and their rules, the user's folder, the copies of messages the user corrected, the user's classification history and mailbox statistics in the admin console, and the user's own allow and block rules. Put the user ID in the path, URL-encoded, for example `/integration/users/user%40example.com`. There is no body.

Calling it for a user that has nothing stored still returns `200`. If you push an [address map](https://help.spamfoo.com/api/address-map), leave the user out of your next push too. If mail for the same address is classified later, SpamFoo Client starts that user again from nothing.

A successful request returns `200` with what was removed:

```
{
    "userId": "user@example.com",
    "folderDeleted": true,
    "archivedCorrectionsDeleted": 2,
    "classificationsDeleted": 140,
    "mailboxStatsDeleted": 1,
    "overridesDeleted": 0
}
```

## Delete Spam Corrections

`DELETE /personalization/data`

Deletes the user's spam memory, plus any per-user spam rules stored on disk. After this call, the user's past spam and not-spam corrections no longer affect their mail.

## Delete Classification Corrections

`DELETE /personalization/tabs/data`

Deletes the user's classification memory, plus any per-user classification rules stored on disk. To remove all of a user's corrections, call both `DELETE` endpoints.

### Delete Request

Both `DELETE` endpoints take the same JSON body and need `Content-Type: application/json`.

```
{
    "userId": "user@example.com"
}
```

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| `userId` | Yes | string | The user whose data to delete. |

### Delete Response

A successful request returns `200`. The message says `tabs personalization` for the classification endpoint.

```
{
    "status": "success",
    "message": "All personalization data cleared for user user@example.com"
}
```

### What Is Not Deleted

The two correction `DELETE` calls remove only the two memories and any rule files. They do not remove the items below. [Delete a User](https://help.spamfoo.com/api/personal-data#delete-user) removes the first two.

- The compressed copies of corrected messages that SpamFoo Client keeps on its server.
- Classification history and stored message content shown in the admin console.
- Anonymized corrections already queued or sent to SpamFoo. These carry a hashed user ID, not the address.

## Count Spam Corrections

`GET /personalization/knn/stats?userId={userId}`

Returns how many entries are in the user's spam memory.

```
{
    "userId": "user@example.com",
    "totalEntries": 37,
    "userCorrections": "N/A",
    "autoEntries": "N/A"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `userId` | string | The user you asked about. |
| `totalEntries` | integer | Number of entries in the user's spam memory. |
| `userCorrections`, `autoEntries` | string | Always `"N/A"`. SpamFoo Client does not report this split. |

## Count Classification Corrections

`GET /personalization/tabs/knn/stats?userId={userId}`

Returns how many entries are in the user's classification memory, grouped by sender.

```
{
    "userId": "user@example.com",
    "totalEntries": 5,
    "entriesBySender": {
        "news@example.org": 3,
        "billing@example.net": 2
    }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `userId` | string | The user you asked about. |
| `totalEntries` | integer | Number of entries in the user's classification memory. |
| `entriesBySender` | object | Number of entries for each sender address. |

### Stats Request

| Query Parameter | Required | Type | Description |
| --- | --- | --- | --- |
| `userId` | Yes | string | The user to count. URL-encode it. |

## Errors

| Status | Body | Cause |
| --- | --- | --- |
| `400` | `{"error": "userId is required"}` | `DELETE`: `userId` was missing or blank. |
| `400` | `{"error": "userId query parameter is required"}` | `GET`: `userId` was blank. |
| `400` | Empty | `GET`: the `userId` parameter was left out. `DELETE`: the body was empty or was not valid JSON. |
| `401` | Empty | The proxy secret was missing or wrong. |
| `415` | Empty | A correction `DELETE` did not use `Content-Type: application/json`. |

## Example

This example counts a user's corrections, then deletes both memories.

### curl

```
curl -G http://localhost:16253/personalization/knn/stats \
    -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \
    --data-urlencode "userId=user@example.com"

curl -G http://localhost:16253/personalization/tabs/knn/stats \
    -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \
    --data-urlencode "userId=user@example.com"

curl -X DELETE http://localhost:16253/personalization/data \
    -H "Content-Type: application/json" \
    -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \
    -d '{"userId": "user@example.com"}'

curl -X DELETE http://localhost:16253/personalization/tabs/data \
    -H "Content-Type: application/json" \
    -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \
    -d '{"userId": "user@example.com"}'
```

### C#

```
using System.Net.Http.Json;
using System.Text.Json;

var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET")
    ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET.");

using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") };
http.DefaultRequestHeaders.Add("X-SpamFoo-Proxy-Auth", secret);

var userId = "user@example.com";
var query = $"?userId={Uri.EscapeDataString(userId)}";

var spamStats = await http.GetFromJsonAsync<JsonElement>("/personalization/knn/stats" + query);
var tabsStats = await http.GetFromJsonAsync<JsonElement>("/personalization/tabs/knn/stats" + query);
Console.WriteLine($"spam entries: {spamStats.GetProperty("totalEntries").GetInt32()}");
Console.WriteLine($"classification entries: {tabsStats.GetProperty("totalEntries").GetInt32()}");

foreach (var path in new[] { "/personalization/data", "/personalization/tabs/data" })
{
    using var request = new HttpRequestMessage(HttpMethod.Delete, path)
    {
        Content = JsonContent.Create(new { userId })
    };
    using var response = await http.SendAsync(request);
    response.EnsureSuccessStatusCode();
}
```

### Python

```
import os

import requests

SPAMFOO_URL = "http://localhost:16253"
HEADERS = {"X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"]}
user_id = "user@example.com"

for path in ("/personalization/knn/stats", "/personalization/tabs/knn/stats"):
    response = requests.get(
        f"{SPAMFOO_URL}{path}",
        params={"userId": user_id},
        headers=HEADERS,
        timeout=30,
    )
    response.raise_for_status()
    print(path, response.json()["totalEntries"])

for path in ("/personalization/data", "/personalization/tabs/data"):
    response = requests.delete(
        f"{SPAMFOO_URL}{path}",
        json={"userId": user_id},
        headers=HEADERS,
        timeout=30,
    )
    response.raise_for_status()
```
