Find every personal detail in text, and know what it is
SporeLabs Scrub finds the names, addresses, phone numbers, emails, card numbers, passwords, login codes and API keys in text, and labels each one. Your app decides what to do with them. It is a 17M-parameter model plus rules, and runs offline on a laptop CPU, in 2.1 ms per message.
Use it now
Call the API
Create a SporeLabs account and an API key, add funds, and send your text:
curl https://sporelabs.dev/v1/scrub \
-H "Authorization: Bearer $SPORELABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Please call Tamsin Rourke on (312) 555-3917 or email tamsin.rourke@fernmail.com.", "mode": "tag"}'
{
"id": "scrub_...",
"object": "scrub",
"model": "sporelabs/scrub-v1",
"mode": "tag",
"results": [{
"text": "Please call [PERSON] on [PHONE] or email [EMAIL].",
"findings": [
{"start": 12, "end": 25, "type": "PERSON", "text": "Tamsin Rourke"},
{"start": 29, "end": 43, "type": "PHONE", "text": "(312) 555-3917"},
{"start": 53, "end": 79, "type": "EMAIL", "text": "tamsin.rourke@fernmail.com"}
],
"by_type": {"PERSON": ["Tamsin Rourke"], "PHONE": ["(312) 555-3917"], "EMAIL": ["tamsin.rourke@fernmail.com"]}
}],
"usage": {"characters": 80, "findings": 3, "cost_millicents": 4}
}
Each finding comes back with its position and type. Use them yourself, or set mode to have the text rewritten: find (default) only reports, tag swaps in the type, pseudonym swaps in a consistent stand-in, and mask swaps in *. Send up to 64 texts at once in texts.
$0.50 per million characters, paid from your SporeLabs wallet. No subscription.
Run it on your own machine
The model and rules are open on Hugging Face. The package downloads the model once; after that, nothing leaves your machine.
pip install "sporelabs-scrub @ https://huggingface.co/SporeLabs/scrub/resolve/main/package/sporelabs_scrub-0.1.1-py3-none-any.whl"
from sporelabs_scrub import Scrub
scrub = Scrub() # downloads SporeLabs/scrub once
text = "Please call Tamsin Rourke on (312) 555-3917."
print(scrub.scrub(text, mode="tag")) # Please call [PERSON] on [PHONE].
for f in scrub.find(text):
print(f["type"], f["start"], f["end"], f["text"])
Or fetch the files: hf download SporeLabs/scrub --local-dir scrub
See it on one message
Hi, it's Tamsin RourkePERSON (username tamsin_rUSERNAME). My parcel never came. I'm at 48 Quillfeather Lane, MillbrookADDRESS. Call me on (312) 555-3917PHONE or email tamsin.rourke@fernmail.comEMAIL. I paid with card 4716 2093 8851 6620CREDIT_CARD, and the login code you texted was 482913OTP. Hi, it's PERSON (username USERNAME). My parcel never came. I'm at ADDRESS. Call me on PHONE or email EMAIL. I paid with card CREDIT_CARD, and the login code you texted was OTP. Hi, it's Novak Kariuki (username khartigan). My parcel never came. I'm at 02 Alder Lane, Hazel. Call me on (016) 422-5022 or email zdunmore@example.net. I paid with card 4892 0894 5335 8927, and the login code you texted was 878316.
Why the type matters
A redactor that only knows something is sensitive gives you black bars. Scrub says what each detail is, so your app can act on it:
| In the message | Type | Into a record | Stand-in |
|---|---|---|---|
| Tamsin Rourke | PERSON | Name | Novak Kariuki |
| tamsin_r | USERNAME | Username | khartigan |
| 48 Quillfeather Lane, Millbrook | ADDRESS | Address | 02 Alder Lane, Hazel |
| (312) 555-3917 | PHONE | Phone | (016) 422-5022 |
| tamsin.rourke@fernmail.com | zdunmore@example.net | ||
| 4716 2093 8851 6620 | CREDIT_CARD | Card ending 6620 | 4892 0894 5335 8927 |
| 482913 | OTP | Not stored | 878316 |
Route a ticket with a card number to payments. Fill a CRM record from a free-text message. Keep a map of the personal data each system holds, ready for the day someone asks what you have on them.
Stand-ins stay consistent, so the same name gets the same stand-in every time:
BeforeHi, Rafe Okonjo here. Please send the refund to card 4024 7716 3390 5182 and update my address to 7 Pelham Mews, Ashcombe. Rafe Okonjo, (617) 555-2841
AfterHi, Hana Jablonski here. Please send the refund to card 4201 2629 5022 2583 and update my address to 3 Juniper Pine, Birch. Hana Jablonski, (390) 325-2811
Everything else stays as it was, so the text still works for analytics, search and AI.
How SporeLabs built it
- SporeLabs collected openly licensed text where personal details turn up: chats, text messages, code and config files.
- SporeLabs placed realistic made-up details into that text and labelled each with its type.
- SporeLabs trained a small encoder, Ettin 17M, to tag each word with its type.
- SporeLabs added fast rules for fixed shapes, like API keys and card numbers, and to finish partly marked details.
- SporeLabs tested it on documents it had never seen.
Results
SporeLabs tested Scrub and 4 other systems on the same 100 documents none of them had seen: support messages, forms, chats, logs and code.
| System | Personal details caught | Passwords, keys and codes caught | Precision | Caught with the right type |
|---|---|---|---|---|
| SporeLabs Scrub | 92.1% | 91.8% | 81.0% | 88.3% |
| OpenAI Privacy Filter | 77.0% | 85.2% | 77.3% | 52.3% |
| GLiNER2-PII | 87.3% | 77.0% | 74.1% | 80.0% |
| AWS Comprehend | 85.5% | 38.5% | 78.1% | 71.2% |
| Microsoft Presidio | 54.4% | 7.4% | 57.8% | 42.4% |
Scrub caught the most personal details (92.1% against 87.3% for GLiNER2-PII), the most passwords, keys and codes (91.8% against 85.2% for OpenAI Privacy Filter) and the most details with the right type (88.3% against 80.0% for GLiNER2-PII).
Each finding comes back with its type. By type, on the same documents:
| Type | n | SporeLabs Scrub | Best other system |
|---|---|---|---|
| Names | 273 | 90.1% | 90.8% (GLiNER2-PII) |
| Addresses | 44 | 75.0% | 95.5% (AWS Comprehend) |
| Phone numbers | 54 | 90.7% | 96.3% (AWS Comprehend) |
| Emails | 61 | 96.7% | 96.7% (AWS Comprehend) |
| Usernames | 72 | 88.9% | 73.6% (GLiNER2-PII) |
| Passwords | 19 | 52.6% | 68.4% (GLiNER2-PII) |
| Login codes | 29 | 69.0% | 24.1% (GLiNER2-PII) |
| API keys and tokens | 87 | 95.4% | 74.7% (GLiNER2-PII) |
Speed
| System | Time per message |
|---|---|
| SporeLabs Scrub | 2.1 ms |
| Microsoft Presidio | 3.2 ms |
| OpenAI Privacy Filter | 70.8 ms |
| GLiNER2-PII | 108.0 ms |
| AWS Comprehend (cloud, round trip) | 49 ms |