Skip to content

API Documentation

Reference for the upii.eu API, which replaces the personal data it detects in text and documents with placeholders. Detection is not perfect: always review the result.

Base URL: https://upii.eu · OpenAPI Spec

1. Authentication

Send your own software's requests with a Bearer token. Create API keys from your dashboard. Without a token, only the limited demo (500 characters, 10 requests a minute) answers.

curl -X POST https://upii.eu/protect \
  -H "Authorization: Bearer pii_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "Jean Dupont, jean@test.fr"}'

2. Processing Modes

Control output format with the mode field. Available on /protect and /batch. The value name anonymize is kept as it is, so existing integrations keep working: it means “replace with placeholders”.

anonymize

Default. Protects the text: replaces personal data with typed placeholders (pseudonymisation).

Jean Dupont → [PERSON_1]

detect

Returns entities only. No text modification.

anonymized: null

redact

Replaces all detected personal data with [REDACTED].

Jean Dupont → [REDACTED]

3. Pseudonymization

Choose how placeholders are generated with the pseudonym field. Available on /protect, /batch, and /protect-document (and on their deprecated aliases).

These three styles belong to the API above. The documents of a project in the app (the dashboard) are always written with one fixed token, {{PERSON:2a37e4c1b9}}: the type and a 10-character tag that is the same for the same value everywhere in that project. Restore needs exactly that token.

counter

Default. Per-request positional labels.

Jean Dupont → PERSON_1

Marie Curie → PERSON_2

Same labels regenerated each request.

stable

A fixed tag per value, different for every account.

Jean Dupont → PERSON_caa52d3b16

Marie Curie → PERSON_627eb1ef64

Same input value → same placeholder across all your calls.

realistic

A believable substitute name from a built-in name list.

Jean Dupont → Pierre Lemaire

Marie Curie → Catherine Lambert

Always the same substitute for the same value (as with stable), and the text reads naturally, for example when a person has to read the result.

When to use:

  • stable: recognising the same person across documents, keeping a record of what was replaced, linking records to each other.
  • realistic: text that has to stay readable, such as a draft you show to someone. Combine with the locale field below.

Locale (realistic mode only):

The locale field selects which name list is used. Allowed values: fr_BE (default), nl_BE, de_BE. Ignored for other pseudonym modes.

Coverage in realistic: PERSON and ORG are replaced with names from the list. Every other type (e-mail, phone, IBAN, IP, date, amount and so on) gets the same tag as in stable mode: upii does not invent look-alike IBANs or phone numbers.

Caveats (apply to stable and realistic):

  • Repetition shows: a placeholder that appears several times tells the reader that the same value appears several times. This is true of any scheme where one value always gets the same placeholder.
  • One account only: the same value gets the same placeholder only inside your account. Two accounts do not get the same placeholder for the same value.
  • Spelling variants: upper or lower case, extra spaces, phone formatting (+33 6 12 = +33612) and spaces in an IBAN are ignored when a value is compared, so these variants get the same placeholder.
  • Realistic mode only: a substitute name can happen to be the name of a real person. The substitute does not reveal the original name.

Repeatable results: in realistic mode your account always gets the same substitutes for the same text, as long as the name list and your account do not change. GET /version returns the running build, its build time, the version of the name list and the identifiers of the detection models, so you can note which version of upii produced a result.

Example — Stable tag

curl -X POST https://upii.eu/protect \
  -H "Authorization: Bearer pii_your_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "Jean Dupont and Marie Curie", "pseudonym": "stable"}'

// Response
{"anonymized": "[PERSON_caa52d3b16] and [PERSON_627eb1ef64]", ...}

Example — Realistic substitution

curl -X POST https://upii.eu/protect \
  -H "Authorization: Bearer pii_your_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "Jean Dupont and Marie Curie", "pseudonym": "realistic", "locale": "fr_BE"}'

// Response (substitute Belgian French names; the same for your account every time)
{"anonymized": "[Pierre Lemaire] and [Catherine Lambert]", ...}

4. POST /protect

Protect a single text: pseudonymise the personal data it detects (replace it with placeholders), only detect it, or redact it.

Deprecated alias: POST /anonymize is the same endpoint under its former name, kept for existing integrations. It takes the same request, returns the same response and has the same limits; its answers carry the headers Deprecation: true and Link: </protect>; rel="successor-version". New integrations should use /protect.

Request Body

Field Type Required Description
textstringYes1 to 50,000 characters
modestringNoanonymize (default), detect, or redact
pseudonymstringNocounter (default), stable, or realistic. See Pseudonymization.
localestringNofr_BE (default), nl_BE, or de_BE. Used by pseudonym=realistic; ignored otherwise.

Example — Protect

curl -X POST https://upii.eu/protect \
  -H "Authorization: Bearer pii_your_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "Marie Lefèvre, marie@company.fr, +33 6 12 34 56 78"}'

Response

The field that holds the protected text keeps its name, anonymized, so existing integrations keep reading it.

{
  "anonymized": "[PERSON_1], [EMAIL_1], [PHONE_1]",
  "entities": [
    {"type": "PERSON", "index": 1, "placeholder": "PERSON_1",
     "value": "Marie Lefèvre", "start": 0, "end": 14},
    {"type": "EMAIL", "index": 1, "placeholder": "EMAIL_1",
     "value": "marie@company.fr", "start": 16, "end": 32},
    {"type": "PHONE", "index": 1, "placeholder": "PHONE_1",
     "value": "+33 6 12 34 56 78", "start": 34, "end": 52}
  ],
  "relationships": [],
  "latency": {"l1_ms": 0.08, "l2_ms": 28.5, "l3_ms": 0.01, "total_ms": 30.2}
}

Example — Detect Only

curl -X POST https://upii.eu/protect \
  -H "Authorization: Bearer pii_your_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "Contact jean@test.fr", "mode": "detect"}'

// Response: entities returned, anonymized is null
{"anonymized": null, "entities": [{"type": "EMAIL", ...}], ...}

5. POST /batch

Process up to 100 texts in a single request. Same response format as /protect but returns an array.

Request Body

Field Type Required Description
textsstring[]Yes1 to 100 texts, each 1-50,000 chars
modestringNoanonymize, detect, or redact
pseudonymstringNocounter (default), stable, or realistic. Applied to every text. See Pseudonymization.
localestringNofr_BE (default), nl_BE, or de_BE. Applied to every text in realistic mode.
curl -X POST https://upii.eu/batch \
  -H "Authorization: Bearer pii_your_key" \
  -H "Content-Type: application/json" \
  -d '{"texts": ["Jean Dupont", "marie@test.fr"], "mode": "anonymize"}'

// Returns: an array of /protect responses

6. POST /protect-document

Upload a document for per-page protection. Not part of the self-serve plans; it is arranged with us (see Plans & limits).

Deprecated alias: POST /anonymize-document is the same endpoint under its former name, kept for existing integrations, with the same request, response and limits and the same Deprecation and Link headers as above (successor: /protect-document).

Supported formats

PDF, DOCX, XLSX, CSV, TXT — max 50 MB, max 500 pages.

curl -X POST https://upii.eu/protect-document \
  -H "Authorization: Bearer pii_your_key" \
  -F "file=@contract.pdf"

Response

{
  "filename": "contract.pdf",
  "pages": [
    {"page": 1, "anonymized": "...", "entities": [...], "relationships": [...]},
    {"page": 2, ...}
  ],
  "totals": {"pages": 2, "entities": 14, "relationships": 3},
  "latency": {...}
}

7. Demo Mode

Try the API without authentication. Send requests to /api/protect (note the /api prefix) with no auth header.

Limit Value
Rate limit10 req/min per IP
Max text500 characters
Detected valuesMasked as ***
Links between itemsNot returned
Endpoints/api/protect only (the deprecated /api/anonymize still works)
# No auth needed
curl -X POST https://upii.eu/api/protect \
  -H "Content-Type: application/json" \
  -d '{"text": "Jean Dupont, jean@test.fr"}'

8. Entity Types

The API detects these types of personal data.

Languages. The upii website and dashboard are available in English, French and Dutch. The detection of names, organisations and addresses was built and checked on English, French and Dutch text, so those are the languages upii supports. Text in another language (German, Spanish, Italian, Portuguese and so on) is not supported: names in it can be missed.

Fixed formats. E-mail addresses, phone numbers, IBANs, national numbers, dates and amounts are found by rules rather than by the language model. They are checked mostly on Belgian documents; formats of other countries can be missed or given another type. Always review the result before you use it.

Substitute names. The locale values fr_BE, nl_BE and de_BE only choose the name list used by the realistic pseudonym style; German detection is not supported.

PERSON

Full names, honorifics

Marie Lefèvre, Dr. Schmidt

EMAIL

Email addresses

jean@company.fr

PHONE

Phone numbers

+33 6 12 34 56 78

ADDRESS

Street addresses

12 rue de Paris, 75001

ORG

Organization names

Goldman Sachs, SNCF

DATE

Dates in various formats

15/03/2025, March 2025

DATE_OF_BIRTH

A date of birth, found by its cue (born on, né le, geboren am, DOB); other dates stay DATE

né le 12/03/1975

AMT

Monetary amounts

1.500,00 EUR, $250

IBAN

IBANs and BIC/SWIFT

FR76 3000 6000...

FINANCIAL

Credit cards, account numbers

4111 1111 1111 1111

IP

IPv4 and IPv6 addresses

192.168.1.1

URL

Web URLs

https://example.com/path

ID

National IDs, SSN, passport

2 85 01 75 116 005 42

DURATION

Time durations

3 years, 6 months

PCT

Percentages

12.5%, 3 percent

9. Plans & limits

upii has three plans: Starter, Pro, Studio. Every plan has every feature; they differ only in how many pages you can protect each month. Allowances and prices are on the pricing page. Documents you add to a project in the app follow your plan's page allowance.

API limits by plan

The plan codes are starter, pro and studio. The API limits are the same on all three, and on the trial. While your trial or plan is active they allow texts of 50,000 characters and 60 requests per minute on the text endpoints; once the trial has ended or a plan is no longer active it is 500 characters and 10 requests per minute. The other limits in the table do not depend on the plan.

Plan code Rate Max text Document endpoint API keys
starter60 req/min while your trial or plan is active, else 1050,000 chars while active, else 500No1
pro60 req/min while your trial or plan is active, else 1050,000 chars while active, else 500No1
studio60 req/min while your trial or plan is active, else 1050,000 chars while active, else 500No1

Higher limits (more API keys, the document endpoint, higher rates) are arranged with us: write to support@chut.app.

10. Errors

All errors return JSON with a detail field.

Code Meaning
400Bad request (text too long, unsupported file, etc.)
401Missing or invalid API key
403Feature not available on your tier
413File too large or too many pages
422Validation error (invalid JSON, missing fields)
429Rate limit exceeded — wait 60 seconds
503Service starting up — retry in 15 seconds
504Inference timeout

11. API Key Management

Manage keys from the dashboard or programmatically via cookie-authenticated endpoints.

Method Path Description
GET/keysList your API keys
POST/keysCreate a new key (body: {"name": "..."})
PATCH/keys/{id}Rename a key
DELETE/keys/{id}Revoke a key (permanent)

Questions? Contact support@chut.app