Typed verdicts for every AI decision
Evally runs judges: small sets of closed questions that Jev answers with calibrated probabilities, and rules in code that turn those answers into a verdict. Use one of 76 ready-made judges as-is, or build your own.
Quickstart
- 1Sign up and create an API key on the dashboard. It’s shown once, so copy it.
- 2Store it server-side as
EVALLY_API_KEY. - 3Pick a judge from the use-case gallery and call it by its slug:
curl -X POST https://tryevally.com/api/v1/judges/hallucination-detector/run \ -H "Authorization: Bearer $EVALLY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "source": "The Pro plan includes 50,000 judgments a month and email support.", "answer": "Pro includes 50,000 judgments a month and phone support." } }'Authentication
Send your key as a bearer token: Authorization: Bearer evl_live_…. Keys are stored hashed, shown once, and can be revoked from the dashboard. A revoked or unknown key returns 401. Keep keys on your server; never ship them to a browser.
/api/v1/rank (up to 20 items) run on a small public demo allowance: 10 runs per visitor per hour. That’s for trying things, not for production.MCP server
Evally is also an MCP server, so Claude Code, Cursor, Codex and other MCP clients can find and run judges as tools. It speaks Streamable HTTP at https://tryevally.com/api/mcp; send your API key as a bearer token.
claude mcp add --transport http evally https://tryevally.com/api/mcp \ --header "Authorization: Bearer $EVALLY_API_KEY"{ "mcpServers": { "evally": { "url": "https://tryevally.com/api/mcp", "headers": { "Authorization": "Bearer evl_live_…" } } }}| Tool | Does |
|---|---|
list_judges | The ready-made judges, by category or search: what each decides, its inputs and its verdicts. Free. |
get_judge | One judge's inputs, questions, verdicts and an example. Free. |
run_judge | Runs a judge on one input: your own judge with that slug, or the template. One judgment. |
run_judge_lines | Runs a judge on every line of a text. One judgment per line. |
list_flows | The flows you can run. Free. |
run_flow | Sends an item through a flow and returns its lane. One judgment per stage it passes. |
rank | Reorders items by relevance to a query. One judgment per 20 items. |
Every tool calls the same endpoint as the REST API, so runs are metered and limited exactly the same way.
How judges work
A judge is plain JSON in four parts, and every run is one call to Jev, however many questions it asks.
| Part | What it is |
|---|---|
fields | The inputs the judge reads, like source and answer. Questions refer to them in backticks. |
questions | Closed questions Jev answers: yes_no (a probability), choice (one option from a list, with every option’s probability) or score (a value on a rubric of 2-10 levels). A yes/no question can quote the sentence that answers it. |
outcomes | The verdicts the judge can give, each with a tone (pass, flag, fail or neutral) and optionally sent to the review queue. |
rules | Checked top to bottom in code; the first whose conditions all hold picks the verdict, or routes by a pick-one answer. Otherwise, the fallback applies. |
An optional weighted score combines answers into one 0-1 number that rules can threshold, like a lead score or a slop score.
{ "v": 1, "fields": [{ "key": "reply", "label": "Reply", "long": true }], "questions": [ { "type": "yes_no", "key": "refund_promise", "label": "Promises a refund", "ask": "Does the `reply` promise a refund? (Explaining the refund policy doesn't count.)", "evidence": "reply" }, { "type": "score", "key": "tone", "label": "Tone", "ask": "How is the tone of the `reply`?", "levels": ["Rude", "Cold", "Polite", "Warm"] } ], "outcomes": [ { "key": "send", "label": "Send", "tone": "pass" }, { "key": "hold", "label": "Hold for review", "tone": "flag", "review": true } ], "rules": [ { "when": [{ "q": "refund_promise", "op": "gte", "value": 0.6 }], "then": "hold" }, { "when": [{ "q": "tone", "op": "lt", "value": 1.5 }], "then": "hold" } ], "fallback": "send"}Run a judge
| Method | Path | Runs |
|---|---|---|
POST | /api/v1/judges/{slug}/run | Your own judge with that slug, or else the built-in template with that slug. So every template works with no setup. |
Body: { "input": { … }, "version"?: 3 }. One call is one judgment, however many questions the judge asks.
Input
Send input as an object keyed by the judge’s fields, or as a plain string when the judge has one field. Values are trimmed; each field is capped at 20,000 characters and 48,000 in total. Required fields must be non-empty, and unknown keys are ignored.
Response
| Field | Type | Description |
|---|---|---|
verdict | string | The outcome's key, e.g. block or billing. Branch on this. |
label | string | The outcome's display name. |
tone | "pass" | "flag" | "fail" | "neutral" | How the verdict should read. |
needs_review | boolean | True when the outcome is marked for review. The run is also in your review queue. |
score | number | null | The weighted score from 0 to 1, when the judge defines one. |
answers | object | Every question's typed answer, by key. Shapes below. |
rule | number | null | Index of the rule that decided, or null when the fallback applied. |
id | number | null | The run's id in your log (null on the public demo). |
judge, ms, model, usage | object | Which judge and version ran, time, model and tokens. |
{ "id": 48213, "verdict": "hallucinated", "label": "Hallucinated", "tone": "fail", "needs_review": false, "score": null, "rule": 1, "answers": { "unsupported": { "type": "yes_no", "value": 0.99, "evidence": { "text": "Pro includes 50,000 judgments a month and phone support.", "p": 0.97 } }, "numbers": { "type": "yes_no", "value": 0.06 }, "entities": { "type": "yes_no", "value": 0.11 }, "contradiction": { "type": "yes_no", "value": 0.92 } }, "judge": { "slug": "hallucination-detector", "name": "Hallucination detector", "version": null, "template": true }, "ms": 394, "model": "jev-1.13.0", "usage": { "input_tokens": 473, "output_tokens": 20 }}// yes/no: the probability that the answer is yes, and optionally the sentence that shows it{ "type": "yes_no", "value": 0.93, "evidence": { "text": "…", "p": 0.81 } } // pick one: the chosen option, how sure, and every option's probability{ "type": "choice", "choice": "billing", "confidence": 0.88, "probabilities": { "billing": 0.88, "bug": 0.1, "how_to": 0.02 } } // score: the expected value on the rubric (0 to max), the most likely level, and its distribution{ "type": "score", "value": 2.4, "max": 3, "confidence": 0.61, "level": { "index": 2, "text": "Good fit" }, "probabilities": [0.02, 0.1, 0.34, 0.54] }Versions
Saving a changed judge creates a new version; calls use the latest. To keep production on a version you’ve tested, pass "version": 3. The version that ran is in judge.version and on every logged run. Templates aren’t versioned: fork one to pin it.
Line by line
POST /api/v1/judges/{slug}/lines runs a judge on every line of a text: a speech, a script, a draft, a transcript. Send text (one line per line, or a paragraph to split into sentences) or lines; field names the input each line goes into (the judge’s first field by default), before an input that receives the three lines before it, and context fills the judge’s other inputs for every line. Up to 60 lines; each is one judgment. The Audience test is this endpoint running the audience-reaction judge.
// Every sentence of a draft through the draft-sentence-check judge.const res = await fetch("https://tryevally.com/api/v1/judges/draft-sentence-check/lines", { method: "POST", headers: { Authorization: "Bearer " + process.env.EVALLY_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ text: draft, // one line per line, or a paragraph to split into sentences field: "sentence", // the input each line goes into context: { goal: "Get developers to try the free plan", sources: releaseNotes }, }),});const { lines } = await res.json();const toFix = lines.filter((l) => l.verdict !== "keep"); // rewrite only theseTest a spec
POST /api/v1/judges/test with { "spec": { … }, "input": { … } } runs a spec without saving it. It’s what the builder’s test panel uses, and handy for checking a spec from code before you save it. Invalid specs return 400 with an issues array of paths and messages.
Spec check
A badly designed question still gets a confident answer: a pick-one with no “none of these” option sends inputs that fit nothing to some option at 0.94-0.99 confidence, past any confidence gate. POST /api/v1/judges/check with { "spec": { … } } returns the structural checks for free: missing escape options, ratings asked as yes/no, two questions in one, references to fields that don’t exist, levels that are bare numbers. Add "semantic": true and Jev also reviews each question (one judgment per question). The builder shows the same checks as you edit.
Templates
GET /api/v1/templates lists every template with its fields and verdicts; GET /api/v1/templates/{slug} returns its full spec and sample inputs. No key needed.
Flows
A flow chains judges into the decisions an agent team makes about every item: worth reading, which agent takes it, does the claim hold, have we seen it. Each stage is a judge whose verdicts route the item to the next stage or to a lane: an agent’s queue, a deliver step, store, escalate to a person or a bigger model, or drop. Entries let work come back in, so a desk’s note can re-enter at the fact-check. Build and watch flows from the app; see the control room.
| Method | Path | Does |
|---|---|---|
POST | /api/v1/flows/{slug}/run | Runs one item: your flow with that slug, or the ready-made one. Returns the lane and the path. |
GET | /api/v1/flows | Lists the ready-made flows, and yours when you're signed in. |
Body: { "input": { … }, "entry"?: "review" }. Every stage an item passes is one judgment, so an item that is skipped at the first gate costs one. Ready-made flows also run on the public demo allowance, one demo run per item.
// A new item comes in: screen it, route it, hand it to the lane's owner.const res = await fetch("https://tryevally.com/api/v1/flows/research-desk/run", { method: "POST", headers: { Authorization: "Bearer " + process.env.EVALLY_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ entry: "intake", input: { title, text, source: "arxiv" } }),});const { lane, path } = await res.json();// lane: { key: "papers", label: "Papers desk", kind: "agent" }// path: every stage it passed, with its verdict, signal and time if (lane.kind === "agent") await queues[lane.key].push(item); // Later, the desk's note comes back in at the fact-check:await fetch("https://tryevally.com/api/v1/flows/research-desk/run", { method: "POST", headers: { Authorization: "Bearer " + process.env.EVALLY_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ entry: "review", input: { title, text, note } }),});Is it a judge?
POST /api/v1/swap with { "prompt": "…", "usage"?: "…" } reads an LLM call from your code and says whether it’s really a decision: swap (a judge can take it over), split (move the decision into a judge, keep the LLM for the writing) or keep. For swap and split it suggests up to three ready-made judges. One judgment for the check plus one per 20 judges ranked. Try it on a prompt.
Rank
Rerank search results, recommendations or candidates against a query. Jev scores each item’s relevance on a five-level rubric and code sorts them. Up to 200 items per call; every 20 items is one judgment. Pass question to redefine relevance, referring to `item` and `query`, e.g. “How likely is `item` to hire the candidate in `query`?”
// POST /api/v1/rank{ "query": "I can't log in after changing my phone", "items": [ "How to change your billing address", { "id": "kb-42", "text": "Recovering your account when you lose your authenticator" }, "Resetting a forgotten password" ], "top_k": 2} // 200 OK: best first; score is relevance from 0 to 1{ "results": [ { "index": 1, "id": "kb-42", "score": 0.96, "level": 4, "label": "Exactly what the query is looking for" }, { "index": 2, "id": null, "score": 0.71, "level": 3, "label": "Relevant" } ], "judgments": 1, "ms": 412, "model": "jev-1.13.0", "usage": { "input_tokens": 388, "output_tokens": 45 }}Decide
For agents, games and anything that needs Jev’s primitives directly: send any state and up to 64 questions in TypeSafe’s format (noul, choice with up to 255 options, score with 2-10 levels). You get Jev’s answers back unchanged, with your key, metering and run log in front. Requires an API key or a session.
// POST /api/v1/decide: TypeSafe's primitives, metered and logged{ "state": { "screen": "Checkout", "cart_total": 129.5, "user_message": "use my saved card" }, "questions": { "action": { "type": "choice", "instructions": "What should the agent do next?", "criteria": { "pay_saved_card": null, "ask_for_card": null, "cancel": null } }, "irreversible": { "type": "noul", "instructions": "Would the next action spend money?" } }} // 200 OK{ "id": 48219, "answers": { "action": { "type": "choice", "choice": "pay_saved_card", "confidence": 0.94, "probabilities": { … } }, "irreversible": { "type": "noul", "noul": 0.97 } }, "model": "jev-1.13.0", "ms": 288, "usage": { … } }Quick eval
The original endpoint, POST /api/judge with output, optional context and question, still runs four fixed checks (grounded, answers, safe, quality) and returns overall: pass, flag or fail. New integrations should use the RAG answer grader judge, which asks the same questions with editable rules.
Batches
Run a judge over a whole dataset from the app: upload a CSV or JSON Lines file (up to 5,000 rows), match its columns to the judge’s fields, and start. Add a column of expected verdicts to see how often the judge agrees with you, then open the disagreements. Results export as CSV with every answer. Each row is one judgment.
Ad research
An ad study is a batch with a purpose: load a category’s ads with how long each has been running (days, or a start date), and the ad-anatomy judge labels each one’s format, hook, angle, awareness stage, offer, proof, objection and call to action. The study page maps every ad by days live and variants, compares each pattern’s survival rate with the category’s, and, when the ads carry a brand, builds the category playbook: the patterns two or more brands each keep a long-runner with. Labels that were a coin flip between two options (under five points apart) are listed for a person to check. It also lists the survivors’ opening lines and scores briefs with ad-brief-scorer: hook, brand fit, survival odds, and whether every claim is backed by your product facts. Up to 2,000 ads per study; each ad is one judgment, and so is each brief. Both judges are templates, so you can call them directly too. See a finished study.
Keyword research
A keyword study is a batch of search results: upload page one for each keyword (the title and link of every result, plus the snippet, publish date, domain rating and search volume when your SEO tool has them), and the serp-beatability judge asks five questions of each result: does it answer the exact search, is it a forum thread, a thin page, out of date, on a strong site. A result that fails any of them is beatable, and a keyword with three or more beatable results on page one is takeable (you can set the bar from two to five). The study page shows every keyword’s page one, why its results are beatable, the keyword to go after first, and the results Jev was split on. Only the top ten per keyword are read, up to 2,000 results per study; each result is one judgment. See a finished study.
Review queue
Any outcome can be marked for review. Runs that land on it, from the API, the app or a batch, wait in your review queue, where a person confirms or corrects the verdict. Each judge’s page tracks how often reviewers agree, so you know when it can be trusted with more. The API also returns needs_review: true so your code can hold the result.
Limits and metering
| What | Limit |
|---|---|
| Judgments per month | 500 on Hobby, 50,000 on Pro, 250,000 on Growth. One judgment is one call to Jev: one judge run, one decide call, or up to 20 ranked items. |
| Questions per judge | 40 (plus up to 6 evidence quotes). Decide accepts 64. |
| Input size | 20,000 characters per field and 48,000 per run. |
| Batch size | 5,000 rows, within your remaining monthly judgments. |
| Flows | 12 stages and 16 lanes per flow. Each stage an item passes is one judgment. |
| Line by line | 60 lines per request, 1,000 characters each. One judgment per line. |
| Ad studies | 2,000 ads per study. One judgment per ad, and one per scored brief. |
| Stored data | Answers are logged for every run. Inputs are kept for app runs, runs that need review, and judges with input logging switched on. |
Errors
Errors return JSON as { "error": "…" }.
| Status | Meaning |
|---|---|
400 | The body isn't valid JSON, a required input is missing, or the spec or questions are invalid. |
401 | The API key is unknown or revoked, or the endpoint needs a key. |
404 | No judge or template with that slug, or that version doesn't exist. |
429 | The plan's judgments for the month are used up, or the public demo's limit was hit. Monthly allowances reset on the 1st (UTC). |
500 | The server isn't configured with its judging credentials. |
502 | The judging model couldn't be reached. Safe to retry. |
503 | Evally couldn't check the account. Safe to retry. |
Guardrail pattern
Screen what goes into your LLM and what comes out, with two ready-made judges. Each check costs one judgment and a few hundred milliseconds.
const guard = (slug, input) => fetch(`https://tryevally.com/api/v1/judges/${slug}/run`, { method: "POST", headers: { Authorization: "Bearer " + process.env.EVALLY_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ input }), }).then((r) => r.json()); async function safeChat(userMessage) { const inbound = await guard("prompt-injection-guard", { message: userMessage }); if (inbound.verdict === "block") return "I can't help with that."; const reply = await llm(userMessage); const outbound = await guard("llm-reply-guard", { prompt: userMessage, reply }); if (outbound.verdict === "support") return CRISIS_RESOURCES; if (outbound.verdict === "block") return "Let me connect you with someone who can help."; if (outbound.needs_review) queueForHuman(reply); // shows up in your review queue too return reply;}Agent firewall
Judge every tool call against the task before it runs. The agent never grades itself, and the decision is in code you control.
// Before your agent runs any tool call:const check = await fetch("https://tryevally.com/api/v1/judges/tool-call-firewall/run", { method: "POST", headers: { Authorization: "Bearer " + process.env.EVALLY_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ input: { task: agent.task, tool_call: JSON.stringify(call) } }),}).then((r) => r.json()); if (check.verdict === "deny") return agent.refuse(call, check.answers);if (check.verdict === "ask") return agent.askHuman(call); // needs_review is truereturn agent.run(call); // "allow"Confidence routing
Every pick-one and score answer carries a confidence. Use it as a second axis: act automatically when it’s high, ask for confirmation in the middle, and send low-confidence cases to a person or a bigger model. In a judge, that’s a rule like { "q": "team", "op": "conf_lt", "value": 0.5 } → an outcome marked for review. Check 50-100 cases you know the answers to before you trust any threshold; a batch with an expected column does exactly that.