Calling your model
Three ways to put Chersus around a model call: relay in one request, the gateway behind your SDK, or Chersus around a model call you make yourself.
You can put Chersus around a model call in three ways. Pick by who makes the call.
| Relay | Gateway | Your own model call | |
|---|---|---|---|
| Who calls the model | Chersus | Chersus, via your SDK | You |
| Requests | One | Your usual SDK call | One before yours, a second only to check the answer |
| What Chersus sees | Prompt and answer | Prompt and answer | The prompt, and the answer only if you check it |
| Pick it when | You want one request | You want to keep your SDK | You want the map, or use a provider relay lacks |
With a tokenize in the workflow, the model sees placeholders instead of the personal data Chersus found. All three run the same operations and workflows.
Relay
Relay is the fourth step next to detect, extract and transform. You send one request. Chersus cleans the prompt, calls the model, checks the answer, and returns it rehydrated.
A workflow or policy relays when it has a relay. Its detect holds the checks on the model’s answer. Rehydration is automatic when a tokenize ran. Answer checks are detects only and run before rehydration, so they see placeholders. A detect.pii.entities there flags only personal data the model brought in itself.
curl -X POST https://api.chersus.com/v1/run \
-H "Authorization: Bearer $CHERSUS_KEY" \
-H "X-LLM-Key: $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "Hi, I am Jane Doe. Can you check invoice #4821?",
"detect": [{ "op": "secrets.credentials", "block": true }],
"transform": ["pii.tokenize"],
"relay": {
"provider": "openai",
"model": "gpt-4.1-mini",
"detect": [{ "op": "secrets.credentials", "block": true }]
}
}'Save the same workflow as a policy and the request shrinks to policy and input. The response:
{
"trace_id": "chs_01J9X6C2...",
"status": "completed",
"decision": "allow",
"output": "Hi Jane Doe, invoice #4821 was paid on 3 September. Anything else I can check?",
"relay": {
"provider": "openai",
"model": "gpt-4.1-mini",
"duration_ms": 812,
"detect": { "secrets.credentials": { "verdict": "pass", "score": 0.00 } }
},
"transform": [{ "op": "pii.tokenize", "changes": 1 }],
"detect": { "secrets.credentials": { "verdict": "pass", "score": 0.01 } },
"usage": { "billed": { "unit": "characters", "input": 47, "output": 78, "quantity": 125 } }
}
output is the answer with the real values restored. relay names the model that answered, and relay.detect holds the checks on the answer. A relay run bills the characters you send plus the characters you get back, and detect and extract stay free. Billing has the rest.
When a check blocks
A block by a check means the model is never called: no output, no relay. A block by an answer check means the model answered and Chersus withheld it: relay is present, output is not. A flag is a model verdict, so treat a block as a configured stop, not a safety guarantee. Errors lists real failures.
Streaming
Set "stream": true, or send Accept: text/event-stream, and the answer arrives as server-sent events: delta with already rehydrated text, detect for each answer check, done with usage. A run with routing opens with a route event. Answer checks run per batch, at sentence or paragraph boundaries. When a blocking check flags, a blocked event is sent and the stream stops.
Multi-turn
Placeholders already stay the same on every turn, so you send nothing extra. To keep one conversation’s placeholders apart from every other conversation, send the same X-Session-Key on each of its turns, any opaque id such as conv_8f21c0 (see the token map).
Choosing a model
Where the model runs is your choice of account, region and deployment. Azure Data Zones, Bedrock eu.* profiles and Vertex AI EU locations keep processing in the EU. A global deployment does not.
| Provider | Value for provider | Other fields | Credential in X-LLM-Key |
|---|---|---|---|
| OpenAI | openai |
model |
Your OpenAI API key |
| Anthropic | anthropic |
model |
Your Anthropic API key |
| Mistral | mistral |
model |
Your Mistral API key |
| Azure OpenAI, such as an EU Data Zone | azure |
endpoint, deployment |
Key of that Azure resource |
Amazon Bedrock, such as an eu.* profile |
bedrock |
model, region |
An Amazon Bedrock API key |
Google Vertex AI, such as the eu multi-region |
vertex |
model, project, location |
A Google OAuth access token |
| Provided model, EU-hosted | chersus |
model |
nothing |
A relay that lacks a field its provider needs is rejected with relay_field_missing before anything runs. Bedrock API keys and Vertex access tokens are short-lived, so fetch a fresh one instead of storing it.
Bring your own account and your provider bills you directly. The credential serves one request, and only its fingerprint appears in the audit trace. With a provided model, the provider’s cost plus 5% appears as its own line, usage.provided_model. The Chersus key is covered under authentication.
Routing
One relay can choose between models. It routes on one value, and each outcome goes to a model, to no model, or to the relay’s own model. Here a prompt with personal data goes tokenized to an EU model, and a clean prompt goes to the default:
{
"input": "Hi, I am Jane Doe. Why was I charged twice for invoice #4821?",
"detect": ["pii.entities"],
"transform": [{ "op": "pii.tokenize", "when": ["detect.pii.entities:flagged"] }],
"relay": {
"provider": "openai",
"model": "gpt-4.1-mini",
"routing": {
"by": "detect.pii.entities",
"models": {
"eu": { "provider": "mistral", "model": "mistral-small-latest" }
},
"cases": { "flagged": "eu" }
}
}
}
routing.by takes a detect in detect (outcomes flagged and clean), extract.language.code (an ISO 639-1 code such as de), or a name from conditions (true and false). models names the models the cases can use. The response names the case that held in route, and exactly one model answers per run. A case for an outcome by cannot take is rejected with unknown_outcome, and a case naming a model not in models with unknown_model. Workflows covers conditions.
A case of null skips the model and the answer checks: no relay, and output is the last transform’s text, still tokenized if a tokenize ran.
X-LLM-Key carries the relay’s own key, and each entry in models gets X-LLM-Key- plus its name:
X-LLM-Key: $OPENAI_API_KEY
X-LLM-Key-eu: $MISTRAL_API_KEY
The header follows the name, not the provider, so two models on one provider can use two accounts. Send every key up front: a missing key is rejected with llm_key_missing before anything runs.
Gateway
The gateway is relay without a new API. Point your OpenAI or Anthropic SDK at gw.chersus.com, add your Chersus key as a default header, and the default policy runs on every request and response.
curl https://gw.chersus.com/openai/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "X-Chersus-Key: $CHERSUS_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4.1-mini",
"messages": [{ "role": "user", "content": "Hi, I am Jane Doe. Can you check invoice #4821?" }]
}'For the Anthropic SDK, set baseURL or base_url to https://gw.chersus.com/anthropic and add the same X-Chersus-Key default header. Your key still travels as x-api-key, and OpenAI keys as Authorization: Bearer.
| Header | Required | Purpose |
|---|---|---|
| The SDK’s own key header | yes | Your LLM key, unchanged. Used once, never stored |
X-Chersus-Key |
yes | Your Chersus API key |
X-Chersus-Policy |
no | A policy ref that overrides the gateway default for this request |
X-Session-Key |
no | Placeholders stable within that key only |
One policy, set in the dashboard as the gateway default, applies to all traffic on your key. It needs a relay step whose provider matches your SDK, and the model in your request wins over the model in the policy.
Bodies stay in the provider’s format, streaming works as in relay, and provider rate limits and errors come back unchanged. A block from the policy arrives as a 4xx in the provider’s error format with blocked_by in the body, so your SDK raises it as a normal API error.
Support for tool calls, JSON mode and images is being confirmed. Until then, the gateway passes and checks text in and text out. Billing follows relay, with nothing extra. The API reference lists the gateway routes.
Your own model call
You keep the model call on your side. Use any provider or gateway, streamed or not. Chersus cleans the prompt on the way in and hands you the token map, and forwards nothing.
Putting the real values back into the answer is your job. The steps below show how.
Before the model
curl -X POST https://api.chersus.com/v1/run \
-H "Authorization: Bearer $CHERSUS_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": "customer-support@3",
"input": "Hi, I am Jane Doe. Can you check invoice #4821?"
}'The response carries the tokenized text in output, the detect and extract results, and the token map:
{
"trace_id": "chs_01J9X5A1...",
"status": "completed",
"policy": "customer-support@3",
"decision": "allow",
"output": "Hi, I am <PERSON_eb7805d77a13>. Can you check invoice #4821?",
"transform": [{ "op": "pii.tokenize", "changes": 1 }],
"detect": { "secrets.credentials": { "verdict": "pass", "score": 0.02 } },
"tokens": { "<PERSON_eb7805d77a13>": "Jane Doe" },
"usage": { "billed": { "unit": "characters", "input": 47, "output": 60, "quantity": 107 } }
}
A block is a normal outcome, not an error: decision is block, and output, transform and tokens are omitted.
Your model call
Send the output of the first call to your model. Chersus is not on this leg. Keep the tokenized history, not the raw one, and one tokens map per conversation.
After the model
After the model answers, put the values back yourself: replace each token in the answer with its value from the map, as putting values back explains. To check the answer too, for example for secrets the model should not repeat, send it as an ordinary run with detects first. Do that before you put the values back, so Chersus sees placeholders only. Each call takes its own conditions.
{
"input": "Hello <PERSON_eb7805d77a13>, invoice #4821 was paid on 3 September.",
"detect": [{ "op": "secrets.credentials", "block": true }]
}
{
"decision": "allow",
"output": null,
"detect": { "secrets.credentials": { "verdict": "pass", "score": 0.00 } }
}
With a streamed answer, batch it yourself at sentence or paragraph boundaries and run the check on each batch. Emit a batch on allow and stop on block. The first run bills like any run. A check on the answer bills its input characters only, because detects hand nothing back. Billing has the rest.
The token map
Tokenize swaps each personal value for a typed token and hands you the map. In relay, Chersus puts the values back; without relay, you do. A token is the type plus 12 hex characters, a keyed hash of the type and the value, made with a secret Chersus holds for your account. Examples are <PERSON_eb7805d77a13> and <EMAIL_33b537052b41>.
The style is set per policy in tokens.style: angle (default), bracket ([PERSON_eb7805d77a13]) or curly ({{PERSON_eb7805d77a13}}). Angle brackets are the default because current models keep them intact best.
Reuse across calls
The same value always gets the same token in every run on your account, in any call, and you pass nothing along. Jane is <PERSON_eb7805d77a13> on turn one and on turn forty, so the model sees one coherent conversation and identical prompts stay identical. To keep one map per conversation, pass the tokens map from an earlier response, and the response returns it merged with the new tokens. Run the policy on each new user message only.
Putting values back
Replace each token in the answer with its value from the map. Models sometimes change a token on the way through: other capitals, spaces inside the brackets, another bracket style. So match tokens without regard to case, whitespace inside the brackets or bracket style.
Leave a token you cannot find in the map as it is, and decide whether the answer is still usable. In relay, Chersus does exactly this for you.
Session keys
Without a session key, a value gets the same token across your whole account, so a model provider could see Jane’s token in several conversations. Send an X-Session-Key, any opaque id, and Chersus mixes it into the hash. Tokens then match within that key and differ in every other. This works in any run, relay included.
Chersus uses the key to make the tokens and keeps nothing: no session to expire, no data. In relay there is no map to hand back, because tokenizing and putting values back happen inside one request.
What Chersus keeps
Chersus keeps nothing from the text, in either pattern. The map is made in memory, returned to you (or used for the answer, in relay), and gone. Session keys are not kept either. What Chersus keeps is the secret your account’s tokens are made with, a random value that holds no data. The audit trace holds hashes only and never the map.
The map holds real values. Store it as carefully as the raw message, do not log it, and do not keep it in plaintext next to the conversation. Zero retention describes Chersus, not your system.