<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Screwtape.ai</title>
    <description>The latest articles on DEV Community by Screwtape.ai (screwtape-ai).</description>
    <link>https://dev.to/screwtape-ai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15446%2F9f2b07d7-2a0c-446b-b82e-17f330ab96f7.png</url>
      <title>DEV Community: Screwtape.ai</title>
      <link>https://dev.to/screwtape-ai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/screwtape-ai"/>
    <language>en</language>
    <item>
      <title>An audit trail for agent payments</title>
      <dc:creator>Screwtape.ai</dc:creator>
      <pubDate>Wed, 28 Oct 2026 12:51:21 +0000</pubDate>
      <link>https://dev.to/screwtape-ai/an-audit-trail-for-agent-payments-2kck</link>
      <guid>https://dev.to/screwtape-ai/an-audit-trail-for-agent-payments-2kck</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was created with the help of AI by the Screwtape.ai team. Product details were checked against screwtape.ai/pricing, /docs and /openapi.json on 2026-10-24.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finance teams already know how to audit a person approving a payment: there is an approver, a timestamp, a policy and a document trail. When an AI agent does the approving, those pieces are often missing. The agent's log says what it did. It rarely shows what was checked, against which rule, and that nobody changed the record later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions an auditor will ask
&lt;/h2&gt;

&lt;p&gt;For any agent-approved payment, expect these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What was the agent about to pay?&lt;/strong&gt; The invoice, vendor and amount as the agent understood them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What was checked?&lt;/strong&gt; Required fields, totals, purchase order match, duplicate check, policy limits.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What was the result, and when?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who or what allowed the payment to go out?&lt;/strong&gt; And was that approval tied to this exact payment?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can you show the record hasn't been changed?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An agent's own chat log answers the first question at best.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a good trail looks like
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One record per decision&lt;/strong&gt;, written at the time of the check, not reconstructed afterwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Written by something other than the agent.&lt;/strong&gt; If the agent writes its own audit log, the log is only as trustworthy as the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tamper-evident.&lt;/strong&gt; Each record signed, and linked to the one before, so an edit or a missing entry shows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tied to the payment.&lt;/strong&gt; An approval should be usable once, for this invoice, this amount and this vendor, and expire quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exportable.&lt;/strong&gt; Your auditor wants a file, not a dashboard login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kept long enough&lt;/strong&gt; for your retention policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Screwtape.ai does it
&lt;/h2&gt;

&lt;p&gt;Screwtape.ai decides whether an AI agent is allowed to spend money, and keeps the proof. It is a separate check that runs outside your agent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every verdict comes with a &lt;strong&gt;signed receipt&lt;/strong&gt;. You can verify it yourself with &lt;code&gt;POST /v1/receipts/verify&lt;/code&gt; or against the public keys at &lt;code&gt;/.well-known/screwtape-receipt-keys.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Receipts can be &lt;strong&gt;exported&lt;/strong&gt; as JSONL, CSV or JSON (&lt;code&gt;GET /v1/receipts/export&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;On the &lt;strong&gt;Team&lt;/strong&gt; plan ($199/mo): 15-minute single-use pass tokens bound to the exact invoice, amount and vendor; linked, tamper-evident audit receipts; policy rules; 1-year receipt retention.&lt;/li&gt;
&lt;li&gt;On &lt;strong&gt;Finance Control&lt;/strong&gt; ($399/mo): the same, plus up to 5 policy sets or entities, unlimited API keys, 7-year receipt retention and an auditor export bundle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Invoice content is not stored by default; retention is opt-in per key (see screwtape.ai/privacy).&lt;/p&gt;

&lt;p&gt;A receipt records what was checked and the result. It does not make the decision right: verdicts come from AI models and automated checks and can be wrong, so keep a person in charge of payments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compare the Team and Finance Control plans: &lt;a href="https://screwtape.ai/pricing" rel="noopener noreferrer"&gt;screwtape.ai/pricing&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>finance</category>
      <category>audit</category>
      <category>aiagents</category>
      <category>accountspayable</category>
    </item>
    <item>
      <title>Stop your AI agent paying the same invoice twice</title>
      <dc:creator>Screwtape.ai</dc:creator>
      <pubDate>Wed, 28 Oct 2026 12:50:58 +0000</pubDate>
      <link>https://dev.to/screwtape-ai/stop-your-ai-agent-paying-the-same-invoice-twice-2cib</link>
      <guid>https://dev.to/screwtape-ai/stop-your-ai-agent-paying-the-same-invoice-twice-2cib</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was created with the help of AI by the Screwtape.ai team. Product details were checked against screwtape.ai/pricing, /docs and /openapi.json on 2026-10-24.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Duplicate payments are an old accounts-payable problem. The same invoice arrives twice: once by email and once through the portal, or re-sent with a new number after a reminder. A person pays one, a colleague pays the other.&lt;/p&gt;

&lt;p&gt;AI agents don't remove that problem. An agent that reads invoices and queues payments will do exactly what it is told, every time a document arrives. If two copies arrive, it can approve both, quickly and confidently.&lt;/p&gt;

&lt;p&gt;The fix is not a better prompt. It is a check that runs &lt;strong&gt;before&lt;/strong&gt; the payment goes out, outside the agent, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a duplicate check should compare
&lt;/h2&gt;

&lt;p&gt;A useful duplicate check looks at more than the invoice number, because duplicates rarely look identical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vendor + invoice number + amount.&lt;/strong&gt; The obvious case: the same invoice submitted twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor + amount + date within a short window.&lt;/strong&gt; Catches a re-sent invoice with a new number, or a typo in the number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Currency.&lt;/strong&gt; 1,000 USD and 1,000 EUR are not the same bill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result should not be a plain yes or no. "Possible duplicate" is a real answer, and the right response to it is to stop and ask a person, not to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the check should live
&lt;/h2&gt;

&lt;p&gt;Put it between "the agent decided to pay" and "the payment is released":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent extracts the invoice and proposes a decision.&lt;/li&gt;
&lt;li&gt;A separate check compares it with invoices already seen, and checks the basics: required fields present, line items adding up to the total.&lt;/li&gt;
&lt;li&gt;Only a clean result lets the payment continue. A duplicate or possible duplicate goes to a person.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping the check outside the agent matters. An agent asked "have you paid this already?" answers from whatever is in its context. A separate service answers from a record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to keep as proof
&lt;/h2&gt;

&lt;p&gt;When an auditor or a controller asks why a payment went out, "the agent said it was fine" is not an answer. Keep, for every decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what was checked and the result,&lt;/li&gt;
&lt;li&gt;when it was checked,&lt;/li&gt;
&lt;li&gt;a record you can show later that has not been edited since.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Screwtape.ai does it
&lt;/h2&gt;

&lt;p&gt;Screwtape.ai decides whether an AI agent is allowed to spend money, and keeps the proof. It is a separate check that runs outside your agent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate invoice check&lt;/strong&gt; (&lt;code&gt;POST /v1/duplicate&lt;/code&gt;): compares an invoice with the ones your API key recorded earlier, by vendor, invoice number and amount, with an optional same-vendor, same-amount, near-date rule. The answer is &lt;code&gt;unique&lt;/code&gt;, &lt;code&gt;duplicate&lt;/code&gt;, &lt;code&gt;possible_duplicate&lt;/code&gt;, &lt;code&gt;replay&lt;/code&gt; or &lt;code&gt;not_checked&lt;/code&gt;. Duplicates and possible duplicates escalate to a person. Only keyed fingerprints are stored, and only when you ask it to store them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invoice checks&lt;/strong&gt;: required fields, line items plus tax equal the total, and fields that must match the purchase order values you pass in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signed receipts&lt;/strong&gt;: every verdict comes with a receipt you can verify yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the Team and Finance Control plans you also get 15-minute single-use pass tokens bound to the exact invoice, amount and vendor, linked tamper-evident receipts, and policy rules such as amount caps, a vendor allowlist and second approval.&lt;/p&gt;

&lt;p&gt;A duplicate check only knows what it has seen, so it works best when every invoice your agent handles goes through it. Verdicts come from AI models and automated checks, and they can be wrong. Keep a person in charge of payments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it free (100 calls a day), then see the team plans: &lt;a href="https://screwtape.ai/pricing" rel="noopener noreferrer"&gt;screwtape.ai/pricing&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>finance</category>
      <category>accountspayable</category>
      <category>aiagents</category>
      <category>automation</category>
    </item>
    <item>
      <title>What is an LLM-as-judge API? How to call one for agent output</title>
      <dc:creator>Screwtape.ai</dc:creator>
      <pubDate>Sun, 25 Oct 2026 13:22:44 +0000</pubDate>
      <link>https://dev.to/screwtape-ai/what-is-an-llm-as-judge-api-how-to-call-one-for-agent-output-10jk</link>
      <guid>https://dev.to/screwtape-ai/what-is-an-llm-as-judge-api-how-to-call-one-for-agent-output-10jk</guid>
      <description>&lt;p&gt;&lt;strong&gt;An LLM-as-judge API is a hosted endpoint that takes a task and another model's output, then returns a structured judgment: a verdict, a score, and the reasons behind it.&lt;/strong&gt; You use one when an agent's output has to be checked before anything downstream trusts it, and you don't want to write and maintain the judging prompt yourself. Screwtape.ai's version is &lt;code&gt;POST /v1/judge&lt;/code&gt;. It returns &lt;code&gt;pass&lt;/code&gt;, &lt;code&gt;fail&lt;/code&gt; or &lt;code&gt;retry&lt;/code&gt;, a score from 0 to 1, and a list of reasons.&lt;/p&gt;

&lt;p&gt;Screwtape.ai decides whether an AI agent is allowed to spend money, and keeps the proof. The judge in this post is the same kind of check, pointed at any agent output instead of a payment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "LLM-as-judge" mean?
&lt;/h2&gt;

&lt;p&gt;LLM-as-judge is the pattern of using one language model to evaluate the output of another. The judge gets the original task, the candidate answer and, ideally, the evidence the answer should be based on. It returns a decision your code can branch on.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;LLM-as-judge API&lt;/strong&gt; moves that judge behind an HTTP endpoint. Your agent framework sends JSON and gets JSON back. You don't have to manage the judge prompt, the parsing or the model choice.&lt;/p&gt;

&lt;p&gt;Two definitions used in the rest of this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verdict&lt;/strong&gt;: a categorical decision about one output. In Screwtape it's one of &lt;code&gt;pass&lt;/code&gt;, &lt;code&gt;fail&lt;/code&gt; or &lt;code&gt;retry&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grounding&lt;/strong&gt;: whether the claims in an output are supported by the evidence you supplied, as opposed to the model's own memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When should you use a judge API instead of a regex or a schema check?
&lt;/h2&gt;

&lt;p&gt;Use a schema validator when the question is structural, for example "Is this valid JSON with a &lt;code&gt;price&lt;/code&gt; field?" Use a judge when the question is about content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the worker answer the task that was actually asked?&lt;/li&gt;
&lt;li&gt;Is each claim backed by the evidence you retrieved?&lt;/li&gt;
&lt;li&gt;Does the output cover the criteria you care about, such as a named entity, a date or a required section?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions come up at hand-off points in multi-agent systems. A researcher passes notes to a writer, a planner passes steps to an executor, or a sub-agent returns a result to an orchestrator. Each hand-off is a place where one unchecked mistake gets copied forward. If several agents then agree on it, you get the false consensus described in &lt;a href="https://screwtape.ai/blog/false-consensus/" rel="noopener noreferrer"&gt;Stop false consensus in multi-agent swarms&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A judge is not a replacement for tests or schema checks. It's an extra gate for the things those checks can't see.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Screwtape's &lt;code&gt;/v1/judge&lt;/code&gt; endpoint accept and return?
&lt;/h2&gt;

&lt;p&gt;Here's the contract as published in &lt;a href="https://screwtape.ai/openapi.json" rel="noopener noreferrer"&gt;openapi.json&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;What the agent was asked to do&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent_output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;The output being judged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;evidence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;string[]&lt;/td&gt;
&lt;td&gt;Source passages the output should be grounded in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;criteria&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;string[]&lt;/td&gt;
&lt;td&gt;Things the output must cover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;retain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;Store this call for 24 hours (API-key callers only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The request schema sets &lt;code&gt;additionalProperties: false&lt;/code&gt;, so any extra fields are rejected.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;200&lt;/code&gt; response always contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;verdict&lt;/code&gt;: &lt;code&gt;pass&lt;/code&gt;, &lt;code&gt;fail&lt;/code&gt; or &lt;code&gt;retry&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;score&lt;/code&gt;: a number from 0 to 1&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reasons&lt;/code&gt;: an array of strings explaining the verdict&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;request_id&lt;/code&gt;: an id for support and logging&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;engine&lt;/code&gt;: &lt;code&gt;llm&lt;/code&gt; or &lt;code&gt;deterministic&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retention&lt;/code&gt;: &lt;code&gt;{ "retained": false }&lt;/code&gt; unless you opted in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other documented status codes are &lt;code&gt;400&lt;/code&gt; (invalid request), &lt;code&gt;401&lt;/code&gt; (API key not recognized), &lt;code&gt;402&lt;/code&gt; (payment required, when you send no key) and &lt;code&gt;429&lt;/code&gt; (daily quota or rate limit exceeded).&lt;/p&gt;

&lt;h2&gt;
  
  
  Which model does the judging?
&lt;/h2&gt;

&lt;p&gt;When a model key is configured, the default model is &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;. If Groq fails, Workers AI is tried next. According to the &lt;a href="https://screwtape.ai/privacy" rel="noopener noreferrer"&gt;privacy page&lt;/a&gt;, the default OpenAI-compatible base URL is Groq.&lt;/p&gt;

&lt;p&gt;With no model key, the service uses a &lt;strong&gt;deterministic engine&lt;/strong&gt;, which does string checks of claims against evidence and of criteria against the output. No model is called in that mode. The &lt;code&gt;engine&lt;/code&gt; field in every response tells you which one ran. On 2026-10-20, &lt;code&gt;GET /v1/health&lt;/code&gt; reported &lt;code&gt;"llm_configured": true&lt;/code&gt; for the live deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I call the judge API with curl?
&lt;/h2&gt;

&lt;p&gt;This is the &lt;code&gt;grounded&lt;/code&gt; example from the OpenAPI spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://screwtape.ai/v1/judge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$SCREWTAPE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "task": "State the crew size and launch site.",
    "agent_output": "The crew size was 4. The mission launched from Cape Canaveral.",
    "evidence": ["The mission launched on 2024-03-01 from Cape Canaveral.", "The crew size was 4."],
    "criteria": ["crew size", "Cape Canaveral"]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you leave out the &lt;code&gt;Authorization&lt;/code&gt; header, you don't get a &lt;code&gt;401&lt;/code&gt;. You get a &lt;code&gt;402 Payment Required&lt;/code&gt;, and the body includes a &lt;code&gt;signup_url&lt;/code&gt; where you can get a free key. The key is checked before the body, so a request with no key gets the &lt;code&gt;402&lt;/code&gt; even if the body is invalid. With a valid key, a body that's missing &lt;code&gt;agent_output&lt;/code&gt; returns a &lt;code&gt;400&lt;/code&gt;. A key the API doesn't recognize returns a &lt;code&gt;401&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I call it from Python?
&lt;/h2&gt;

&lt;p&gt;This client uses &lt;code&gt;requests&lt;/code&gt;. It treats &lt;code&gt;402&lt;/code&gt; separately so a missing key is obvious rather than looking like a generic HTTP error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# judge_requests.py  (pip install requests)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://screwtape.ai/v1/judge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;judge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;POST /v1/judge. Returns the parsed JSON body; raises on non-200.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agent_output&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;criteria&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SCREWTAPE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# No key: the body points to the free signup page.
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;402: no API key. Get a free one at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;signup_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# 400 invalid body, 401 unknown key, 429 quota
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;judge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;State the crew size and launch site.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;agent_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The crew size was 4. The mission launched from Cape Canaveral.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The mission launched on 2024-03-01 from Cape Canaveral.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The crew size was 4.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crew size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cape Canaveral&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verdict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;engine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasons&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We ran this script with no key set. It raised the &lt;code&gt;402&lt;/code&gt; error with the signup link, which is the documented behavior. With a key, it prints the verdict, score, engine and reasons.&lt;/p&gt;

&lt;p&gt;We use &lt;code&gt;requests&lt;/code&gt; rather than &lt;code&gt;urllib&lt;/code&gt; for a reason. &lt;a href="https://screwtape.ai/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; notes that the API itself doesn't filter by User-Agent, but Cloudflare's Bot Fight Mode can return &lt;code&gt;403&lt;/code&gt; to Python's default &lt;code&gt;Python-urllib&lt;/code&gt; agent before the request reaches the API. If you must use &lt;code&gt;urllib&lt;/code&gt;, set an explicit &lt;code&gt;User-Agent&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should my code act on the verdict?
&lt;/h2&gt;

&lt;p&gt;The API returns a verdict. What you do with it is your own policy. A common mapping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pass&lt;/code&gt;: hand the output to the next step.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retry&lt;/code&gt;: run the worker again and include the &lt;code&gt;reasons&lt;/code&gt; in its prompt.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fail&lt;/code&gt;: stop, and escalate to a human or a different agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How you treat &lt;code&gt;score&lt;/code&gt; is up to you. The spec defines its range (0 to 1) but not a recommended threshold, so pick one from your own data. &lt;a href="https://screwtape.ai/blog/agent-guardrails-retry/" rel="noopener noreferrer"&gt;Agent guardrails with retry&lt;/a&gt; builds a bounded loop around this mapping, and &lt;a href="https://screwtape.ai/blog/validate-crewai-langgraph-output/" rel="noopener noreferrer"&gt;Validating CrewAI and LangGraph worker output&lt;/a&gt; wires it into those two frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets stored when I send output to a judge API?
&lt;/h2&gt;

&lt;p&gt;By default, nothing. According to the &lt;a href="https://screwtape.ai/privacy" rel="noopener noreferrer"&gt;privacy page&lt;/a&gt;, judged content stays in memory for the life of the request and isn't written to the database or to logs. Each request logs four fields: request id, endpoint, status and latency.&lt;/p&gt;

&lt;p&gt;If you send &lt;code&gt;"retain": true&lt;/code&gt; with an API key, that request and its result are stored for 24 hours. You can list them with &lt;code&gt;GET /v1/retention&lt;/code&gt; and delete one with &lt;code&gt;DELETE /v1/retention/{id}&lt;/code&gt;. Calls without an API key can't turn retention on. If a model key is configured, the content you send is forwarded to the model provider for that request.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is LLM-as-judge the same as evaluation?&lt;/strong&gt;&lt;br&gt;
It's one way to do evaluation. Offline evals score a dataset after the fact. A judge API is usually called inline, on each output, so the result can change what your agent does next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need evidence to use &lt;code&gt;/v1/judge&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
No. Only &lt;code&gt;task&lt;/code&gt; and &lt;code&gt;agent_output&lt;/code&gt; are required. Without evidence, though, there's nothing to check grounding against, so include it whenever your agent used retrieved sources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does it cost?&lt;/strong&gt;&lt;br&gt;
Free keys include 100 calls per UTC day, with up to three active keys per email and no card needed. Prepaid packs: Trial, $5 for 1,000 calls (one per customer); Starter, $40 for 10,000 calls; and Builder, $175 for 50,000 calls. Team is $199/mo and Finance Control is $399/mo. Card payment through PayPal is live. See &lt;a href="https://screwtape.ai/pricing/" rel="noopener noreferrer"&gt;screwtape.ai/pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use it from an MCP client?&lt;/strong&gt;&lt;br&gt;
Yes. The same judge is exposed as an MCP tool at &lt;code&gt;https://screwtape.ai/mcp&lt;/code&gt;. See &lt;a href="https://screwtape.ai/blog/mcp-fact-checking-tools/" rel="noopener noreferrer"&gt;MCP tools for fact-checking agents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the deterministic engine an LLM?&lt;/strong&gt;&lt;br&gt;
No. It does string-matching checks and says so in its reasons. Check the &lt;code&gt;engine&lt;/code&gt; field if you need to know which one ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Get a free API key (100 calls per UTC day, no card needed): &lt;strong&gt;&lt;a href="https://screwtape.ai/signup" rel="noopener noreferrer"&gt;screwtape.ai/signup&lt;/a&gt;&lt;/strong&gt;. Then run the script above with &lt;code&gt;SCREWTAPE_API_KEY&lt;/code&gt; set. The full contract is in the &lt;a href="https://screwtape.ai/docs" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was created with the help of AI by the Screwtape.ai team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>aiagents</category>
      <category>api</category>
      <category>python</category>
    </item>
  </channel>
</rss>
