# Calling your model | Chersus

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 2026-10-24·Also as Markdown:[this page](/en/docs/calling-your-model.md)[llms.txt](/llms.txt)

You can put Chersus around a model call in three ways. Pick by who makes the call.

|  | [Relay](#relay) | [Gateway](#gateway) | [Your own model call](#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](/en/docs/operations/) and [workflows](/en/docs/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.

inputdetectextracttransformabortrelayyour modelrehydrateoutput
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 }]
  }
}'
```

```
const res = await fetch("https://api.chersus.com/v1/run", {
method: "POST",
headers: {
  Authorization: "Bearer " + process.env.CHERSUS_KEY,
  "X-LLM-Key": process.env.OPENAI_API_KEY ?? "",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  input: userMessage,
  detect: [{ op: "secrets.credentials", block: true }],
  transform: ["pii.tokenize"],
  relay: {
    provider: "openai",
    model: "gpt-4.1-mini",
    detect: [{ op: "secrets.credentials", block: true }],
  },
}),
});
const result = await res.json();
```

```
result = requests.post(
  "https://api.chersus.com/v1/run",
  headers={
      "Authorization": "Bearer " + os.environ["CHERSUS_KEY"],
      "X-LLM-Key": os.environ["OPENAI_API_KEY"],
  },
  json={
      "input": user_message,
      "detect": [{"op": "secrets.credentials", "block": True}],
      "transform": ["pii.tokenize"],
      "relay": {
          "provider": "openai",
          "model": "gpt-4.1-mini",
          "detect": [{"op": "secrets.credentials", "block": True}],
      },
  },
).json()
```

Save the same workflow as a [policy](/en/docs/policies/) 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](/en/docs/api-reference/#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](/en/docs/api-reference/#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](#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](#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](/en/docs/api-reference/#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](/en/docs/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?" }]
}'
```

```
const client = new OpenAI({
baseURL: "https://gw.chersus.com/openai/v1",
defaultHeaders: { "X-Chersus-Key": process.env.CHERSUS_KEY },
});
```

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](/en/docs/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](#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](/en/docs/api-reference/#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.