EU Mode API
Send EU Mode a request on its way in, a reply on its way out, or the actions your agent is about to take. It answers allow, mask, escalate or block. Your app routes on that answer. We never hold your model key and never call your model.
- Until our data processing agreement is signed with your company, send no personal data to the API.
- During the trial there is no uptime guarantee: the guard can be briefly unavailable.
1Before you start
- ·Sign in to the console to create a key. The key is shown once. You can revoke or restore it there. A key that calls HaloGuard EU can also call EU Mode.
- ·Call the API from your server. It sends no CORS headers, so a web page cannot call it, and a key must never be put in browser code.
- ·Every key starts in shadow mode (section 4). Switch a key to enforce on the API keys page in the console when you are ready.
2The call
POST https://api.astroware.ai/v1/chat/completions. The same endpoint, key and price as HaloGuard EU; only the model name differs.
- Headers
Authorization: Bearer <key>andContent-Type: application/jsonmodel"eu-mode". Required.messages- A list of messages. The last one decides what is checked (table below).
| The last message | checked | What runs |
|---|---|---|
A user message | request (on its way in to your model) | AI Act check and personal-data check |
An assistant message with text | reply (on its way out of your model) | AI Act check |
An assistant message with tool_calls (OpenAI shape, up to 20 calls) | action (what your agent is about to do) | Auto Mode, the dangerous action check |
- ·Requests and replies: the same rules as HaloGuard EU. Only
userandassistantmessages, text only. Atoolorsystemmessage gives 400. Up to about 7,800 tokens in a conversation. - ·Actions: the user messages are what the person asked for, and earlier tool calls give the context.
toolmessages (tool results) and asystemmessage are accepted and not used. Up to 400,000 characters in the conversation, and at most 20 tool calls in the last message. - ·For all three: a body of up to 2 MB, no streaming.
curl https://api.astroware.ai/v1/chat/completions \ -H "Authorization: Bearer $HALOGUARD_KEY" -H "Content-Type: application/json" \ -d '{"model": "eu-mode", "messages": [ {"role": "user", "content": "Write to Anna Schmidt at anna.schmidt@example.com about the Q3 report."}]}'
3The answer
Status 200 and an OpenAI-style chat completion. choices[0].message.content is itself a JSON string. Parse it. These three were recorded on the live API on 30 October 2026, with a key in shadow mode.
A request with a name and an email address:
{
"model": "eu-mode",
"messages": [
{
"role": "user",
"content": "Write to Anna Schmidt at anna.schmidt@example.com about the Q3 report."
}
]
}{
"id": "hg-b1a320fe4cca4ae78b339bc90d89c1df",
"created": 1793333504,
"model": "eu-mode",
"object": "chat.completion",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "{\"decision\": \"mask\", \"mode\": \"shadow\", \"would_decide\": \"mask\", \"checked\": \"request\", \"checks\": [{\"check\": \"ai_act\", \"decision\": \"allow\", \"policy\": \"none\"}, {\"check\": \"privacy\", \"decision\": \"mask\", \"personal_data\": [\"email\", \"person\"], \"masked_messages\": [{\"role\": \"user\", \"content\": \"Write to [PERSON_1] at [EMAIL_1] about the Q3 report.\"}], \"map\": {\"[PERSON_1]\": \"Anna Schmidt\", \"[EMAIL_1]\": \"anna.schmidt@example.com\"}, \"privacy_mode\": \"all\"}]}",
"role": "assistant",
"provider_specific_fields": {
"refusal": null
}
},
"provider_specific_fields": {}
}
],
"usage": {
"completion_tokens": 8,
"prompt_tokens": 24,
"total_tokens": 32,
"haloguard_eu_mode": {
"checked": "request",
"decision": "mask",
"would_decide": "mask",
"mode": "shadow"
}
}
}A reply:
{
"model": "eu-mode",
"messages": [
{
"role": "user",
"content": "Summarise our Q3 report."
},
{
"role": "assistant",
"content": "Revenue rose 4 percent in Q3; costs were flat."
}
]
}{
"id": "hg-306e6c0431c64c3fb9ad25fb45355321",
"created": 1793333344,
"model": "eu-mode",
"object": "chat.completion",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "{\"decision\": \"allow\", \"mode\": \"shadow\", \"would_decide\": \"allow\", \"checked\": \"reply\", \"checks\": [{\"check\": \"ai_act\", \"decision\": \"allow\", \"policy\": \"none\"}], \"skipped\": [{\"check\": \"privacy\", \"reason\": \"reply\"}]}",
"role": "assistant",
"provider_specific_fields": {
"refusal": null
}
},
"provider_specific_fields": {}
}
],
"usage": {
"completion_tokens": 8,
"prompt_tokens": 30,
"total_tokens": 38,
"haloguard_eu_mode": {
"checked": "reply",
"decision": "allow",
"would_decide": "allow",
"mode": "shadow"
}
}
}An action (the agent is about to send the email the person asked for):
{
"model": "eu-mode",
"messages": [
{
"role": "user",
"content": "Email the Q3 summary to anna.schmidt@example.com"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": {
"name": "send_email",
"arguments": "{\"to\": \"anna.schmidt@example.com\", \"subject\": \"Q3 summary\", \"body\": \"Revenue rose 4 percent in Q3.\"}"
}
}
]
}
]
}{
"id": "hg-87f256ead4024dcebcbe4ef6e7b86be4",
"created": 1793333506,
"model": "eu-mode",
"object": "chat.completion",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "{\"decision\": \"allow\", \"mode\": \"shadow\", \"would_decide\": \"allow\", \"checked\": \"action\", \"checks\": [{\"check\": \"action\", \"decision\": \"allow\", \"engine\": \"auto-mode-v6\", \"calls\": [{\"tool\": \"send_email\", \"decision\": \"allow\", \"band\": \"judged\", \"reason\": \"low risk, or asked for by the person\", \"p_yes\": 0.9248, \"threshold\": 0.5}]}], \"skipped\": [{\"check\": \"ai_act\", \"reason\": \"action\"}, {\"check\": \"privacy\", \"reason\": \"action\"}]}",
"role": "assistant",
"provider_specific_fields": {
"refusal": null
}
},
"provider_specific_fields": {}
}
],
"usage": {
"completion_tokens": 0,
"prompt_tokens": 54,
"total_tokens": 54,
"haloguard_eu_mode": {
"checked": "action",
"decision": "allow",
"would_decide": "allow",
"mode": "shadow",
"judged": 1
}
}
}decision is what your app routes on:
decision | What your app does |
|---|---|
allow | Send the text or run the action as it is. |
mask | Send masked_messages on instead. Putting the originals back into your model's reply with map is your step. |
escalate | Hold it for a person to approve. You also get escalate when a check could not answer, so an outage never reads as a clean allow. |
block | Stop. |
The most severe check decides: block, then escalate, then mask, then allow.
Each entry in checks has the check's own answer:
- ·
ai_act:decisionallow or block, andpolicy(the policy that matched, ornone). A block also carrieslabelandact_area, as on the HaloGuard EU page. - ·
privacy:decisionallow or mask,personal_data(the kinds found), and on maskmasked_messagesandmap.privacy_modeis your company's masking setting. - ·
action:callshas one entry per tool call:tool,decision(allow, escalate or block) andreason.band,p_yesandthresholdsay how that decision was reached. They can change between versions; route ondecision. - ·A check that could not answer says
unavailable, and the answer is escalate. - ·A check that did not run is named in
skipped, with the reason.
usage also carries haloguard_eu_mode: checked, decision, would_decide and mode, and for an action judged, the number of calls that were judged. It holds no text.
Ignore keys you do not know.
4Shadow or enforce
Each key has a mode. You set it on the API keys page in the console; a call cannot change it.
- ·Shadow (every key starts here): an AI Act block and the action check's answer do not stop anything.
decisionlets the traffic through, andwould_decidesays what enforce would have answered. Your company's masking still applies in shadow: a request with personal data still answers mask. The Shadow report on the Usage page counts what would have been blocked or escalated. - ·Enforce:
decisionis what the checks found.
Write your routing once. Switching a key from shadow to enforce needs no change to your code, and takes effect on the key's next call.
5Errors
The body is {"error": {"message", "type", "param", "code"}}. Branch on the status, not on the message text: the text can change.
As on the HaloGuard EU page, with these differences:
| Status | When |
|---|---|
| 403 | A model name your key may not call (type is key_model_access_denied) |
| 400 | For an action: more than 20 tool calls; tool_calls that is not a non-empty list; a tool call with no function.name; function.arguments that is not JSON, or not a JSON object; a role other than user, assistant, tool or system; a conversation over 400,000 characters |
A check that could not run is not an error: the answer is 200 with decision escalate.
6Limits and price
These are today's settings and can change.
- ·60 requests a minute per key.
- ·Trial: the same $5 budget for your company as HaloGuard EU, shared by up to 5 active keys.
- ·Price: $30 / billion input tokens, counted on the messages you send. The answer is free. The same as HaloGuard EU.
7Your data
We see only what you send and keep none of its text. Usage records hold no text. For an action check, we send the tool call, with the earlier tool calls and the user messages in that request, to an AI provider in the EU, which judges whether the person asked for the action and keeps no copy. When the AI Act check is unsure about a message, we send that message, with earlier messages of the conversation, to the same provider, which decides and keeps no copy. Our Terms and our data processing agreement say how your data is processed, and by whom. Until our data processing agreement is signed with your company, send no personal data to the API.
8Not promised
No uptime guarantee: the guard can be briefly unavailable (a 503). Speed is not promised. The limits and budget above are today's settings and can change.
Auto Mode judges whether an action fits what the person asked for. It does not check what the tool returns. Security checks (prompt injection, jailbreaks) are in development and not in the eu-mode model yet.