Sep 26, 2026
How to call the Jev API from curl, Python and JavaScript
A first Jev AI /v1/systemone request you can paste into a terminal and then into an app — with a real response, measured latency and the full error table.
Before your first call
Jev answers typed decisions: you send a state plus named questions, and it returns one of the answers you declared, with a probability. Optio is an independently operated gateway that forwards the request to Jev — you create an account, open the dashboard and create an API key. The full key is shown exactly once and starts with jev_live_; store it in a server-side secret manager or environment variable, never in frontend code and never in a Git repository.
The endpoint is the same for every client:
POST https://jevai.website/api/v1/systemone
Authorization: Bearer $JEV_API_KEY
Content-Type: application/jsonEndpoint and auth headerThe smallest useful request
A request body has two parts: state (what the decision is about) and questions (what you want to know). The state can be a plain string, an array of strings or a JSON object, up to 100,000 characters. Questions are a map of 1 to 8 named questions; the name must be a valid identifier (letters, digits, underscores — it becomes the key in the answer).
curl https://jevai.website/api/v1/systemone \
-H "Authorization: Bearer $JEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "The payments webhook failed twice overnight. Should I wake up the on-call engineer?",
"questions": {
"page": {
"type": "noul",
"instructions": "Should someone be paged right now?",
"criteria": {
"true": "Revenue-impacting or data loss",
"false": "Can wait until morning"
}
}
}
}'
curl — one yes/no questionThe values in criteria are your own wording — write them like policy lines. They are what the model judges against, so a question like “Should someone be paged?” with explicit true/false definitions is much more repeatable than the same question bare.
What the response looks like
A real 200 response from this endpoint looks like this (captured against the live gateway; token counts are from the actual request):
{
"request_id": "req_fe17d05411092c4f7f97947a",
"model": "jev-1.14.0",
"answers": {
"page": { "type": "noul", "noul": 0.91 }
},
"usage": {
"input_tokens": 128,
"output_tokens": 21,
"credits": 1,
"credits_remaining": 4
},
"latency_ms": 432
}
200 response, abbreviatedAnswers are keyed by your question names, so branching in code is trivial: if (answers.page.noul < 0.8) escalate(). Every response also carries a request_id — quote it when you write to support, and log it on your side for reconciliation.
The three question types
- noul — yes/no. Returns noul: the probability (0–1) that the answer is yes. criteria with true/false descriptions is optional but recommended.
- choice — pick one of 2–20 labelled options. Returns choice (the selected label), probabilities per option and a confidence value.
- score — place the state on an ordered scale of 2–10 levels, e.g. [Calm, Frustrated, Very angry]. Returns a continuous score (0 = first level), probabilities over the levels, a legend mapping score ranges back to labels, and confidence.
Up to eight questions ride in one request — that matters for cost, because billing is per input token with a 1-credit minimum, not per question. Three questions over the same state cost the same as one, whenever the total stays under 1,000 tokens.
Parsing the answer in Python and JavaScript
import os, requests
resp = requests.post(
"https://jevai.website/api/v1/systemone",
headers={"Authorization": f"Bearer {os.environ['JEV_API_KEY']}"},
json={
"state": {
"ticket": {"subject": "Refund duplicate charge", "order": "A-104"},
"refund_policy": "Duplicate charges are eligible for a full refund.",
},
"questions": {
"policy_supports": {"type": "noul", "instructions": "Does the policy support this refund?"},
"frustration": {"type": "score", "instructions": "How frustrated is the customer?",
"criteria": ["Calm", "Frustrated", "Very angry"]},
},
},
timeout=30,
)
resp.raise_for_status()
answers = resp.json()["answers"]
if answers["policy_supports"]["noul"] > 0.9:
print("auto-approve; frustration =", answers["frustration"]["legend"])Pythonconst resp = await fetch("https://jevai.website/api/v1/systemone", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JEV_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
state: "Charged twice for order A-104, please refund the duplicate.",
questions: {
route: {
type: "choice",
instructions: "Which team handles this?",
criteria: {
billing: "Payments, payouts, invoicing, refunds",
technical: "Bugs, outages, integrations",
sales: "Pricing, upgrades, new accounts",
},
},
},
}),
});
const { answers, usage, latency_ms } = await resp.json();
console.log(answers.route.choice, answers.route.probabilities, latency_ms);JavaScript / TypeScriptError codes and retries
Errors always use one shape: {error: {code, message, request_id}}. Branch on code, not on the message text. The table:
- 401 missing_api_key / invalid_api_key — no Bearer header, or the key was revoked. Do not retry.
- 402 insufficient_credits — the workspace balance could not cover the reservation. Do not retry; top up.
- 422 invalid_request — malformed body, bad question names, zero questions, state over 100,000 chars. Do not retry; fix the request.
- 429 rate_limited — the plan's concurrency or requests-per-minute ceiling was hit. Retry with exponential backoff; the response carries Retry-After and X-RateLimit-Remaining headers.
- 502 upstream_error (also upstream_timeout, upstream_unreachable) — the model provider failed or the 15-second upstream window expired. Retry with backoff; you were not charged.
- 529 upstream_overloaded — provider capacity. Same treatment as 502.
import time
def decide(body, max_retries=3):
for attempt in range(max_retries + 1):
resp = requests.post(URL, headers=HEADERS, json=body, timeout=30)
if resp.status_code in (429, 502, 529):
time.sleep(min(2 ** attempt, 8))
continue
resp.raise_for_status()
return resp.json()
raise RuntimeError("upstream kept failing; give up or degrade")Python retry loop worth copyingCredits are reserved before the model runs and settled afterwards against the real token count; if the provider fails, the reservation is returned automatically. A failed request never leaves a charge on the ledger.
What it costs and how fast it answers
One credit is 1,000 input tokens; a charge is ceil(input tokens / 1,000), minimum 1, and output tokens are free. In the test above, a 401-token request with two questions cost exactly 1 credit. On the Starter pack ($10 = 100,000 credits) that is $0.0001 per decision.
Latency: over 16 successive live calls on a single business day, the reported latency_ms clustered around 400–560 ms for both one-question and two-question requests, with a median near 445 ms. The first call after an idle period hit an occasional ~3 s cold start; longer tails were not observed at this sample size. Set your timeout from the p99 of your own region rather than trusting this one measurement.
Can I ask more than 8 questions in one request?
No — the limit is 8 questions per request. Split the rest into a second request over the same state, or move lower-value questions to a cheaper batched path.
Where do I get my first free credits?
Every new workspace starts with 5 free credits, and the playground is free in the browser (30 runs per day anonymously, 50 when signed in). Five credits is enough to validate a decision on real data before paying anything.
Is this the official TypeSafe endpoint?
No. Optio is an independent gateway for calling Jev and is not affiliated with TypeSafe. The request shape follows the same /v1/systemone contract, so existing clients can point at it.
Try the Jev AI playground to send your first free decision after you finish this guide.