---
title: "Run Eval"
method: POST
path: "/api/platform/v1/evals/run"
tags: ["observability"]
---

# Run Eval

`POST /api/platform/v1/evals/run`

## Request

- Base URL: `https://docs.amdahl.ai`
- URL: `https://docs.amdahl.ai/api/platform/v1/evals/run`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Grade content against your workspace data and get a run id to poll. Use when scoring a prompt or a drafted message: pass inputs (prompt and/or message, optional audience, account, mode=rewrite|advisory|gate) and context.facts, what you know first-hand about the recipient. A blinded judge scores relevant positioning, grounding, verified specifics, differentiation and CTA clarity against real customer quotes. Returns eval-run://<id> plus a console_url to give the user.

## Request body

- object
  - `eval` string: Eval slug or id. Default 'prompt-and-message-eval'. Discover options + their input fields via eval://list.
  - `inputs` object: Run inputs for the eval's declared fields. prompt-and-message-eval requires at least one of prompt / message, and also accepts audience (a seniority or title to scope the cohort), account (the company it is going to — retrieves what THAT account said), and mode: rewrite (the default, a full improved prompt and message), advisory (anchored suggestions against what you wrote), or gate (grade ONLY what you sent and stop — read the verdict from eval-run://<id>/gate). Read the exact schema from eval://<slug>.
  - `reuse` 'cached' | 'force': cached (default) reuses an active run for the same target (fingerprint); force always starts a fresh run.
  - `evidence_from_run` string: Run id whose frozen customer quotes this run should be graded against, instead of retrieving fresh ones. Use to A/B two drafts: without it each run retrieves its own evidence, so the score difference mixes your edit with a different set of quotes. The source run must be in this workspace and have reached retrieval; the report states the pin and how old that evidence is.
  - `include_external` boolean: Add public web/market evidence to the grade (default false). One web fan-out runs and its snippets join the pool tagged EXTERNAL — the tier that backs market claims ("analysts report"), never customer voice. Needs the external_search:execute scope (refused without it, never silently dropped) and cannot combine with a pinned run — a pin holds the evidence frozen, and its pool already carries any external quotes it was graded on.
  - `external_cap` integer: How many external quotes to retrieve when include_external is set. Clamped to 1..25; default 15. The ack echoes the clamped value as external.cap.
  - `generate_model` string: STAGING ONLY, ignored in production: the Anthropic model id the writer (generate stage) uses on this run, for a writer bake-off without a redeploy. The judge never moves. Not part of the content address, so pass reuse=force or a cached run of another arm returns.
  - `generate_thinking` 'off' | 'low' | 'medium' | 'high' | 'default': STAGING ONLY, ignored in production: how deeply the writer thinks on this run. Omitted uses the shipped depth (medium); default sends no thinking field. Same reuse=force caveat as generate_model.
  - `candidate_from_run` string: Run id whose improved version this run should GRADE, instead of writing a new one. Use to re-run the judge over fixed text — the run makes no generate call, so a score difference between two such runs is the judge, not the writer. Pass reuse=force when sampling: two identical pinned requests share one content address, so on the default reuse=cached the second is served the first run and an N-sample spread reads as zero. Also pins that run's quotes (the citations in the frozen text address them), so pass the same id to evidence_from_run or omit it. Refused if this run submits different copy or a different mode than the pinned candidate was written for.
  - `scope` object: Narrow WHICH conversations the grade speaks for. Use when the verdict is about a cohort rather than your whole corpus. filters are AND-joined over interactions (utterance grain), deals or deal_qualification (account grain - these resolve to the matching accounts and scope the conversations to them). A slice below the evidence floors (3 speakers, 25 utterances, 2 companies) abstains to whole-corpus grading unless allow_thin_evidence, which grades it anyway and flags below_floors.
    - `filters` object[]: Typed predicates, AND-joined. At most 25. Read the filterable fields and their operators per surface from the search field catalog.
      - `surface` 'interactions' | 'deals' | 'deal_qualification'
      - `field` string, required
      - `op` 'eq' | 'neq' | 'in' | 'not_in' | 'contains' | 'gt' | 'gte' | 'lt' | 'lte' | 'between' | 'is_null' | 'not_null', required
      - `value` unknown
    - `audience` 'all' | 'customer_voice': customer_voice restricts the slice to buyer speech; all (default) leaves it unrestricted.
    - `allow_thin_evidence` boolean: Grade the slice even when it falls below the evidence floors, instead of abstaining to the whole corpus. The report carries the real counts and below_floors: true.
  - `context` object: Optional. What you know that the corpus cannot: facts, the CALLER evidence tier. A claim a fact states is not graded as fabricated; a claim that inflates one still is; a fact is never counted as a customer quote. Only facts is accepted here (rules and voice_examples belong to messages.optimize); any other key is refused. Graded by evals with an improvement_loop grader (prompt-and-message-eval); refused on any other.
    - `facts` string[]: What you know first-hand about the recipient or situation that no call recording holds, one fact per entry, such as "Met Priya at the GTM dinner on Sep 24; she said her SDRs rewrite every AI draft". Graded as caller-supplied evidence (tier CALLER, not a customer quote): a claim a fact states is not marked fabricated, a claim that inflates one still is. Up to 10, each up to 1000 characters once trimmed.
  - `recipient` object: Beta, per workspace: refused unless the workspace has recipient evidence on. Who the message is for: any of email (up to 320 characters), name and company (up to 200 each), domain (up to 253), linkedin_url (up to 300) and attributes. attributes is a flat map of text, number, true/false or null values, such as a whole Clay or CSV row: up to 100 keys of 100 characters, each text value up to 1000 characters. Any other key is refused, and so is an attribute key that names a business, user, tenant or workspace id.
    - `email` string
    - `name` string
    - `company` string
    - `domain` string
    - `linkedin_url` string
    - `attributes` object
  - `recipient_notes` string: Beta, per workspace: refused unless the workspace has recipient evidence on. Free text about the recipient, such as your own research or a pasted CRM note. Up to 10000 characters.
  - `message_type` string: Beta, per workspace: ignored unless the workspace has recipient evidence on. The kind of message: cold, re-engage, follow-up, deal. follow-up is a reply in a thread that is in progress.

## Response `200`

OK

- object
  - `data` object, required
    - `success` boolean, required
    - `run_id` string
    - `reused` boolean
    - `status` string
    - `eval_slug` string
    - `eval_version` string
    - `resource` string
    - `poll` object: How to read the verdict back: tool (get_eval_run, the MCP tool; action status is the same read under its old MCP name), run_id, resource, report_resource, rest (method, url, report_url: the same two reads as REST paths, url already carrying wait_ms), and wait_ms_max - the largest long-poll budget the read honours, so a caller blocks once instead of looping. The run is queued when this returns; it is not the answer yet.
    - `console_url` string: Link to this run's report in the workspace console. Give it to the user verbatim so they can open the run — run_id and resource are machine addresses they cannot follow. Works immediately (it shows the live grading trail, then the report). Absent when it could not be resolved; never invent one.
    - `recipient_received` object: Beta, per workspace. Present only when the request carried a recipient field: fields (the request paths received), attribute_count, notes_chars and message_type. Names and counts only, never a value.
    - `evidence_run_id` string: Beta, per workspace. Present only when the request carried a recipient field: this run's recipient-evidence id (a UUID), the key of its lookup log. A reused run gives the id it stored, or none. run_id stays the eval run id.
    - `ignored_params` string[]: Present only when the request sent message_type to a workspace without recipient evidence: the params ignored. The run is the one the request would get without them.
    - `setup_needed` object: Present only when this workspace has no connected data source, so there is nothing to read. reason is no_sources_connected; message says so in plain words for the user; connect_url is the Connections page in the console. Give the user the message and the link. Absent when a source is connected.
      - `reason` 'no_sources_connected', required
      - `message` string, required
      - `connect_url` string, required
    - `error` object

## Other responses

- `400`: Invalid input - failed schema validation
- `401`: Authentication required
- `403`: Forbidden - missing scope or role
- `500`: Internal server error

---

[API](https://skmtc.dev/amdahl/apis/amdahl-platform-api.md) · [All operations](https://skmtc.dev/amdahl/apis/amdahl-platform-api/llms.txt) · [OpenAPI document](https://skmtc.dev/amdahl/apis/amdahl-platform-api/revisions/d4eee33e2472?raw)
