AI Data Veil removes personal data from texts before you use them anywhere else: customer emails, tickets, documents. The detection runs on our own servers in Europe, no external AI service sees your text. You pay for tokens only.
1. Sign in (Login / Register) and create an API key under Setup → API keys. 2. Send a text:
curl https://api.aidataveil.eu/v1/anonymize \
-H "Authorization: Bearer $VEIL_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Hello, I am Anna Schmidt from Berlin, anna@example.com.", "lang": "en"}'The answer contains the veiled text and the list of what was replaced:
{
"text": "Hello, I am [PERSON_1] from [LOCATION_1], [EMAIL_1].",
"replacements": [
{ "type": "PERSON", "original": "Anna Schmidt", "replacement": "[PERSON_1]" },
{ "type": "LOCATION", "original": "Berlin", "replacement": "[LOCATION_1]" },
{ "type": "EMAIL", "original": "anna@example.com", "replacement": "[EMAIL_1]" }
],
"infos": {},
"usage": { "chars": 55, "tokens": 18, "size_class": "S", "weight": 1, "billed_tokens": 18 },
"meta": { "lang": "en", "mode": "technical", "total_ms": 240 }
}Base address: https://api.aidataveil.eu. Send your key in the header Authorization: Bearer veil_…. Keep keys secret. An account can have up to 5 keys, and you can delete a key at any time.
| Field | Type | Meaning |
|---|---|---|
| text | string, required | The text to process. |
| lang | de en fr es it | Language of the text (default de). |
| mode | technical | fictional | [PERSON_1]-style placeholders (default) or invented names and values. Within one text the same term always gets the same replacement. |
| prefix | string, optional | Placed before the technical names: with B1 you get [B1_PERSON_1] instead of [PERSON_1]. This keeps placeholders apart when you combine several texts or parts with variable content. Letters, digits, - and _, up to 16 characters. Not used with fictional. |
| ruleset | string | Name of one of your rule sets. Without it the key’s default rule set applies. |
| allowlist | list of strings | Terms that must never be replaced, for this request only. |
| options.mask_countries | boolean | Also replace countries and regions (default off). |
| rules | list of {label, pattern, mask} | Up to five own regex fields (label up to 40, pattern up to 500 characters). Matches appear under infos. |
| async | boolean | true: always process in the background. false: an immediate answer is required (see below). |
| Field | Meaning |
|---|---|
| text | The veiled text. |
| replacements | Every replaced term: type, original, replacement. Use it to map your answer back. |
| infos | Values of your own regex fields, as they stand in the veiled text. |
| usage | Size of the text: chars, tokens, size_class and billed_tokens. |
| meta | Language, mode and timing. |
Texts up to 1,000 tokens (about 3,500 characters, a long email) are answered immediately. Longer texts are accepted and processed in the background; you fetch the result later. Short requests are not delayed by this: they always go first.
HTTP/1.1 202 Accepted
{
"request_id": "6549e33e1121a934ac0ece670c8b5017",
"status": "queued",
"tokens": 15900,
"size_class": "XL",
"result_url": "/v1/requests/6549e33e1121a934ac0ece670c8b5017",
"poll_after_seconds": 100
}Fetch the result with GET result_url. The status is queued, processing, done or failed. A finished result comes in blocks of up to 10,000 characters, each with a link to the next one:
GET /v1/requests/6549e33e1121a934ac0ece670c8b5017
{
"request_id": "6549e33e1121a934ac0ece670c8b5017",
"status": "done",
"block": 1,
"blocks": 7,
"next_url": "/v1/requests/6549e33e1121a934ac0ece670c8b5017?block=2",
"next_is_confirmation": false,
"text": "…first 10,000 characters, veiled…",
"replacements": [ … only those used in this block … ],
"infos": {}, "usage": { … }, "meta": { … }
}[PERSON_1] always means the same term. Each block lists the replacements it contains.infos, usage and meta are in the first block.410 block_released if you ask again). If a connection drops, simply request the same block again.next_url of the last block (next_is_confirmation: true) confirms receipt and deletes the request. Without confirmation the result is deleted 60 minutes after the last block was fetched, after 24 hours at the latest. DELETE /v1/requests/{id} deletes at once."async": true processes even short texts in the background (same price). "async": false demands an immediate answer; a text that is too long is rejected with 413 too_large_for_immediate.A complete example in Python:
import os, time, requests
API = "https://api.aidataveil.eu"
H = {"Authorization": "Bearer " + os.environ["VEIL_KEY"]}
def anonymize(text, lang="de"):
r = requests.post(API + "/v1/anonymize", headers=H, json={"text": text, "lang": lang})
r.raise_for_status()
if r.status_code == 200: # short text: the answer is here
return r.json()["text"]
url = API + r.json()["result_url"] # 202: processed in the background
while (block := requests.get(url, headers=H).json())["status"] in ("queued", "processing"):
time.sleep(10)
if block["status"] == "failed":
raise RuntimeError(block["error"])
parts = []
while True:
parts.append(block["text"])
if block["next_is_confirmation"]: # last block: confirm, the data is deleted
requests.get(API + block["next_url"], headers=H)
return "".join(parts)
block = requests.get(API + block["next_url"], headers=H).json()Under Setup → Rule sets you collect your own terms: words that are always replaced (project names, customer numbers) and words that are never replaced (your company, products). Assign a rule set to a key as its default, or name it per request with ruleset.
Besides terms, a rule set can contain fields: values of a fixed shape such as an order number. You create a field in Setup → Rule sets by pasting a sample text and clicking the value; we build the pattern for you, you give it a technical name such as Ordernumber. Every match is returned under infos.Ordernumber and, if you like, replaced in the text. Fields of the rule set and rules of the request work together.
usage.POST /v1/tokens with {"text": "…"} returns the token count and whether the text would be answered immediately. It does not process the text and costs nothing.429 quota_exceeded until the next month or until the plan is upgraded. Continuing and billing the extra tokens afterwards will come with the paid plans; we set it up for you.| Status | Error | Meaning |
|---|---|---|
| 400 | text_required, unsupported_language, unsupported_mode, invalid_rule, unknown_ruleset | The request is incomplete or a value is not allowed. |
| 401 | missing_api_key, invalid_api_key | The key is missing, wrong or deleted. |
| 404 | not_found, block_not_found | Unknown request, a request of another account, a confirmed or expired result, or a block that does not exist. |
| 410 | block_released | The block was released after the next block had been fetched. |
| 413 | too_large_for_immediate | Too long for an immediate answer with "async": false. |
| 429 | quota_exceeded | The monthly limit is reached and the account is set to stop. |
| 502 | anonymizer_unavailable | Temporarily not available. Try again shortly. |