Skip to content

Join the waitlist

Enter your email and we will send you a one-time code. No password to remember.

Workflow builder

Start with examples

Conditions

Detect
Extract
Transform
Relay
Relay

Documentation

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.

Last updated Also as Markdown:this pagellms.txt

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.