Features
Small jobs that stop
big mistakes.
Detects judge the text. Extracts read it. Transforms rewrite it. Relay hands the result to your model, and you pay for the characters in and the characters handed back.
One request, read line by line.
// request { "input": "Hi, I am Jane Doe. Can you check invoice #4821?", "detect": [ 1 { "op": "secrets.credentials", "block": true }, 4 "pii.entities" ], "extract": ["language.code"], 1 "transform": [ 2 { "op": "pii.tokenize", "when": ["detect.pii.entities:flagged"] } 3 ] } // response { "decision": "allow", 5 "output": "Hi, I am <PERSON_eb7805d77a13>. Can you check invoice #4821?", 5 "usage": { "billed": { "input": 47, "output": 60, "quantity": 107 } } 6 }
- 1
detect,extractRun side by side on your input. They never change it. - 2
transformRuns in the order you list. Each step gets the text the previous one handed back. - 3
whenRuns a transform only when its outcome holds, likedetect.pii.entities:flagged. - 4
blockStops the run when that detect flags. Nothing derived from the text comes back. - 5
decision,outputAllow or block, and the text the last step handed back. - 6
usage.billedCharacters in plus characters out. That's the bill.
Three shapes come back: a verdict and a score, a value, or rewritten text with a count of changes.
detect
Catch attacks first.
Threats and secrets can be stopped before they reach a model, a log or a tool. Each check returns a verdict and a score, and a check set to block stops the request when it flags.
- PII and secrets
- Names, addresses, account numbers, API keys and credentials, flagged with a score.
- Prompt injection and jailbreaks
- Instructions hiding in user text, caught before the model reads them.
- Hate, abuse and threats
- Three separate scores from one model, ready to route on.
- Agents, too
- Run the same detects on tool output, web pages and documents, not only on what users type.
- detect.pii.entities
- detect.toxicity.hate
- detect.toxicity.abuse
- detect.toxicity.threat
- detect.prompt.injection
- detect.prompt.jailbreak
- detect.secrets.credentials
"detect": {
"secrets.credentials": {
"verdict": "flag", "score": 0.97, "labels": ["api_key"]
},
"prompt.injection": { "verdict": "pass", "score": 0.02 },
"toxicity.hate": { "verdict": "pass", "score": 0.03 }
}extract
Fields, without a parser.
Route, file and act on a message without a parser of your own. An extract returns a value and leaves the text alone. That value can still decide what runs next.
- Language and topics
- Language picks the model or the queue; topics decide which steps run.
- Fields from a schema
- Typed values your systems can insert without a parsing layer.
- Titles, descriptions and emoji
- Short text for an inbox, a feed or a notification.
- extract.fields.schema
- extract.language.code
- extract.content.title
- extract.content.description
- extract.content.topics
- extract.content.emoji
"extract": {
"language.code": { "value": "en", "confidence": 0.99 },
"fields.schema": {
"value": { "invoice": "4821", "amount": 129.5 }
}
}transform
Only clean text leaves.
What leaves your system is already clean, with a count of what changed. Transform operations run in the order you list them, and you are billed for what the last one returns.
- Redact
- Replace personal data with a marker before it reaches a log or a model.
- Tokenize
- Replace it with a typed placeholder you can put back later.
- Scrub secrets
- Credentials removed, the rest untouched.
- transform.pii.redact
- transform.pii.tokenize
- transform.secrets.redact
"output": "Contact <PERSON_eb7805d77a13> at <EMAIL_33b537052b41> about invoice #4821.",
"transform": [
{ "op": "pii.tokenize", "changes": 2 }
],
"tokens": {
"<PERSON_eb7805d77a13>": "Jane Doe",
"<EMAIL_33b537052b41>": "jane.doe@example.com"
}Pick the model per message.
One relay can pick a model per message. It decides on one value: a detect, the language, or a custom condition your code sets, like high_security. So exactly one route holds, every time.
- Personal data found?
- Tokenized, to a model in the EU. Clean, to the model you prefer.
- Contract says EU-only?
- Your code sets
high_securityfor that customer, and their prompts stay in the EU. - A German message?
- To the model that writes German best.
eu.* profiles, Vertex AI in the EU.relay
The model never sees the name.
Hi, I am Jane Doe. Can you check invoice #4821?
Hi, I am <PERSON_eb7805d77a13>. Can you check invoice #4821?
Hi Jane Doe, invoice #4821 was paid on 3 September.
- Your own key
- Used once, never stored, only its fingerprint in the audit trace.
- Values come back
- After a tokenize, relay puts the real names back before you see the answer.
- Same placeholder, every turn
- Jane gets the same token every turn; a session key separates conversations.
- Streaming
- The answer streams back with the real values already in place.
- Provided models
- No key needed, provider cost plus 5% on its own line. Not available yet.
Two ways in.
The API
POST /v1/run. Send text and steps, get the result. Start here.
curl -X POST https://api.chersus.com/v1/run \
-H "Authorization: Bearer $CHERSUS_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "Hi, I am Jane Doe. Can you check invoice #4821?",
"transform": ["pii.tokenize"]
}'Your SDK
gw.chersus.com. Already on the OpenAI or Anthropic SDK? Change the base URL and keep your code. Your default policy runs on every call.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://gw.chersus.com/openai/v1",
defaultHeaders: { "X-Chersus-Key": process.env.CHERSUS_KEY },
});If you need a big model, who calls it?
- Chersus, with relay
- One request; the clean prompt goes to your model, and the answer comes back with the real values.
- You
- Take the clean text and the token map, call your model wherever it runs, and put the values back yourself.
Control and proof.
Save the workflow as a policy.
A policy is a saved workflow: reference it by name from every application, and publish a new version to change it. Pin customer-support@3 in the app you're testing and run @4 everywhere else. Nobody redeploys.
customer-support@4
├──detectsecrets.credentials (block), prompt.injection (block)
├──extractlanguage.code, content.topics
├──transformpii.tokenize
├──relayprovider: your account | provided model
└──answerdetect.secrets.credentials (block), rehydrate, automatic
Prove what ran, without the content.
Every response carries an audit trace of hashes, models and timings, never the key, never the text. An auditor sees which operations ran, which version, when, and on which hashed text. They never see the text itself.
{
"trace_id": "chs_01J9X6C2K2M8N3P5R6T7V8W9X0",
"region": "eu-nl-1",
"retention": "none",
"input_sha256": "9f2c1e...a1",
"ops": [
{ "op": "detect.pii.entities", "verdict": "flag" },
{ "op": "transform.pii.tokenize", "changes": 1 }
],
"relay": {
"provider": "openai",
"model": "gpt-4.1-mini",
"key_fingerprint": "sha256:3f9a...c1"
}
}Fine print, answered.
What is the difference between a detect, an extract and a transform?
A detect or an extract reads the text and never changes it: a detect returns a verdict, a score and labels, an extract returns a value. A transform returns rewritten text and a count of changes; detects and extracts run in parallel, transforms in the order you list them, each reading the previous one's output.
How fast is it?
Every operation runs on a small model built for one job, not a general model that thinks it over. Try it on your own traffic in early access.
Which languages?
It depends on the operation: personal data in 27, language detection in 84, topics in 101. Each operation lists its own.
Does the model ever see personal data?
It depends on your workflow. With a tokenize step, personal data becomes typed placeholders before the prompt goes to a model, so the model sees placeholders instead. Without one, the model gets the text as it is. With relay, the real values come back automatically.
What happens to my provider key?
With your own key, the key travels in a header, is used once for that request, and is never stored; only its fingerprint appears in the audit trace. Provided models need no key at all.
What does a block do?
A block is the decision a flagged blocking check makes: the request stops before it reaches a model, a log or a tool, transform output is discarded, and you get the verdicts back. With relay, a blocking answer check stops the answer, or the stream.
Can I change a policy without redeploying?
Yes: a policy is referenced by name, and you change it by publishing a new version; nothing is edited in place. Any application using the unpinned name picks up the change at once, while a pinned reference like customer-support@3 keeps running the version it names.
Your first request is one curl away.
Access opens in batches. Request yours, get your key, and start with 1M characters free every month.