Jev API Tutorial: Getting Started with OpenRouter (Keys, Requests, Errors, Retries)
·2 min read
Jev API Tutorial: Getting Started with OpenRouter
This Jev API tutorial takes you from zero to a typed response in about five minutes. Jev is served through OpenRouter, so one API key covers it alongside other models. Model ID: typesafe/jev-1.13.
TL;DR:
POST /api/v1/chat/completionswithcontent = { state, questions }. Readanswers[].selected,confidence, anddistribution. Retry 429; validate questions before send.
1. Get an OpenRouter API key
- Create an account at OpenRouter.
- Generate an API key (
sk-or-...). - Store it in an environment variable — never in frontend code or a git repo.
export OPENROUTER_API_KEY="sk-or-..."
No separate TypeSafe signup is required for this path.
2. Send your first request
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-1.13",
"messages": [{
"role": "user",
"content": {
"state": "I was charged twice for the same order #4821. Please fix this.",
"questions": [{
"type": "choice",
"text": "Which team should handle this?",
"options": ["billing", "tech_support", "sales"]
}]
}
}]
}'
Request shape
| Field | Type | Notes |
|---|---|---|
state |
string | Unstructured text (email, ticket, DOM snapshot…) |
questions[].type |
choice | score | noul |
Question type |
questions[].text |
string | The decision, phrased for branching |
questions[].options |
string[] | For choice/score |
You can send multiple questions in one call (e.g. intent + urgency).
3. Read the typed answer
{
"answers": [{
"type": "choice",
"text": "Which team should handle this?",
"selected": "billing",
"confidence": 0.94,
"distribution": {
"billing": 0.94,
"tech_support": 0.04,
"sales": 0.02
}
}]
}
selected— top option (or weighted pick for score).confidence— probability of the top option.distribution— full probability mass.
type JevAnswer = {
type: 'choice' | 'score' | 'noul';
text: string;
selected?: string;
confidence: number;
distribution?: Record<string, number>;
};
TypeScript client sketch
export async function runJev(state: string, questions: unknown[]) {
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'typesafe/jev-1.13',
messages: [{ role: 'user', content: { state, questions } }],
}),
});
if (!res.ok) throw new Error(`Jev HTTP ${res.status}`);
const json = await res.json();
return json.choices?.[0]?.message?.content?.answers ?? json.answers ?? json;
}
Validate questions locally before send in hot paths — missing options is a common 400.
Error handling and retries
| Status | Meaning | Action |
|---|---|---|
| 401 | Bad/missing key | Fail fast; check env |
| 400 | Invalid question payload | Fix request; do not retry blindly |
| 429 | Rate limit | Exponential backoff + jitter |
| 5xx | Upstream | Retry with cap; fallback path |
| Timeout | Network / slow | 2s budget; fall back to human/queue |
async function withRetry<T>(fn: () => Promise<T>, tries = 3): Promise<T> {
let lastErr: unknown;
for (let i = 0; i < tries; i++) {
try { return await fn(); } catch (e) {
lastErr = e;
await new Promise((r) => setTimeout(r, 2 ** i * 200 + Math.random() * 100));
}
}
throw lastErr;
}
Security notes for the Jev API
- Call Jev from a server or worker, not the browser, if the key is secret.
- If you must call from the client, use a proxy with auth and rate limits.
- Redact PII from
statewhen policy requires it. - Set an overall timeout (~2s) so product UX cannot hang.
Logging & observability
Log per call: model id, latency, selected, confidence, distribution hash, ticket/message id.
Alert on error rate and p95 latency; chart average confidence and entropy for drift.
Next steps
- Try presets in the Playground.
- Copy configs from the Scene library.
- Read question design before scaling labels.
FAQ
Is there an official SDK?
Community TypeScript/Python wrappers exist; the HTTP contract is OpenRouter-compatible.
Can I stream?
Classification returns a small typed payload — streaming is rarely useful.
How do I pin a model version?
Use the full model id typesafe/jev-1.13 and watch release notes before bumping.