Redaction as an API
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.
What it detects
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 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.
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.
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.
IBAN
Two letters, two check digits, then the exact body length that country publishes, validated with the ISO 7064 mod-97 checksum.
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'.
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.
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.
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.
Private key block
A complete PEM block, from a BEGIN … PRIVATE KEY line to its matching END line, including the body.
Precision, honestly
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.
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.
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.
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.
Outside the US SSN there is no checksum we can rely on across issuing countries, and the formats overlap with ordinary reference 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.
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.
This app takes text. It does no OCR and no transcription — use Extractor or Transcriber first, then send the text here.
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.
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.
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
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.
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.
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
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.
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"
}'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
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
Credit math
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.
Questions
One call before the prompt, one after the answer. Everything in between sees tokens.