This server mirrors the TypeSafe Jev HTTP API
and adds a small set of extensions. Everything below is the behavior of this
server, not of api.typesafe.ai.
Base URL:
- Local development:
http://127.0.0.1:8787 - Production: your deployed worker URL, for example
https://jeb.<account>.workers.dev
When the API_KEY environment variable is set, every /v1/* request must
send:
Authorization: Bearer <API_KEY>When API_KEY is unset the server is open and any bearer token (or none) is
accepted, which keeps drop-in SDK usage working out of the box. /health and
/ are never authenticated.
A missing or wrong key returns 401:
{
"error": {
"message": "Missing or invalid API key. Check the Authorization header.",
"type": "authentication_error"
}
}The JEB_PROVIDER environment variable chooses where model calls go; it does
not affect the request or response shapes documented here.
openrouter(default) calls OpenRouter's chat-completions API. The model isOPENROUTER_MODEL(or the legacyMODEL_IDalias), defaulting toinception/mercury-2.5; the key isOPENROUTER_API_KEY.inceptioncalls Inception's direct OpenAI-compatible API (https://api.inceptionlabs.ai/v1, overridable withINCEPTION_BASE_URL). The model isINCEPTION_MODEL, defaulting tomercury-2.5; the key isINCEPTION_API_KEY.
Both routes send the same prompt and JSON Schema. Reasoning is mapped to each
provider's native field: OpenRouter receives reasoning: { effort }, Inception
receives an OpenAI-style reasoning_effort string (with none sent when
reasoning is off). A missing key for the selected provider returns 500 with a
message naming the environment variable to set.
Responses include access-control-allow-origin: *, and OPTIONS requests
return 204 with GET, POST, OPTIONS allowed and Authorization, Content-Type
permitted headers.
Evaluate a state against a map of typed questions. All questions are
evaluated against the same state in a single model call; answers come back
under the question ids you chose.
| Field | Type | Required | Notes |
|---|---|---|---|
state |
any JSON value | yes | String, object, array, or null. |
model |
string | no | Advisory. Any name resolves to the active provider's configured model (see below). |
reasoning_effort |
string | no | Extension. One of none, low, medium, high. |
questions |
map<string, Question> | yes | Non-empty. Question ids are arbitrary and never sent to the model. |
reasoningEffort is accepted as an alias for reasoning_effort; when both are
present the snake_case field wins.
Every question has a type and an optional instructions value. instructions
may be any JSON value: a string, or an object/array that puts the question in
one field and the data it references in others. TypeSafe documents structured
questions in Advanced: structure.
| Type | Extra field | Shape |
|---|---|---|
noul |
criteria |
Optional object with true and/or false descriptions of the boundary. |
choice |
criteria |
Required map of option name to description. 1–255 options. |
score |
criteria |
Required ordered array of level descriptions, lowest first. 2–10 levels. |
Criteria descriptions are also any JSON value, so an option or level can carry
structured guidance (what, not_for, examples, and so on).
{
"state": "I sent the shoes back a week ago. When do I get my money?",
"questions": {
"topic": {
"type": "choice",
"instructions": "Which returns topic is the customer asking about?",
"criteria": {
"return_policy": "Whether and how an item can be returned",
"return_status": "Progress of a return already sent"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": ["Calm", "Frustrated but civil", "Very angry"]
},
"wants_refund": {
"type": "noul",
"instructions": "Does the customer want money back?"
}
}
}| Field | Type | Notes |
|---|---|---|
model |
string | The model that answered, for example inception/mercury-2.5 (OpenRouter) or mercury-2.5 (direct Inception API). |
answers |
map<string, Answer> | One answer per question, under the same ids you used. |
usage |
{ input_tokens, output_tokens } |
Token counts mapped from the provider's prompt/completion counts. |
output_tokens includes reasoning tokens when reasoning is enabled.
Every answer has a type matching its question. Choice and Score answers carry
probabilities and confidence; Noul answers do not carry confidence.
noul:
{ "type": "noul", "noul": 0.87 }choice:
{
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 0.88, "technical": 0.12, "sales": 0 },
"confidence": 0.82
}score:
{
"type": "score",
"score": 1.43,
"legend": {
"0": "Cosmetic; no impact to functionality",
"1": "Broken or degraded feature, but workaround exists",
"2": "Blocking issue; no workaround exists"
},
"probabilities": { "0": 0, "1": 0.57, "2": 0.43 },
"confidence": 0.355
}How the values are produced:
probabilitiesare clamped to non-negative, normalized to sum to 1, and rounded to six decimals with the rounding residue placed on the largest value, so they sum to exactly 1. If the model returns all zeros the distribution falls back to uniform.choiceis the option with the highest probability (first one wins a tie).scoreis the probability-weighted mean of the level numbers,sum(i * probabilities[i]), and can land between levels.legendmaps each level number back to the description you supplied, including structured descriptions.confidenceis computed from the distribution with(n * peak - 1) / (n - 1), clamped to[0, 1], wherepeakis the largest probability andnis the number of options or levels. All probability on one outcome gives 1; an even split gives 0. Single-option questions give 1.
Request:
curl -X POST http://127.0.0.1:8787/v1/systemone \
-H "Content-Type: application/json" \
-d '{
"state": "Hi, I have been trying to connect my Stripe account for 3 days and the integration keeps failing. I am losing sales. Please help ASAP.",
"model": "jev-latest",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this",
"criteria": {
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated the customer appears",
"criteria": ["Calm, just stating facts", "Frustrated but civil", "Very angry, strong language"]
},
"is_urgent": {
"type": "noul",
"instructions": "The message conveys urgency or time-sensitivity"
}
}
}'Response:
{
"model": "inception/mercury-2.5",
"answers": {
"department": {
"type": "choice",
"choice": "technical",
"probabilities": { "billing": 0.05, "technical": 0.9, "sales": 0.05 },
"confidence": 0.85
},
"frustration": {
"type": "score",
"score": 1.05,
"legend": {
"0": "Calm, just stating facts",
"1": "Frustrated but civil",
"2": "Very angry, strong language"
},
"probabilities": { "0": 0.05, "1": 0.85, "2": 0.1 },
"confidence": 0.775
},
"is_urgent": { "type": "noul", "noul": 0.95 }
},
"usage": { "input_tokens": 1172, "output_tokens": 138 }
}Lists the configured model and the TypeSafe aliases this server accepts. The
model request field is advisory, so this list is informational.
{
"models": [
{
"name": "inception/mercury-2.5",
"description": "inception/mercury-2.5 served through OpenRouter with structured outputs.",
"release_date": "2026-09-08"
},
{ "name": "jev-latest", "description": "TypeSafe alias accepted for compatibility; resolves to inception/mercury-2.5.", "release_date": "2026-09-08" },
{ "name": "jev-preview", "description": "TypeSafe alias accepted for compatibility; resolves to inception/mercury-2.5.", "release_date": "2026-09-08" },
{ "name": "jev-1.13.0", "description": "TypeSafe version id accepted for compatibility; resolves to inception/mercury-2.5.", "release_date": "2026-09-08" }
]
}With JEB_PROVIDER=inception the descriptions name Inception's API instead.
When OPENROUTER_MODEL (or legacy MODEL_ID) points elsewhere, that slug is
listed first and inception/mercury-2.5 is appended as the default slug; the
same applies to INCEPTION_MODEL with mercury-2.5.
{ "ok": true }{
"name": "jeb",
"description": "A TypeSafe Jev-compatible structured evaluation API powered by Inception Mercury 2.5.",
"provider": "openrouter",
"model": "inception/mercury-2.5",
"endpoints": {
"systemone": "POST /v1/systemone",
"models": "GET /v1/models",
"health": "GET /health"
}
}Errors use the HTTP status codes TypeSafe documents, with a JSON body:
{
"error": {
"message": "Score question 'severity' must have at least 2 levels.",
"type": "invalid_request_error",
"param": "questions.severity.criteria"
}
}| Status | type |
When |
|---|---|---|
401 |
authentication_error |
API_KEY is set and the bearer token is missing or wrong. |
404 |
not_found_error |
Unknown path. |
405 |
invalid_request_error |
Wrong method for a known path. |
422 |
invalid_request_error |
Body failed validation. param names the offending field, for example state, questions.<id>.criteria, or reasoning_effort. |
429 |
rate_limit_error |
The model provider rate-limited the request. retry-after is passed through when present. Back off and retry. |
500 |
internal_error |
Unhandled server error, including an invalid REASONING_EFFORT or JEB_PROVIDER value, or a missing key for the selected provider. |
502 |
upstream_error |
The model provider returned an error or malformed content that is not retryable. |
529 |
overloaded_error |
The provider failed, timed out, or is temporarily overloaded. Retry after a short delay. |
Validation enforces TypeSafe's documented limits: 2–10 Score levels, at most
255 Choice options, and a non-empty questions map. state accepts any JSON
value, including null. State size limits are not enforced.
reasoning_effort(and thereasoningEffortalias) is an extension. Accepted values arenone,low,medium, andhigh, the efforts Mercury 2.5 advertises. The request value overrides theREASONING_EFFORTserver default; when neither is set, reasoning is disabled for the default model. Other effort values (minimal,max,xhigh) return422.modelis advisory. Any model name is accepted and resolves to the configuredMODEL_ID. The response reports the model that actually answered.GET /v1/modelslists the aliases kept for SDK compatibility.GET /v1/models,GET /health, andGET /are not part of the TypeSafe API.- Probabilities are instructed, not natively calibrated. Jev is trained for calibrated decisions; Mercury 2.5 is prompted to distribute probability mass honestly. The shapes and arithmetic match Jev, but values may need retuning if you have thresholds tuned against real Jev answers.
confidenceuses the formula from TypeSafe's confidence explorer and is not rounded to two decimals, so it can show more precision than TypeSafe's example responses.instructionsis optional. TypeSafe's HTTP reference marks it required, but their SDKs treat it as optional; this server accepts missing ornullinstructions on any question.usage.output_tokensincludes reasoning tokens when reasoning is enabled, matching the provider's completion token count.- Error message text may differ from
api.typesafe.aifor statuses TypeSafe does not document (404,405,500), and upstream provider failures are mapped onto429,529, and502.