Skip to main content
The firewall inspects what your application sends. It pseudonymizes personal data in a request before it reaches a model and restores the original values in the response, so the model never sees the raw data while your application still gets a normal answer. It can also check every prompt for instructions hidden inside the text. It reads text. In Akumi Chat that now includes a document: a spreadsheet, a presentation, a PDF, a scanned page and a picture printed on a page are read into text when somebody uploads one, and those words are masked with the message they arrived on. On /v1 a file is bytes in your request and is passed on untouched, because there is no upload step to read it at. An image is never read into text in either place, because a model needs the pixels and pixels cannot be masked. See Attachments for the whole of it. You will find it under Services > Firewall. The page has two tabs: Overview, where you decide what happens to each kind of personal data, and Advanced configuration, for the prompt-injection check and your own list of terms.

Turning the firewall on

Use the switch behind the menu in the top right of the page. It takes effect immediately, with no separate save step, and the change is recorded in your audit log. Every service starts off, including this one. A new organization has nothing enabled until somebody turns it on, so a request from a fresh account carries personal data to the model intact. Turn the firewall on before you send anything you would not want a provider to hold. Switching it off sends requests to models with personal data intact. External, non-EU routing stays available only where the workspace has already accepted non-compliant routing.

How pseudonymization works

Detected values are replaced with tokens such as <PERSON_1> or <EMAIL_ADDRESS_1>. The mapping from token to original value lives only for the duration of the request and is never sent to a provider. After the model responds, the tokens in the output are replaced with the original values before the response reaches you. The same value always maps to the same token within a request, so the model can still reason about relationships: two mentions of the same person stay consistent.

What it looks for

Ten kinds, and all ten are detected on every request: Four of these are checked rather than merely matched. The Dutch BSN is validated against its checksum, the German tax identification number against its own, VAT numbers against the per-country format, and passport numbers by shape. Validating rather than pattern-matching keeps an order reference that happens to be nine digits long from being treated as somebody’s national identifier. A row on the Overview tab decides what happens to what the firewall finds. It never decides whether the firewall looks.

What happens when it finds something

Each kind carries its own answer, chosen from three:
  • Pseudonymize. Replace the value with a placeholder before the provider sees it, and put the original back in the answer.
  • Ask. Show what will be replaced and wait for a person to decide.
  • Block. Refuse the request when that kind of data is present.
A name should probably be replaced and an IBAN should probably stop the request, which is why the answer is per kind rather than one setting for everything. There is no workspace-wide default action. A kind you have never answered is pseudonymized, which is the safe direction to be wrong in when the platform adds a kind after you last looked at the page. To change everything at once, use Set all to above the table; it writes every row rather than storing a second setting the rows inherit from. Ask needs somewhere to ask. Akumi Chat can suspend a run and put the question on screen; /v1/chat/completions cannot. An organization without Akumi Chat is not offered Ask at all, and the option says why rather than disappearing. The question names what was found, shows the value rather than the placeholder it would become, and says where in the conversation it came from, including which command printed it when a client tells us. A person cannot judge “PERSON detected” on a file listing; they can judge their own username appearing in a path.

Your own terms

The Advanced tab holds a list of literal strings the general detectors cannot know about: a project codename, an internal system, a customer you do not want named. They are matched whole and case-insensitively, and they carry their own answer from the same three actions, set once for the whole list. A new term applies to the conversations you have already had. Adding one re-masks every message of every conversation in the workspace, which is the point of adding it. That pass costs detection on the whole history, so add everything you have in one go rather than one term at a time. Terms are matched in what you send, and never in what the model writes back.

Mask on EU-hosted models too

External models are always masked. On EU-hosted models your data never leaves the EU, so masking there is a choice rather than a requirement, and it is off by default. With it on, the model no longer sees the values, and a question that depends on them gets a worse answer.

Checking prompts for hidden instructions

The Advanced tab carries one more switch: Check every prompt for hidden instructions. A hidden instruction is one written for the model rather than for you, and a pasted web page, a document or a tool result can carry one.
  • It is on or off, and nothing else. When it is on, a prompt that carries an injection is refused. There is no observe-only setting, because recording an attempt and running it anyway is not a defence.
  • It reads the prompt only, never the answer. A refusal therefore reaches you before any model is called, and a streaming request is refused with the same guard_violation 422 a non-streaming one receives, rather than a truncated stream.
  • It is a judgement rather than a match, so it will be wrong in both directions sometimes. There is no sensitivity to tune; the threshold is fixed and chosen for a low false-positive rate.
  • It fails closed. If the classifier cannot answer, the request is refused and the reason names our outage rather than your prompt.
  • It is metered per request, whatever the prompt is made of. Content the platform has already scored is free, so replayed conversation history costs nothing.
The card shows what it caught in the last 30 days, and links straight to those entries in the audit log.

Remembering what somebody allowed

Answering always allow records the value so it is never proposed again. Exactly that value, matched case-insensitively and never as a pattern: allowing jellespekken does not exempt jelle@customer.com. Exemptions have two tiers.
  • Personal. Applies only to messages attributable to the person who answered. Anyone may create one.
  • Workspace-wide. Applies to everybody’s messages. Creating one needs the firewall permission.
The split exists because names collide. If somebody exempts their own name and a customer happens to share it, a workspace-wide entry would leave that customer’s name unprotected for everyone, decided by a person with no authority to decide it. A personal entry cannot reach anybody else’s text. Both tiers are readable and revocable over the chat API, and both directions are audited with who decided. Removing an exemption re-arms masking immediately, including on conversations that were already stored. An exemption stops a value being detected, so it cannot distinguish two people who share a name. Exempting “Jan Jansen” means Jan Jansen is not masked in your messages, whichever Jan Jansen is meant. Allowing once limits what the model sees, not what the platform keeps. Answering allow once masks the value for that request. If the workspace has Memory, the same message is also read for durable facts, and that reading happens on the original text rather than the masked form. So a colleague’s name you allowed once is stored in memory as the name. That is deliberate. A memory of <PERSON_1> is worth nothing: it cannot be matched against a later question, and a person asking what the assistant knows about them would be shown a placeholder. The store is Akumi’s own and EU-resident, and a stored fact is masked again every time it re-enters a prompt, because recalled facts are injected before the firewall and pseudonymized with everything else. So “allow once” is a decision about one request rather than about the record. Erase the fact if you want it gone: memory is readable and erasable per person, and erasing is the control for what the platform keeps.

Routing to external models

Pseudonymized data is still personal data. Sending it to a non-EU or third-party model remains a data transfer with its own obligations. External routing is only allowed when the firewall is active for the request, or when an administrator has recorded an explicit acknowledgement that accepts responsibility for the transfer. Without one of those, the request is refused rather than sent.

What is recorded

Each firewalled request records that the firewall ran and how many values it handled, never the original values. Each prompt the injection check reads records its verdict, never the prompt. You can review both in the audit log. The firewall governs what leaves the platform, not what other services you enable store. The Cache stores the final answer with the real values restored, and Observability payload capture stores what each stage was given, pseudonymized wherever the firewall ran. Each of those pages says what it keeps and how to delete it.