⚙️ For developers

Eval inside
your product.

One script tag. One element. One callback. The EvalQA form renders inside an iframe with full postMessage round-trips - your DOM, cookies, and CSP stay yours.

📦 ~5 KB minified ⚡ Zero deps 🔒 iframe-isolated
your-product / review.html
<!-- 1. Load -->
<script src="https://eval.qa/embed.js"></script>

<!-- 2. Target -->
<div id="eval-here"></div>

<!-- 3. Render -->
<script>
EvalQA.embed({
  container: "#eval-here",
  template:  "saas",
  taskId:    "draft-9012",
  prompt:    user.lastMessage,
  raterId:   currentUser.email,
  onSave: ({ eval_id }) => {
    fetch("/api/drafts/9012/eval", {
      method: "POST",
      body:   JSON.stringify({ eval_id })
    });
  }
});
</script>
~5 KB
minified, gzip ~2 KB
0
npm dependencies
6
market templates
MIT
open source
Step-by-step

Pick your stack. Copy. Paste.

The SDK is framework-agnostic - every snippet below produces the same DOM. Pick whichever feels native.

index.html
<script src="https://eval.qa/embed.js"></script>
<div id="eval-here"></div>
<script>
  EvalQA.embed({
    container: "#eval-here",
    template:  "foundation",
    taskId:    "msg-9012",
    prompt:    "What is photosynthesis?",
    onSave: ({ eval_id }) => console.log("saved", eval_id)
  });
</script>
EvalPane.jsx
import { useEffect, useRef } from "react";

// once: <script src="https://eval.qa/embed.js" /> in your <head>
export default function EvalPane({ taskId, prompt, raterId, onSave }) {
  const ref = useRef();
  useEffect(() => {
    const ev = window.EvalQA.embed({
      container: ref.current,
      template:  "saas",
      taskId, prompt, raterId, onSave
    });
    return () => ev.destroy();
  }, [taskId]);
  return <div ref={ref} />;
}
EvalPane.vue
<template>
  <div ref="box"></div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
const props = defineProps(["taskId","prompt","raterId"]);
const emit = defineEmits(["saved"]);
const box = ref(); let handle;
onMounted(() => {
  handle = window.EvalQA.embed({
    container: box.value,
    template:  "saas",
    taskId:    props.taskId,
    prompt:    props.prompt,
    raterId:   props.raterId,
    onSave:    (p) => emit("saved", p)
  });
});
onBeforeUnmount(() => handle?.destroy());
</script>
app/EvalPane.tsx
"use client";
import { useEffect, useRef } from "react";
import Script from "next/script";

export default function EvalPane({ taskId, prompt }: { taskId: string; prompt: string }) {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    if (!(window as any).EvalQA) return;
    const ev = (window as any).EvalQA.embed({
      container: ref.current,
      template:  "saas",
      taskId, prompt
    });
    return () => ev.destroy();
  }, [taskId]);
  return <><Script src="https://eval.qa/embed.js" strategy="afterInteractive" /><div ref={ref} /></>;
}
node / python - server side, no UI
// Node - LLM judge writes directly to the API
const verdict = await myJudge.grade(prompt, response);
const res = await fetch("https://eval.qa/api/save_eval.php", {
  method:  "POST",
  headers: { "Content-Type": "application/json",
             "X-Rater-Type": "llm_judge",
             "X-Rater-Model": "claude-sonnet-4-6" },
  body: JSON.stringify({
    schema_version: "1.0.0",
    rater:   { type: "llm_judge", model: "claude-sonnet-4-6", n_samples: 5 },
    subject: { system_under_test: "Acme Copilot", modality_tags: ["chat"] },
    task:    { task_id: "ticket-9012", modality: "chat", prompt, reference: gold },
    universal: {
      overall_quality:       { score: verdict.score, rationale: verdict.reason },
      instruction_following: { score: 4 },
      faithfulness:          { score: 5 },
      helpfulness:           { score: 5 },
      safety_overall:        { score: 5 }
    }
  })
});
const { eval_id } = await res.json();
Recipes

Patterns we see most.

Production integrations from design partners. Each is < 20 lines of code.

A · Save eval_id back to your DB

The reviewer submits → onSave fires → you persist the link between your draft and our eval record.

EvalQA.embed({
  container: "#review", template: "saas",
  taskId: "draft-"+id, prompt: msg,
  onSave: ({ eval_id }) =>
    fetch("/api/drafts/"+id+"/eval",{
      method:"POST", body: JSON.stringify({ eval_id })
    })
});

B · Block CI on a safety violation

Server-side LLM judge writes the eval; CI bails if any AILuminate hazard was flagged.

const r = await EvalQA.postEval(payload);
if (r.payload?.safety?.is_violating_any) {
  process.exit(1);
}

C · Hybrid review queue

LLM judge fills overnight. A human opens the review URL the next morning.

EvalQA.embed({
  container: "#review",
  evalId:    pendingEvalId,
  mode:      "review",
  raterId:   user.email
});

D · End-user thumbs in chat

Tiny iframe alongside each AI response. Stored with rater type end_user.

EvalQA.embed({
  container: "#fb-"+messageId,
  template:  "enduser",
  taskId:    "msg-"+messageId,
  prompt:    msg.userText,
  height:    320
});
API surface

What you can pass.

Full reference is on the integrate page. The essentials:

OptionTypeNotes
containerstring / ElementCSS selector or DOM node. Required.
templatestringfoundation · agent · rag · robotics · saas · enduser · universal
taskIdstringStable identifier. Multiple raters → same task_id.
promptstringThe AI's input. Pre-fills task.prompt.
referencestringGold answer. Strongly recommended for LLM-judge runs.
raterIdstringEval Army rater email. Pre-loads the profile.
evalId + mode:"review"stringHybrid mode - load existing eval for human confirm/override.
onSavefunction({eval_id, payload}) => void
heightnumberInitial min-height. Auto-resizes as content grows.
baseUrlstringOverride for self-host deployments.

postMessage protocol

MessageDirectionPayload
eval:savediframe → parent{ eval_id, payload }
eval:resizeiframe → parent{ height } · auto-handled
eval:closeiframe → parentfired on destroy()
FAQ

Dev questions.

What's the SDK size?

~5 KB minified, ~2 KB gzipped. Zero npm dependencies. Lives at https://eval.qa/embed.js with aggressive edge caching.

Cross-origin safe?

Yes. The form runs inside an iframe - your DOM, cookies, and CSP stay isolated. Communication is via window.postMessage with an explicit source === "evalqa" check. The SDK does the check for you.

CSP requirements?

Add https://eval.qa to script-src for the SDK and frame-src for the iframe. The SDK does not use unsafe-inline or unsafe-eval. Only the direct-API path (postEval) needs connect-src https://eval.qa.

Can the LLM judge write without the UI?

Yes - use EvalQA.postEval(payload) or POST directly to /api/save_eval.php. Schema is at /eval-form.schema.json. No iframe required for server-side flows.

Browser compat?

Modern evergreen browsers (Chrome, Safari, Firefox, Edge). The SDK uses URLSearchParams, fetch, and ResizeObserver - all baseline 2020+.

Can I self-host the API?

Yes. The /api/*.php files, /embed.js, and /eval-form.schema.json are PHP/JS and open-source. Drop into your host, then call EvalQA.embed({ baseUrl: "https://your-host/" }).

You can ship this in a sprint.

Three lines of code, one PR. Then iterate. Full docs and live demo on the integrate page.

Open full docs See it live