Docs

Grade a message

messages.optimize with mode grade grades one draft with the independent judge on five rubric lines and writes nothing. It returns a 1 to 5 score with passed and total beside it, a verdict and reason for each line, and what the grade can and cannot tell you.

messages.optimize with mode: "grade" grades one draft and writes nothing. It is the same call as writing a message, with one more field. The draft goes in message, and the grade comes back on the same call.

The grade measures the message against your evidence and this rubric; it does not predict replies.

OperationRESTMCPScope
Grade a messagemessages.optimizePOST /messages/optimizeoptimize_message with mode: "grade"messages:execute

It needs the editor role or above. To grade a reusable prompt, by the emails it writes, see Improve a prompt.

Who grades

A grade comes from Amdahl's final judge (Claude Sonnet 5.5 with thinking off). This is the independent judge that also re-grades a draft and its rewrite for lift. It is never the judge inside the rewrite loop that picks which version comes back. The result names the judge in grade.judge.

The five lines

The grade is five pass or fail lines:

LineWhat it asks
relevant_positioningIs the offer positioned for this customer's situation, rather than as a generic pitch?
groundingAre the message's claims backed by what your customers said?
verified_specificsAre its specific details ones your customers stated?
differentiationDoes it make a distinct, evidence-backed point rather than boilerplate?
cta_clarityIs the ask clear?

The first four read your customers' own words. They are graded only when the call sends evidence: "workspace" and the workspace has synced conversations. Otherwise those four lines abstain, and only cta_clarity is graded. A line that abstains is not a fail: see Unknown is never zero.

Request

json
{
  "mode": "grade",
  "message": "Hi Jordan, saw Corvid shipped SSO last month. Teams that add SSO usually find provisioning is the next manual job. We automate it; Helix cut provisioning tickets by 70%. Worth a 15-minute call next week?",
  "channel": "email",
  "evidence": "workspace",
  "context": {
    "facts": ["Helix, a 300-person health tech company, cut provisioning tickets by 70% with us."]
  }
}
FieldRequiredNotes
modeyes, for a gradegrade. The default, improve, writes a message.
messageyesThe draft to grade, verbatim, up to 10,000 characters. A grade without a draft is refused.
evidencenoworkspace grades the four evidence lines against your synced conversations. off (the default) grades cta_clarity alone.
contextnoYour facts count as evidence you supplied: a fact backs only what it states. rules and the constraints are read as they are on a write.
channelnoemail or linkedin.
recipient, recipient_notes, message_typenoBeta, as on a write. See Recipient fields.

Two fields that only make sense for a write are refused with 400 invalid_input, so a result never reads as though they applied: include_tries: true (a grade writes no versions) and on_missing_input: "ask" (a grade asks no questions).

Over MCP, call optimize_message with the same fields. Neither console.amdahl.ai/try nor the batch lane takes mode.

Response

json
{
  "data": {
    "ok": true,
    "outcome": "graded",
    "rewrite": null,
    "optimize_id": "opt_7f3c2a",
    "run_id": "ad5a34f6-...",
    "summary": "Graded against your workspace's conversations.",
    "lift": null,
    "evidence": { "requested": "workspace", "used": true, "reason": null },
    "unsupported_claims": [],
    "grade": {
      "score": 4.2,
      "passed": 4,
      "total": 5,
      "bar": 4.2,
      "passes_bar": true,
      "judge": { "model": "claude-sonnet-5-5", "judge_version": "v7", "role": "final" },
      "lines": [
        { "id": "relevant_positioning", "pass": true, "applicable": true, "reason": "..." },
        { "id": "grounding", "pass": true, "applicable": true, "reason": "..." },
        { "id": "verified_specifics", "pass": true, "applicable": true, "reason": "..." },
        { "id": "differentiation", "pass": false, "applicable": true, "reason": "..." },
        { "id": "cta_clarity", "pass": true, "applicable": true, "reason": "..." }
      ],
      "not_applicable_reason": null
    },
    "disclosures": {
      "single_run": true,
      "not_reply_prediction": true,
      "repeat_spread": {
        "model": "claude-sonnet-5-5",
        "moved_past": 0.2,
        "share": 0.15,
        "measured": "..."
      }
    }
  }
}

A grade has no message key and no unchanged key: the draft is yours, and nothing was written. Read outcome first. graded is the only outcome a grade has.

FieldNotes
outcomegraded.
grade.score1 to 5, or null when no line could be graded. It is 1 + 4 × passed / total, so it is never 0.
grade.passed, grade.totalThe lines that passed, out of the lines that were graded. They ride beside the score, because the score is that fraction.
grade.bar, grade.passes_barThe bar is 4.2. passes_bar is null when score is null.
grade.lines[]One entry per line: id, pass (true, false, or null when the line abstained), applicable, reason, and not_applicable_reason on a line that abstained.
grade.judgeThe judge's model, judge_version and role (final).
grade.not_applicable_reasonWhy lines or the whole grade abstained, or null.
disclosuressingle_run: true, not_reply_prediction: true, and repeat_spread. See below.
optimize_idThe handle of this grade.
evidence, unsupported_claims, cited_evidence, fit_for_audience, notesAs on a write: see Response.
liftAlways null: a grade has no before and after.

Unknown is never zero

A line that could not be graded has pass: null and applicable: false, with a reason: for example corpus_empty (no synced conversations) or evidence_read_failed (Amdahl could not read your conversations, which is our outage, not a fault in the message). Such a line is out of both passed and total. When no line could be graded, score and passes_bar are null, never 0.

What one grade can tell you

  • It is one reading. disclosures.single_run is always true. A judge does not give the same score every time. disclosures.repeat_spread says how far this judge's score moved on a repeat in Amdahl's measurement: share is the fraction of texts whose score moved by more than moved_past (0.2). For Sonnet 5.5 with thinking off that was about 15% of texts. It is not a confidence interval. To compare two drafts, grade both and read the line verdicts, not only the scores.
  • It does not predict replies. disclosures.not_reply_prediction is always true. When Amdahl tested judges on real first-touch emails, one that got a reply and one that did not, from the same sender, every judge scored the replied email higher about half the time. A high grade means the message meets this rubric against your evidence. It does not mean the message is more likely to get a reply.
  • Length is not graded. No line rewards a shorter or a longer message.
  • A grade written for one recipient fits that recipient. Your facts and a recipient package are evidence for that call only.

Grading the same text again

A grade is stored by its request: the same text with the same fields returns the same grading, and does not grade again. To measure how far the judge moves on a repeat, send grade_nonce (only with mode: "grade"; 1 to 64 characters of letters, digits, _, ., : and -). A request with a different grade_nonce is a different request, so the same text is graded fresh. No judge sees the nonce. The same nonce again returns the stored grading. Each fresh grading is billed as a grade.

Billing and limits

A grade that returns ok: true is billed as a grade, on its own meter (message_grade_run), separate from a write. With an API key or an external OAuth token it counts toward the same monthly cap as an optimize (1,000 per workspace by default). A grade that fails is not billed. The in-flight limits are the same as for a write.

Record what happened

To say what the sender did with a message, use record feedback on a run.

Grades before 22 October 2026

Until 22 October 2026, grades came from evals (evals.run, the MCP run_eval tool). Evals retired that day. Their scores were on a different scale and from a different judge, so they do not compare with a grade from this call. Stored eval runs stay readable through the MCP get_eval_run tool. The REST eval routes answer 410 gone. See the changelog.