Redactor

Redaction as an API

Strip the PII out
before the model
sees it.

Nine kinds of personal and secret data, found by rules and checksums rather than a model, and swapped for stable tokens you can reverse later. Deterministic, offline, and nothing you send is kept — not the text, not the mapping, not in the logs.

No subscription — pay in Ounie credits1 credit per 10,000 charactersREST · MCP · x402
INCIDENT NOTEReported byDana Whitfield,not detectedReach her atdana@halberd.example[EMAIL_1]Card on file4111 1111 1111 1111[CREDIT_CARD_1]Session from203.0.113.42at 09:14.[IP_ADDRESS_1]MAPPING · RETURNED TO YOU, STORED NOWHERE[EMAIL_1]dana@halberd.example[CREDIT_CARD_1]yours to keep, not ours3 found · 1 restored · 1 left alone

What it detects

A closed list, and every entry has a rule you can read.

Not “PII and similar”. These nine, each decidable from the text itself: a Luhn checksum, a mod-97 remainder, a published vendor prefix, a base64 header that really decodes to JSON. If a type cannot be decided that way, it is not on this list.

email

Email address

An RFC-shaped local part, an @, a domain with at least one dot and a 2–24 character TLD. Bounded on both sides so an address inside a longer token is not half-matched.

precision 100%
recall 100%
n = 14
phone

Phone number

Either an E.164 number (a leading + and 8–15 digits) or a North American number written with separators or parentheses, with the area code and exchange code both starting 2–9. A bare run of ten digits with no separators is NOT treated as a phone number.

precision 100%
recall 100%
n = 9
credit_card

Payment card number

A 13–19 digit run that passes the Luhn checksum AND carries a known network prefix (Visa, Mastercard, Amex, Discover, JCB, Diners, UnionPay, Maestro). Luhn alone is reported as unsure, never redacted.

precision 100%
recall 100%
n = 8
iban

IBAN

Two letters, two check digits, then the exact body length that country publishes, validated with the ISO 7064 mod-97 checksum.

precision 100%
recall 100%
n = 8
us_ssn

US Social Security number

AAA-GG-SSSS with a dash or space separator, rejecting the ranges the SSA never issues (area 000/666/900-999, group 00, serial 0000). Also matched when nine bare digits directly follow the words 'SSN' or 'social security'.

precision 100%
recall 100%
n = 5
ip_address

IP address

A dotted quad with every octet in 0–255, or an IPv6 address parsed structurally (never regex-guessed). A quad written directly after a 'v' is treated as a version string and left alone.

precision 100%
recall 100%
n = 9
api_key

API key or token

A credential carrying a published vendor prefix — OpenAI, Anthropic, GitHub, AWS, Google, Slack, Stripe, SendGrid, GitLab, DigitalOcean, Hugging Face, npm, Shopify, Twilio, Notion, Linear. High-entropy strings with no known prefix are reported as unsure, never redacted.

precision 100%
recall 100%
n = 8
jwt

JSON Web Token

Three base64url segments where the first one actually decodes to JSON containing an `alg` field. A string that merely looks like a JWT is not one.

precision 100%
recall 100%
n = 4
private_key

Private key block

A complete PEM block, from a BEGIN … PRIVATE KEY line to its matching END line, including the body.

precision 100%
recall 100%
n = 2
API keys recognised by vendor prefix:anthropicawsdigitaloceangithubgitlabgooglehuggingfacelinearnotionnpmopenaisendgridshopifyslackstripetwilio

Precision, honestly

Person names are not detected.

That is the first thing on this page rather than the last line of the terms, because it is the thing that will hurt you if you find it out later. There is no rule that separates a person’s name from any other pair of capitalised words, and this engine runs no model. A name-detector built on a word list would miss every name that is not on the list while looking like it worked — and a redactor that silently misses a category is worse than no redactor, because you stop checking.

not detected

Person names

There is no rule that separates a person's name from any other pair of capitalised words, and this engine deliberately runs no model. A name-detector built on a list would miss every name not on the list while looking like it worked. Names stay visible in the output — see the unsure count and the hero on the homepage.

not detected

Postal addresses

Street addresses vary by country in ways no regex settles, and the plausible patterns collide with ordinary prose. A partial address redaction is worse than none: it removes the street and leaves the city, so the record still identifies one household.

not detected

Dates of birth

A date is only a birth date because of what surrounds it. Redacting every date would gut a document; redacting some of them would be a guess about which.

not detected

Passport and national ID numbers

Outside the US SSN there is no checksum we can rely on across issuing countries, and the formats overlap with ordinary reference numbers.

not detected

Bare account and routing numbers

US ABA routing numbers do carry a checksum, but nine bare digits with no label match far too many internal identifiers. Nine-digit runs in the SSA's issued range are reported in `unsure` rather than removed.

not detected

Generic high-entropy secrets

A random-looking string is as likely to be a hash, a content id or a base64 payload as a credential. Only credentials with a published vendor prefix are redacted; the rest are counted in `unsure` so you can look.

not detected

Anything inside an image, a PDF or an audio file

This app takes text. It does no OCR and no transcription — use Extractor or Transcriber first, then send the text here.

Where the numbers come from

Measured against our own labelled corpus of 26 documents and 67 marked instances, most of which is text that resembles personal data and is not. It is evidence that the detectors work on the cases we could think of, including a set of near-misses that must not fire. It is not an independent benchmark, and it is not a promise about your documents.

documents
26
characters
7,284
labelled
67

A test recomputes the whole table from that corpus on every build and fails if this site disagrees with it. The figures here cannot drift away from the code, because the build will not let them.

unsure

The things we would not touch

Every response carries an unsure count and the offsets that produced it: a Luhn-valid number with no card-network prefix, ten bare digits in dialling shape, a high-entropy string next to a secret-shaped label.

They stay in the text. Removing them would cost precision on every invoice and order number you own; ignoring them would cost you the one case where it mattered. So we report and leave.

A clean result is not proof there was nothing there. Read the not-detected list first.

Mask or tokenize

One of these has a way back. The other is a door.

Tokenize when the document has to come back whole — a support reply, a generated email, anything the model writes that a person then reads. Mask when it never should: logs, analytics, a training set, an archive you would rather not be holding.

WHAT YOU SENDRefunddana@halberd.example, card4111 1111 1111 1111.MASKRefund[EMAIL], card[CREDIT_CARD].no mappingNothing to restore from. That is the mode.TOKENIZERefund[EMAIL_1], card[CREDIT_CARD_1].mapping → rehydrateReturned to you. We never had a copy.
tokenize

Stable tokens, one mapping

The same value gets the same token everywhere in the call, including across a batch of fifty documents — so a diff still lines up and one mapping restores all of them. If your text already contains something shaped like a token, new ones are numbered above it and the collision is reported rather than absorbed.

mask

No mapping, on purpose

A fixed marker per type and nothing returned that could undo it. We cannot restore a masked document and neither can anyone who gets hold of it later. That is the entire feature.

For developers & AI agents

Three ways in. One wallet, and it cannot be overdrawn.

Redaction is a pre-processing step, so it belongs in the pipeline rather than in a browser tab. Every rail runs the same engine and returns the same shape.

REST

Bearer key, or your session

Mint a key in the dashboard. It works on every read and every run, and it draws your Ounie credits — reserve-before-run means a key can refuse but never overdraw.

curl -s https://redact.ounie.com/api/redact \
  -H "Authorization: Bearer rdc_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Reach Dana at dana@halberd.example, card 4111 1111 1111 1111",
    "mode": "tokenize"
  }'
MCP

A tool your agent can hold

Claude, Cursor, ChatGPT and the Ounie AI Team all speak it. Hosts that cannot set a header take the key in the URL, which is why the ?api_key= fallback exists.

{
  "mcpServers": {
    "redactor": {
      "url": "https://redact.ounie.com/api/mcp?api_key=rdc_live_..."
    }
  }
}

redact_text · rehydrate_text · get_supported_entities · get_run · list_runs · get_pricing · get_credit_balance · whoami

x402

No account at all

POST the payload; you get a 402 quoting the exact price for that payload, in USDC on Base. Sign it, retry with an X-Payment header, get the result.

An unsupported entity or an over-cap payload comes back as a plain 4xx with no quote. You never sign an authorization for a call that cannot succeed.

https://redact.ounie.com/api/x402/redact
Full API docsMint a key1 credit per 10,000 characters, on every rail

Credit math

1 credit per 10,000 characters.

There is no provider behind this and no model call in it, so the price is our compute and nothing else. A document with no personal data in it still bills — “there is nothing to strip” is a real answer, and charging nothing for it would make a clean document look identical to a failed call.

CHARACTERS IN ONE CALL11 cr10,0001 cr10,0012 cr50,0005 cr200,00020 crRehydrating is a second call and bills the same way — it runs. Reading a run you already paid for is free.

Questions

The ones worth asking before you trust it.

Do you detect person names?
No, and that is the first thing on the homepage rather than the last line of the docs. There is no rule that separates a person's name from any other pair of capitalised words, and this engine deliberately runs no model. A name-detector built on a word list would miss every name not on the list while appearing to work, and a redactor that silently misses a category is worse than none — because you stop looking.
Then what do you actually detect?
Nine types, each decidable from the text by a rule or a checksum: email addresses, phone numbers, payment cards (Luhn plus a known network prefix), IBANs (mod-97), US social security numbers, IP addresses, vendor-prefixed API keys, JSON Web Tokens, and PEM private key blocks. Every one ships with a fixture suite that contains true negatives, and the measured figures are on the homepage.
Where do the precision and recall numbers come from?
From our own labelled corpus — 26 documents and 67 marked instances, most of it text that resembles personal data and is not. A test recomputes them from that corpus on every build and fails if this site disagrees, so the numbers cannot drift into marketing. What they are not is an independent benchmark, and we say so wherever we print them.
What is the unsure count?
Things that looked like they might matter and did not clear the bar — a Luhn-valid number with no card-network prefix, ten bare digits in dialling shape, a high-entropy string sitting next to a secret-shaped label. They are left in the text on purpose and reported with offsets so you can decide. Silently removing them would cost precision; silently ignoring them would cost you.
Do you store my text?
No. Not the text you send, not the redacted output, not the token mapping, and not in the logs — errors are recorded as identifiers rather than messages, because a message can quote its input. What is stored is a row of counts: how many characters, how many findings by type, how many credits. There is a test in the repository that reads the database migration and fails if a column appears that could hold any of it.
What happens if I lose the mapping?
The values are gone. We cannot recover them, because we never had them — the mapping is generated in the request and returned in the response. That is not a gap we intend to close; it is the reason handing you the mapping is safe in the first place.
Why does rehydrating cost credits?
Because it runs. The rule across Ounie is that anything which executes bills and anything which re-reads a result you already paid for does not — so a round trip is two calls, and looking up a past run is free.
How much text can one call take?
Up to 200,000 characters, across at most 50 documents. A batch shares one token namespace, so the same email address gets the same token in every document and one mapping restores all of them.
Is this a compliance product?
No. It removes specific, well-defined patterns from text and tells you exactly which ones it can and cannot find. Whether that is sufficient for your obligations under GDPR, HIPAA, PCI-DSS or anything else is a question for someone qualified to answer it, who should look at the not-detected list first.
Does it work in languages other than English?
The format-based detectors do — an IBAN, a card number, a JWT and an IP address have no language. Email addresses handle non-ASCII local parts and internationalised domains. The two rules that lean on English words are the phone-number context check and the labelled social-security-number pattern, and our corpus is English, so those are the weak spots.

Put it through here first.

One call before the prompt, one after the answer. Everything in between sees tokens.