Documentation
API reference
The complete POST /v1/run contract: headers, request and response fields, statuses, per-step billing, and the audit trace. Plus the service descriptor.
Last updated
One endpoint runs everything. A second returns the contract for each service. This page specifies both, plus every field a response carries. For the walk-through, start with the quickstart; for per-service examples, browse the services directory.
POST /v1/run
Runs a pipeline of one or more steps against an input. A single service call and a multi-step chain look the same on the wire.
POST /v1/run
Authorization: Bearer chrs_live_YOUR_KEY
Content-Type: application/json
Idempotency-Key: order-8812-check
| Header | Required | Purpose |
|---|---|---|
Authorization |
yes | Bearer API 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. |
Every response also carries the rate-limit window in X-RateLimit-Limit,
X-RateLimit-Remaining, and X-RateLimit-Reset. Limits are per account; a chained run
counts as one request. Maximum input is 100,000 characters per run, and the validator
rejects larger inputs before anything runs. Region is an account setting, not a
request field.
Request body
{
"request_id": "order-8812-check",
"input": {
"type": "text",
"value": "Hi, I'm Jan de Vries, my IBAN is NL91ABNA0417164300, fix this or else."
},
"pipeline": [
{
"id": "shield",
"service": "security.jailbreak.shield",
"stop_if": { "verdict": "flag" }
},
{
"id": "redact",
"service": "governance.pii.redact",
"params": { "entities": ["name", "iban"] }
},
{ "id": "tox", "service": "security.toxicity.flag" },
{
"id": "intent",
"service": "routing.intent.map",
"params": { "catalog": "support-v3" }
}
]
}
| Field | Required | Description |
|---|---|---|
request_id |
no | Client correlation id, echoed back in the response |
input.type |
yes | text today. Reserved: image, audio, video |
input.value |
yes | Inline content. Media will use input.uri or input.base64 instead |
pipeline |
yes | Ordered list of steps. One step is valid |
pipeline[].id |
yes | Unique step id, used as the key in results and audit |
pipeline[].service |
yes | Service name, e.g. governance.pii.redact |
pipeline[].params |
no | Service-specific, validated against the descriptor |
pipeline[].stop_if |
no | Classify steps only: { "verdict": "flag" }, { "verdict": "pass" }, { "score_gte": 0.9 }, or { "score_lte": 0.1 } |
Each step receives the payload as the previous step left it. When a stop_if fires,
later steps do not run and do not bill. Halting on extract results needs
workflows.
GET /v1/services/:name
Returns the descriptor clients need to build valid chains.
{
"name": "governance.pii.redact",
"kind": "transform",
"accepts": ["text"],
"emits": "same",
"billing_unit": "characters",
"languages": "multilingual",
"params": {
"entities": { "type": "array", "default": ["name", "email", "id"] },
"mask": { "type": "string", "default": "[REDACTED]" }
},
"model": { "id": "Roblox/roblox-pii-classifier-v2", "license": "Apache-2.0" }
}
| Field | Description |
|---|---|
kind |
transform, classify, or extract; fixes the result shape |
accepts |
Payload types the service accepts |
emits |
"same" (payload type unchanged) or a concrete type |
billing_unit |
What the service bills on; characters today, pixels and seconds reserved |
languages |
The languages the service handles: an array of ISO 639-1 codes, or "any" when it runs no language model, "multilingual" when its backend supports many without enumerating them, or "unspecified" when the backend publishes no claim |
params |
Typed parameters with defaults, validated per step |
labels |
Classify services only: every label the service can return |
output_schema |
Extract services only: the JSON schema data is validated against |
model |
Backend model id and license |
The validator uses accepts and emits to reject impossible chains before anything
runs. There is no version in a service name: every call runs the latest version, and
the audit trace records the version that ran.
Response body
{
"request_id": "order-8812-check",
"trace_id": "chs_01J9X4Q7K2M8N3P5R6T7V8W9X0",
"status": "completed",
"halted_at": null,
"output": {
"type": "text",
"value": "Hi, I'm [REDACTED], my IBAN is [REDACTED], fix this or else."
},
"results": {
"shield": { "kind": "classify", "status": "ok", "verdict": "pass", "score": 0.03, "labels": [] },
"redact": { "kind": "transform", "status": "ok", "changes": 2 },
"tox": { "kind": "classify", "status": "ok", "verdict": "flag", "score": 0.84, "labels": ["threat"] },
"intent": { "kind": "extract", "status": "ok", "data": { "intent": "billing.dispute", "confidence": 0.91 } }
},
"usage": {
"billed": [
{ "unit": "characters", "quantity": 70, "steps": ["shield"] },
{ "unit": "characters", "quantity": 70, "steps": ["redact"] },
{ "unit": "characters", "quantity": 60, "steps": ["tox"] },
{ "unit": "characters", "quantity": 60, "steps": ["intent"] }
]
},
"audit": {
"region": "eu-nl-1",
"retention": "none",
"received_at": "2026-09-03T09:14:22.118Z",
"total_ms": 41,
"steps": [
{
"id": "shield",
"service": "security.jailbreak.shield",
"version": 1,
"model": "meta-llama/Prompt-Guard-86M",
"status": "ok",
"input_type": "text",
"input_size": 70,
"input_sha256": "9f2c...a1",
"output_type": "text",
"output_size": 70,
"output_sha256": "9f2c...a1",
"verdict": "pass",
"duration_ms": 6
},
{
"id": "redact",
"service": "governance.pii.redact",
"version": 2,
"model": "Roblox/roblox-pii-classifier-v2",
"status": "ok",
"input_type": "text",
"input_size": 70,
"input_sha256": "9f2c...a1",
"output_type": "text",
"output_size": 60,
"output_sha256": "4b71...c9",
"changes": 2,
"duration_ms": 11
}
]
}
}
| Field | Description |
|---|---|
request_id |
Echoed correlation id |
trace_id |
Unique run id |
status |
completed, halted, failed, or duplicate |
halted_at |
Step id where the run halted, else null |
output |
The final payload, { type, value } |
results |
One result per executed step, keyed by step id |
usage.billed |
One line per executed step, with the characters that step processed |
audit |
The trace: region, retention, timing, and a record per step |
Status values
| Status | Meaning |
|---|---|
completed |
Every step ran |
halted |
A stop_if fired (or a cap was hit). halted_at holds the step id; output is the payload at that point |
failed |
A step failed or validation rejected the pipeline. An error object replaces output; results still holds every step that completed before the failure |
duplicate |
The Idempotency-Key was seen in the last 24 hours. Only trace_id is returned |
Result shapes by kind
Every result carries kind and status. The rest depends on the kind only.
| Kind | Fields |
|---|---|
| transform | changes (integer). governance.pii.tokenize adds map (token to original value) |
| classify | verdict (pass, flag, uncertain), score (0 to 1), labels. A flag always means the thing the service guards against was found; score is the probability of flag, so a pass has a low score |
| extract | data (JSON, validated against the service’s output_schema) |
Billing fields
Each line in usage.billed carries unit (characters; pixels and seconds
reserved), quantity, and the steps it covers. The short version of the rules:
- Every executed step bills the characters it processes, at the tier rate. There is one rate table and no included steps.
- A transform changes the payload, so a step after one bills what it was left rather than the original input.
- A
stop_ifhalt stops the count: steps that never ran never bill. - A run that fails part-way bills only the steps that completed. Chersus bills nothing for rejected requests or for its own failures.
The account’s total billed characters for the calendar month set the tier. Rates are marginal. The pricing page has the rate table and a calculator.
Audit trace fields
Per run: region, retention ("none"), received_at, total_ms. Per step: the
service and version, the model, input and output type and size, SHA-256 hashes of
input and output, the verdict or change count, and duration_ms.
No payload content is ever included. Hashes are SHA-256 over the UTF-8 bytes of the payload exactly as received or produced, with no trimming or normalization. Recompute them and you can prove a given text went through a given step with a given result, without Chersus storing the text.
Errors
Every error carries one envelope and one code to branch on, whether the request was
rejected before it ran or a run failed part-way through. Errors
documents the full set, the two levels they arrive at, and what each one bills.
Key handling is covered in authentication, and throttling in rate limits.