Evally
Get started

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

  1. 1Sign up and create an API key on the dashboard. It’s shown once, so copy it.
  2. 2Store it server-side as EVALLY_API_KEY.
  3. 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.

Without a key, built-in templates and /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 Code
claude mcp add --transport http evally https://tryevally.com/api/mcp \  --header "Authorization: Bearer $EVALLY_API_KEY"
Cursor and other clients · mcp.json
{  "mcpServers": {    "evally": {      "url": "https://tryevally.com/api/mcp",      "headers": { "Authorization": "Bearer evl_live_…" }    }  }}
ToolDoes
list_judgesThe ready-made judges, by category or search: what each decides, its inputs and its verdicts. Free.
get_judgeOne judge's inputs, questions, verdicts and an example. Free.
run_judgeRuns a judge on one input: your own judge with that slug, or the template. One judgment.
run_judge_linesRuns a judge on every line of a text. One judgment per line.
list_flowsThe flows you can run. Free.
run_flowSends an item through a flow and returns its lane. One judgment per stage it passes.
rankReorders 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.

PartWhat it is
fieldsThe inputs the judge reads, like source and answer. Questions refer to them in backticks.
questionsClosed 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.
outcomesThe verdicts the judge can give, each with a tone (pass, flag, fail or neutral) and optionally sent to the review queue.
rulesChecked 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.

a judge spec
{  "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

MethodPathRuns
POST/api/v1/judges/{slug}/runYour 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

FieldTypeDescription
verdictstringThe outcome's key, e.g. block or billing. Branch on this.
labelstringThe outcome's display name.
tone"pass" | "flag" | "fail" | "neutral"How the verdict should read.
needs_reviewbooleanTrue when the outcome is marked for review. The run is also in your review queue.
scorenumber | nullThe weighted score from 0 to 1, when the judge defines one.
answersobjectEvery question's typed answer, by key. Shapes below.
rulenumber | nullIndex of the rule that decided, or null when the fallback applied.
idnumber | nullThe run's id in your log (null on the public demo).
judge, ms, model, usageobjectWhich judge and version ran, time, model and tokens.
200 OK · application/json
{  "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 }}
answer shapes
// 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.

lines.js
// 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 these

Test 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.

MethodPathDoes
POST/api/v1/flows/{slug}/runRuns one item: your flow with that slug, or the ready-made one. Returns the lane and the path.
GET/api/v1/flowsLists 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.

flow.js
// 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`?”

rank
// 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.

decide
// 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

WhatLimit
Judgments per month500 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 judge40 (plus up to 6 evidence quotes). Decide accepts 64.
Input size20,000 characters per field and 48,000 per run.
Batch size5,000 rows, within your remaining monthly judgments.
Flows12 stages and 16 lanes per flow. Each stage an item passes is one judgment.
Line by line60 lines per request, 1,000 characters each. One judgment per line.
Ad studies2,000 ads per study. One judgment per ad, and one per scored brief.
Stored dataAnswers 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": "…" }.

StatusMeaning
400The body isn't valid JSON, a required input is missing, or the spec or questions are invalid.
401The API key is unknown or revoked, or the endpoint needs a key.
404No judge or template with that slug, or that version doesn't exist.
429The plan's judgments for the month are used up, or the public demo's limit was hit. Monthly allowances reset on the 1st (UTC).
500The server isn't configured with its judging credentials.
502The judging model couldn't be reached. Safe to retry.
503Evally 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.

guardrails.js
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.

agent.js
// 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.