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

API reference

The complete contract: POST /v1/run, policies, authentication, rate limits, errors, statuses, billing rules, and the audit trace, in one place.

Last updated Also as Markdown:this pagellms.txt

One endpoint does the work, POST /v1/run. List the operations inline, or name a policy. Both answer in the same shape. The quickstart is the walk-through, and the contract is also an OpenAPI document.

Operations

Every operation has a full name, kind.subject.action. A request lists each kind under its own name, and inside that list an operation goes by its short name: pii.entities in detect, pii.tokenize in transform. Wherever one stands on its own (when, routing.by, blocked_by, failed_ops, the audit trace) it keeps its full name, such as detect.pii.entities.

Kind Returns Never
detect.* verdict (pass, flag), score (0 to 1), labels rewrites
extract.* structured data: a value with confidence, or a JSON object rewrites
transform.* rewritten text, plus changes reads without rewriting

An operation is a string, or an object with options, such as { "op": "secrets.credentials", "block": true }. Both forms name the same operation.

Option Operations Meaning
min_score every detect The score at or above which the detect flags. Without it, the model’s own verdict
block every detect When the detect flags, the decision is block and no text is handed back. The detect and extract results still come back
schema fields.schema The fields to fill, by name and type
types pii.redact, pii.tokenize The entity types to act on
mask pii.redact, secrets.redact The placeholder, default [REDACTED]
when every transform The outcomes it waits for. See conditions and routing

An option on the wrong operation is rejected with invalid_request. GET /v1/ops returns the catalogue with each operation’s options. The operations page lists all sixteen.

POST /v1/run

Operations on one piece of text, written inline or named as a policy.

Header Required Purpose
Authorization yes Bearer chrs_live_YOUR_KEY
Content-Type yes application/json
Idempotency-Key no Deduplicates retries for 24 hours. A repeat returns status: "duplicate" and the original trace_id, runs nothing, bills nothing. Chersus keeps no response, so keep the first one
X-LLM-Key with relay, unless the model is provided Your credential for the relay’s provider. See your provider credential
X-LLM-Key-<model name> when routing names a model The credential for that model in routing.models, such as X-LLM-Key-eu
X-Session-Key no Any opaque id, such as one per conversation. Placeholders are then stable within that key only. See the token map

Request body

{
  "input": "Contact Jane Doe at jane.doe@example.com. Your service is garbage.",
  "transform": ["pii.tokenize"],
  "detect": ["toxicity.abuse", "secrets.credentials"],
  "extract": ["language.code"]
}
Field Description
input (required) The text, as a string. At most 100,000 characters
policy A policy name (latest active version) or a pinned ref name@version, in place of inline operations
conditions Up to 32 names your code sets to true or false, such as { "high_security": true }. Lowercase letters, digits, - and _, no dot
detect Detects, in parallel on the original input. A detect with block: true decides the run
extract Extracts, in parallel with the detects
transform An ordered list. Each receives the output of the previous one. A transform with when runs only when one of its outcomes holds
tokens A token map from an earlier response. The response returns it merged with the new tokens, so one map can serve a whole conversation. See the token map
relay Sends what the last transform handed back to a model and returns the answer as output. provider is openai, anthropic, mistral, azure, bedrock or vertex with your key in X-LLM-Key, or chersus for a provided model. model names the model (azure takes endpoint and deployment instead), detect runs checks on the answer (before rehydration, so on placeholders), routing picks the model. See relay and choosing a model for each provider’s fields
stream Relay only. true, or Accept: text/event-stream, streams server-sent events: route, delta, detect, blocked, done

At least one of detect, extract, transform or relay is required, or a policy. A policy next to any of them is rejected with invalid_request. An operation in another kind’s list is rejected with invalid_op_kind, and a full name inside a list with invalid_op.

Conditions and routing

A transform waits for an outcome with when, written source:outcome. A relay routes on one value with routing: by names the source, and each case is one of its outcomes. The workflows guide explains how they combine.

Source Outcomes Example
a detect flagged, clean detect.pii.entities:flagged
extract.language.code An ISO 639-1 code extract.language.code:de
extract.content.topics Each of 36 topics, several at once extract.content.topics:travel
a name from conditions true, false high_security:true
Field Description
routing.by The one source that decides. Topics can hold several outcomes at once, so they work in when but not here
routing.models Named models, each with provider and model. Each needs its own X-LLM-Key-<name>
routing.cases Outcome to model name, or null for no model: the run goes to the output. An outcome without a case goes to the relay’s own model

The error codes are unknown_condition, unknown_outcome, unknown_model, invalid_condition_name and llm_key_missing.

Response body

{
  "trace_id": "chs_01J9X4Q7K2M8N3P5R6T7V8W9X0",
  "status": "completed",
  "decision": "allow",
  "output": "Contact <PERSON_eb7805d77a13> at <EMAIL_33b537052b41>. Your service is garbage.",
  "transform": [{ "op": "pii.tokenize", "changes": 2 }],
  "detect": {
    "toxicity.abuse": { "verdict": "flag", "score": 0.84 }
  },
  "tokens": { "<PERSON_eb7805d77a13>": "Jane Doe", "<EMAIL_33b537052b41>": "jane.doe@example.com" },
  "usage": {
    "billed": { "unit": "characters", "input": 66, "output": 79, "quantity": 145 }
  },
  "audit": { "...": "see the audit trace below" }
}
Field Description
trace_id Unique run id, also in the audit trace
status completed, failed or duplicate. See statuses
policy With a policy: the pinned ref that ran
decision allow or block. block only when a detect with block: true flagged, on the input or on the answer
blocked_by On block: the checks that flagged, with op, score and labels
failed_ops On failed: the operations that did not run
output The last transform’s text, or with relay the model’s answer. null when the run had neither
relay provider, model, duration_ms, and detect with one result per answer check. Absent when routing skipped the model
route With routing: by, outcome, the routed model’s name (null for the relay’s own model), provider and model (for azure, the deployment). All null but by and outcome when it skipped the model
detect, extract One result per operation, keyed by short name. Absent when the run had none
transform One record per transform, in order: op, changes, skipped: true when its when did not hold
tokens The full token map. Present only when a tokenize ran
usage.provided_model Provided-model relay only: input_tokens, output_tokens, provider_cost, fee (5% of the provider cost), currency
usage.billed unit, input (characters sent), output (characters handed back, 0 without a transform) and quantity, their sum, which bills
audit The trace: hashes, never content

On block, output, transform and tokens are omitted, detect and extract stay, and only the input bills. An input check that blocks never calls the model, so relay is absent. A block by an answer check withholds the answer: relay is present, output is not.

Policies

A policy is a workflow saved under a name and versioned. The policies guide walks through the object.

{
  "name": "customer-support",
  "extends": "template:external-llm",
  "detect": [{ "op": "prompt.injection", "block": true }, "pii.entities"],
  "transform": [{ "op": "pii.tokenize", "when": ["detect.pii.entities:flagged", "high_security:true"] }],
  "conditions": { "high_security": false },
  "relay": { "provider": "openai", "model": "gpt-4.1-mini" },
  "tokens": { "style": "angle" }
}
Field Description
name Posting a name that exists creates the next version
extends One parent, one level: a template (template:external-llm) or a policy of yours (support-base@2). Frozen into the version at creation. A parent that extends something is rejected with extends_depth
detect, extract, transform, relay Exactly as in run. At least one is required
conditions Defaults for the conditions the policy uses. A request overrides them per name
tokens.style angle (default, <PERSON_eb7805d77a13>), bracket ([PERSON_eb7805d77a13]) or curly ({{PERSON_eb7805d77a13}})

Merging with a parent: detect and extract are the union, with the child’s options winning. transform is the parent’s list, then the child’s. relay and tokens are the child’s when set. conditions is the union, with the child’s default winning.

Routes are in the endpoint list. Posting returns name, version, ref (customer-support@3), status and created_at. Nothing is edited in place: customer-support resolves to the latest active version at request time, customer-support@3 is pinned. A retired version is refused with policy_retired but stays readable for audit traces. The only active version cannot be retired. Reads return the resolved object plus the raw definition. Templates (external-llm, internal-llm, strict-pii, support-chat, rag-context, tool-call) are owned by Chersus, and an update never reaches your policy until you publish a new version.

Running a policy

POST /v1/run with a policy in place of the operations. Same headers and response. The key needs the apply scope and the policy in its allowlist, or the run is refused with policy_not_allowed. See the policies guide.

{
  "policy": "customer-support@3",
  "input": "My name is John Smith and my account number is 123456.",
  "conditions": { "high_security": true }
}

Fields: policy and input (required), conditions, tokens and stream.

Authentication

Keys start with chrs_live_ or chrs_test_, and you see the full key once, at creation. Test keys run the same models, bill nothing, and are rate-limited low. Keep keys out of client code. After a rotation the old key stops working at once, so deploy the new one first.

Key property Meaning
scopes run for inline operations, apply for runs that name a policy, policies:read, policies:write
policies An allowlist, wildcard versions allowed (customer-support@*). Others fail with policy_not_allowed
environment production, staging or development. Appears in the audit trace

Your provider credential

A relay to your own provider account takes its credential in X-LLM-Key. Provided models need none. It is used once and never stored. The audit trace records its SHA-256 fingerprint. A routed model gets its own header, X-LLM-Key- plus its name in routing.models. Send every routed model’s key up front, since the case is only known after the detects and extracts ran. Choosing a model lists what each provider takes.

Rate limits

Limits scale with volume and apply per account, per minute. A run is one request, however many operations it holds. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (a Unix timestamp).

Limit Value
Window exhausted 429 with Retry-After in seconds. Never billed
Input per run 100,000 characters, else input_too_large
Test keys Rate-limited low
Idempotency An Idempotency-Key and its trace_id are kept 24 hours, no content
Provider limits Still apply on the model leg. A provider 429 is provider_error

Retry only 429 and 5xx, with a token bucket rather than a tight loop. Region is an account setting, not a request field. Ask about reserved capacity for dedicated throughput.

Errors

Branch on error.code, never on the message. A rejected request is an HTTP error and bills nothing.

{
  "error": {
    "code": "invalid_op_kind",
    "message": "'pii.redact' is a transform; pass it in transform, not in detect.",
    "details": { "op": "transform.pii.redact" }
  }
}

Rejected before anything runs

Code (HTTP) Meaning
unauthorized (401) The key is missing, malformed or revoked, often rotated
insufficient_quota (402) The month’s allowance is exhausted. Do not retry before the reset
rate_limited (429) Too many requests. Wait Retry-After seconds
invalid_request (400) Malformed body or missing field. details.field
invalid_op (400) The operation does not exist, or a full name sits in a list. details.op
invalid_op_kind (400) An operation in another kind’s list. details.op
field_reserved (400) A reserved field was sent. details.field
input_too_large (413) Input over 100,000 characters
policy_not_found (404) No policy with that ref
policy_retired (400) The version is retired
policy_not_allowed (403) The key’s allowlist excludes this policy
extends_depth (400) The parent already extends something
llm_key_missing (400) No X-LLM-Key, or a routed model’s X-LLM-Key-<model name>. details.provider, details.model
relay_field_missing (400) The relay lacks a field its provider needs, such as endpoint for azure. details.field
unknown_condition (400) A when or routing.by names a detect or extract the run lacks, or a condition with no request value and no policy default. details.condition
unknown_outcome (400) An outcome its source cannot take. details.outcome
unknown_model (400) A routing case names a model not in routing.models. details.model
invalid_condition_name (400) A condition name outside a-z, 0-9, - and _. details.condition

Quota is a 402, not a 429, because a 429 clears in seconds and a 402 only at the reset. A 5xx carries the same envelope and is safe to retry with an Idempotency-Key.

Provider side and after the run started

Code (HTTP) Meaning
provider_auth_failed (401) The provider rejected your credential. Your Chersus key was fine
provider_timeout (504) The provider did not answer in time. Retry with an Idempotency-Key
provider_error (502) The provider returned an error. details.provider_status and details.provider_code carry it

Through the gateway, these arrive in the provider’s own error format. A run that fails after it started is a 200 with status: "failed", failed_ops and a null output. A block and a duplicate also return 200 and are not errors: a block is a check doing its job, and a duplicate is a success.

Statuses

Status Meaning
completed Every operation ran. decision is independent of status
failed An operation failed on the Chersus side. Nothing handed back, nothing billed. failed_ops lists which
duplicate The Idempotency-Key was seen in the last 24 hours. Only trace_id and status are returned

Billing

The month’s characters, in and out together, set the rate: the first 1M are free, then €0.06 per 1K, falling 20% for every doubling of volume. The pricing page has the full rule.

  • A completed run: input characters plus the characters handed back in output. Without transforms or relay, output is null and only the input bills.
  • decision: block on the input: input characters, inline or with a policy.
  • A completed relay: input characters plus the rehydrated answer handed back. Relay adds nothing.
  • A provided model adds its provider cost plus 5% as a separate line.
  • A relay that an answer check blocks: the input, and for a provided model the tokens the provider spent.
  • A relay that fails on the provider side: nothing.
  • A transform skipped by its when: nothing. A run that routing sends to no model bills as a run without relay.
  • A failed run: nothing.
  • A rejected request (4xx): nothing.
  • A failure on Chersus’s side (5xx): nothing.
  • A duplicate: nothing.
  • Detect and extract: nothing, ever.
  • Test keys: nothing.

Audit trace

Every run response carries an audit trace. Hashes, never content.

"audit": {
  "trace_id": "chs_01J9X5A1...",
  "region": "eu-nl-1",
  "retention": "none",
  "policy": "customer-support@3",
  "decision": "allow",
  "total_ms": 31,
  "input_sha256": "9f2c1e...a1",
  "output_sha256": "6b7d44...0f",
  "ops": [
    {
      "op": "transform.pii.tokenize",
      "version": 2,
      "model": "pii-entities-2",
      "duration_ms": 26,
      "changes": 2,
      "input_sha256": "9f2c1e...a1",
      "output_sha256": "6b7d44...0f"
    }
  ]
}
Level Fields
Run trace_id, region, retention, environment, the decision, the policy when one ran, the route when routing ran, total_ms, SHA-256 of input and output
Relay (a relay block) provider, model, key_fingerprint (SHA-256 of the LLM key), prompt_sha256, response_sha256 and duration_ms. Never the key or the prompt
Operation The operation and its version, the model, duration_ms, the verdict, value or change count, and its own hashes

Hashes are SHA-256 over the UTF-8 bytes exactly as received or produced. They prove that a given text went through a given policy version with a given result, using only the hash of text you hold. Chersus retains for invoicing only trace_id, key, policy ref, decision, character counts and timestamp. Not the hashes, not the text, not the map.

Endpoint list

POST   /v1/run

GET    /v1/policies                      list
POST   /v1/policies                      create version 1, or the next version
GET    /v1/policies/{name}               latest active version, resolved
GET    /v1/policies/{name}@{version}     a pinned version, resolved
GET    /v1/policies/{name}/versions      every version and its status
DELETE /v1/policies/{name}@{version}     retire a version

GET    /v1/templates                     the templates Chersus ships
GET    /v1/templates/{name}

GET    /v1/ops                           the operation catalogue

gw.chersus.com/openai/v1/*
gw.chersus.com/anthropic/*

Ten routes and a gateway. The gateway is the same relay at a base URL, for existing OpenAI and Anthropic SDKs, with bodies, streaming and provider errors in the provider’s own format. The gateway guide has SDK examples. Its headers:

Gateway header Required Purpose
The SDK’s own key header yes Your LLM key, unchanged
X-Chersus-Key yes Your Chersus API key
X-Chersus-Policy no Overrides the gateway default policy set in the dashboard
X-Session-Key no Placeholders stable within that key only