Developers
Calling the API with your key
Two endpoints, one key, one balance. Everything here works with a free key and nothing here needs an SDK.
Authentication
A bearer token on every request. Get one free at /api-access — no card, no waitlist — and manage it on your dashboard.
Authorization: Bearer jv_live_…
Keys are stored hashed and shown exactly once. A lost key cannot be recovered by anyone, including us — create another instead. Up to five at a time, so staging and production can be revoked independently.
POST /api/v1/classify
Text plus a ruleset name. No question shapes to write, no model to choose.
curl -X POST https://jev-agent.com/api/v1/classify \
-H "Authorization: Bearer $JEV_AGENT_KEY" \
-H "content-type: application/json" \
-d '{
"ruleset": "support-ticket-triage",
"items": ["Charged twice for September and cancelling Friday unless refunded."]
}'{
"ruleset": "support-ticket-triage",
"ruleset_version": 1,
"model": "jev-1.13.0",
"results": [
{ "index": 0,
"model": "jev-1.13.0",
"answers": {
"queue": { "type": "choice", "choice": "billing", "confidence": 1 },
"severity": { "type": "score", "score": 2.48, "legend": {"0":"Low","3":"Urgent"} },
"churn_risk": { "type": "noul", "noul": 0.97 }
},
"latency_ms": 333 }
],
"usage": { "credits_charged": 1, "used": 1, "limit": 5, "remaining": 4 }
}Up to 20 items per request. Each item is a separate upstream call; failures are reported per item rather than failing the batch, so one malformed ticket does not discard nineteen good answers.
The rulesets
| id | What it decides | Questions |
|---|---|---|
| support-ticket-triage | Route an inbound ticket to a queue, grade its urgency, and flag churn risk. | 3 |
| llm-model-routing | Decide how much model capability a request actually needs, before you spend it. | 1 |
| agent-tool-selection | Pick which tool an agent should call next from a fixed toolset. | 1 |
| content-moderation | Grade harm on a four-band rubric and flag targeting and spam separately. | 3 |
| phishing-detection | Independent yes/no signals for an email — combine them yourself rather than trusting one verdict. | 5 |
| rag-reranking | Score a retrieved passage for real relevance, not embedding similarity. | 1 |
| agent-output-guardrails | Verification checks on a draft response before it reaches a user. | 4 |
| lead-scoring | Score fit and intent against a rubric so hot leads reach a human today. | 3 |
The exact Jev call each one makes is public at GET /api/v1/rulesets/<id>, no key required. That is deliberate: take it, add your own TypeSafe key, and you never have to call us again.
POST /api/v1/systemone
Your own questions, in the official shape.
curl -X POST https://jev-agent.com/api/v1/systemone \
-H "Authorization: Bearer $JEV_AGENT_KEY" \
-H "content-type: application/json" \
-d '{
"state": "Charged twice for September and cancelling Friday unless refunded.",
"questions": {
"queue": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": { "billing": "Payments and refunds", "technical": "Bugs and outages" }
},
"severity": {
"type": "score",
"instructions": "How urgent is this?",
"criteria": ["Low", "Normal", "High", "Urgent"]
},
"churn": {
"type": "noul",
"instructions": "Threatening to cancel?",
"criteria": { "true": "Mentions cancelling", "false": "No cancellation intent" }
}
}
}'Caps here are ours, not the API's: 8,000 characters of state, 10 questions, 255 options on a choice. model is optional and accepts jev-latest, jev-preview or jev-1.13.0.
Three answer shapes that catch people out
scoreis not 0–1. It is the probability-weighted mean of the rubric indices, so it runs 0..n−1:2.48on a four-level rubric means “between High and Urgent”. Round for the band; the fraction says which way the mass leans.- Score probabilities are keyed by index, as strings —
"0","1"— never by your label. Uselegendto map back. - A
noulhas noconfidencefield. Not null — absent. Distance from 0.5 is what you have.
State
state is what you are asking about. One state per request; every question sees it and is evaluated independently, so you can mix all three types in one call.
It does not have to be a string. A JSON object or an array of text values works too, and an object is usually better — each part of the state gets a name, and the relationships between the parts stay visible.
{
"state": {
"ticket": {
"subject": "Duplicate charge",
"messages": [
{ "from": "customer", "text": "Charged twice for order A-104." },
{ "from": "support", "text": "We are checking the charges." }
]
},
"order": { "id": "A-104", "charges": [ { "usd": 49 }, { "usd": 49 } ] },
"refund_policy": "Duplicate charges are eligible for a refund."
},
"questions": { "…": "…" }
}That is one state, even though it holds a conversation, an order and a policy. Put related material together when the decision needs the parts compared. Text only — no images, no audio. English is where accuracy is best; other languages including CJK are accepted and are measurably weaker.
Confidence, and what to do with it
Choice and Score return a confidence; Noul does not, and its distance from 0.5 is the equivalent. Confidence is not the same as the winning probability: a two-option choice split 51/49 has a clear winner and very little certainty, and that difference is the whole reason to read it.
const { answers } = await callJev(ticket, { route: routeQuestion });
if (answers.route.confidence >= 0.85) {
dispatch(answers.route.choice); // decided
} else {
escalate(ticket); // not sure enough — ask a human or an LLM
}Pick the threshold against your own labelled data. The right number depends entirely on what a wrong decision costs you, and our own measurements put the separation between easy and genuinely ambiguous inputs at about 0.98 against 0.84 — real, but narrower than you might assume before testing.
Four patterns worth knowing
Speculative fan-out
Questions share one state and run in parallel, so asking five costs roughly the latency of asking one — measured: 70ms for one, 74ms for five. Ask everything you might want, including questions you will probably discard, and let your code decide what was relevant. Three separate calls pay for the state three times.
Confidence-gated routing
Two axes, not one: the answer says what, the confidence says whether to act on it unattended. A cheap fast path when sure, a human or an LLM when not.
Composite scoring
Do not ask one vague question. Ask several atomic ones and combine them with weights you own in code — the weights stay auditable and changeable without touching the model.
Intent routing
Classify the incoming request, then send each class to the handler that fits: deterministic code, a specialist model, or a person. This is the most common thing people build with Jev.
What a call costs
One credit is 1,000 input tokens, the unit TypeSafe bills us in. A request costs ceil(input_tokens / 1,000) with a floor of one, charged per request rather than per question — questions share the state and the state is nearly all of the tokens. A typical support ticket with three questions is about 440 input tokens, so it costs a single credit.
| What you send | Input tokens | Credits |
|---|---|---|
| A support ticket, three questions | ~440 | 1 |
| A short email, one question | ~200 | 1 |
| A page of documentation | ~1,800 | 2 |
| The 8,000-character maximum | ~2,000 | 2 |
| 20 tickets through /classify in one call | ~8,800 | 9 |
There is no per-request ceiling, so the published limits are the real ones: a fully loaded /v1/systemone call — 8,000 characters of state, ten questions, hundreds of options — can reach about 31 credits, and a twenty-item batch of 4,000-character items about 21. Those are the extremes of what the caps allow, not what normal use looks like, but nothing stops you reaching them.
Five requests and five credits are not the same thing. If you want the number of calls to be predictable, keep the state short; if you want the cheapest possible answer per decision, ask several questions in one request rather than several requests — they share the state, and the state is nearly all of the tokens.
Every account gets 5 free credits a month, spent before anything you have bought. Purchased credits never expire. Failed requests are not charged, because the charge is settled from the token count in a successful response. Top up or check GET /api/keys with your key for the current balance.
Limits
There is no per-minute limit on the API at any tier — a key is metered by credits, not frequency. The size ceilings are ours and holding credits raises them: 8,000 to 32,000 characters of state, 10 to 20 questions per request, and 20 to 50 items per /classify batch. Full table on the credits page.
Errors
| 400 | Malformed body — the message names the field. Sending `kind` instead of `type` lands here. |
| 401 | Missing, unrecognised or revoked key. A TypeSafe key will not work on this host, and vice versa. |
| 429 | Out of credits. The free grant resets on the 1st; top-ups do not expire. |
| 502 | Upstream refused or timed out. Nothing is charged. |
| 503 | Our key store or the API is unavailable. Nothing is charged. |
Every error is {"error": "…"} with a sentence a human can act on, and the useful ones carry extra fields — a 400 on an unknown ruleset lists the valid ones.
Managing keys from code
curl https://jev-agent.com/api/keys -H "Authorization: Bearer $JEV_AGENT_KEY" # usage curl -X PUT https://jev-agent.com/api/keys -H "Authorization: Bearer $JEV_AGENT_KEY" # rotate curl -X DELETE https://jev-agent.com/api/keys -H "Authorization: Bearer $JEV_AGENT_KEY" # revoke
Rotating kills the old key immediately and returns a new one; the month's usage follows your account, not the credential, so it is not a way to reset the counter.