Overview
Jev evaluates one state — a support ticket, a chat log, a tool call, any text or JSON you pass — against a set of typed questions, and returns structured answers your code can branch on. It does not generate text.
One request can ask up to 8 questions; they are evaluated together and share the state input cost. The request body is the same shape TypeSafe's SystemOne API accepts — to move a working integration, swap the base URL and the Bearer key.
- POST
https://jevmodel.org/v1/systemone— the single decision endpoint. - CORS is open (
access-control-allow-origin: *), so the endpoint answers from any origin. Keep your key on a server anyway. - The playground sends the identical request shape through your browser session — what you preview there is what you send.
Authentication
Every API call needs a jevmodel.org API key. Create one in Dashboard → API keys;
keys start with sk- and are shown once at creation. Store it as an environment variable, for
example JEVMODEL_API_KEY.
Authorization: Bearer $JEVMODEL_API_KEY Keys belong to your account's token balance and can be revoked from the dashboard. Never ship a key in client-side code or commit it to a repository — if a key leaks, revoke it and mint a new one. The playground authenticates with your signed-in browser session instead and does not need a key.
HTTP endpoint
Send a JSON body to the evaluation endpoint with Bearer authentication:
POST /v1/systemone HTTP/1.1
Host: jevmodel.org
Authorization: Bearer $JEVMODEL_API_KEY
Content-Type: application/json
Idempotency-Key: ticket-4821-run-1 # optional Idempotency-Key is optional (up to 100 characters). Retries sent with the same key reuse the
original billing record, so a retried request cannot be charged twice for the same input.
curl -X POST https://jevmodel.org/v1/systemone \
-H "Authorization: Bearer $JEVMODEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-latest",
"state": "The customer has been billed twice and wants a refund today.",
"questions": {
"route": {
"type": "choice",
"instructions": "Which queue should own this ticket?",
"criteria": {
"billing": "Payments, refunds, invoices",
"technical": "Product errors and usage problems",
"sales": "Pricing, upgrades, new business"
}
},
"escalate": {
"type": "noul",
"instructions": "Does this need a human right now?"
},
"urgency": {
"type": "score",
"instructions": "How urgent is this for the customer?",
"criteria": ["routine", "soon", "urgent", "critical"]
}
}
}' Request body
One JSON object with a state and a map of questions:
{
"model": "jev-latest",
"state": "The customer has been billed twice and wants a refund today.",
"questions": {
"route": {
"type": "choice",
"instructions": "Which queue should own this ticket?",
"criteria": {
"billing": "Payments, refunds, invoices",
"technical": "Product errors and usage problems",
"sales": "Pricing, upgrades, new business"
}
},
"escalate": {
"type": "noul",
"instructions": "Does this need a human right now?"
},
"urgency": {
"type": "score",
"instructions": "How urgent is this for the customer?",
"criteria": ["routine", "soon", "urgent", "critical"]
}
}
} | Field | Type | Notes |
|---|---|---|
model | string | Optional; defaults to jev-latest. |
state | string, object, or array | Required. Serialized form is limited to 8,000 characters. |
questions | object | Required map of 1–8 question names to typed definitions. Names are identifiers: letters, digits, and _, at most 64 characters. |
Each question needs a type and instructions (up to 1,800 characters) telling the
model what to decide. criteria supplies the answer space — required for choice and score,
optional for noul.
Question types
Choose the answer shape your application can act on.
| Type | Criteria | Answer |
|---|---|---|
noul | Optional true / false label descriptions. | noul — the probability of yes, from 0 to 1. |
choice | Object of 2–20 option keys mapped to descriptions. | Selected choice, a probabilities map, and confidence. |
score | Ordered array of 2–10 levels. | Numeric score (may be fractional) and confidence. |
Serialized criteria are limited to 2,000 characters per question. Validation failures return 422 before any tokens are reserved — bad requests are never billed.
Response JSON
A 200 response is the model output itself: the resolved model, one answer per question name, and usage. The values below are illustrative — run the request for real output.
{
"model": "jev-latest",
"answers": {
"route": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 0.93, "technical": 0.05, "sales": 0.02 },
"confidence": 0.93
},
"escalate": {
"type": "noul",
"noul": 0.12
},
"urgency": {
"type": "score",
"score": 1.4,
"confidence": 0.88
}
},
"usage": { "input_tokens": 136, "output_tokens": 18 }
} answersis keyed by your question names — you read the fields you asked for.- For
choice, branch on the selected label and inspectprobabilitieswhen you need the full distribution. noulis P(yes) — your code picks the action threshold.usage.input_tokensis what this call actually billed; output tokens are free.
Billing
The API bills input tokens only — output is free. Each call reserves an estimate up front, then settles to
the actual usage.input_tokens the model reports. If Jev fails upstream, the reservation is fully
refunded automatically.
- Sign in once and get 100,000 input tokens free; paid packs start at $9.90 and never expire.
- Insufficient balance returns
402before Jev is called. X-Tokens-Remainingon every 200 response is your balance after settlement.- Estimate it yourself:
ceil(JSON.stringify({state, questions}).length / 4).
Models and limits
jev-latest is the default and the model currently served — the model field is
optional. Laya variants shown in the playground are upcoming and not yet exposed through this endpoint.
| Limit | Value |
|---|---|
| Questions per request | 8 |
| Question name | Identifier ≤ 64 characters |
Serialized state | 8,000 characters |
instructions per question | 1,800 characters |
| Choice options / score levels | 2–20 options, 2–10 levels |
Serialized criteria | 2,000 characters per question |
| Rate limit | 120 requests / minute per key |
These figures describe the API validator; the upstream model may have its own context limits.
Errors and retries
Errors return a JSON envelope with a machine-readable type and a human message:
{
"error": {
"type": "invalid_request_error",
"message": "At most 8 questions per request."
}
} | Status | Type | Cause | Charged? |
|---|---|---|---|
401 | authentication_error | Missing, invalid, or revoked key. | No |
402 | insufficient_credits | Estimated input tokens exceed your balance. | No |
422 | invalid_request_error | Body failed validation — see message. | No |
429 | rate_limit_error | More than 120 requests this minute. | No |
502 | upstream_error | Jev could not complete the call. | No — refunded |
Retry 429 and 502 with exponential backoff — the rate window is per minute, so short pauses are usually
enough. Send an Idempotency-Key when retrying so a repeated request settles against the same
billing record instead of charging twice.
Playground to API
Build the scenario in the playground first — the request preview shows the exact JSON body your application should send, and Copy API example gives you the matching cURL. When the answers look right, create a key in the dashboard and swap the browser session for Bearer authentication.
More reading: How to use Jev walks through a first call end to end, and Jev API examples has production patterns for each question type.