Benchmarks is in preview and rolling out to organizations
gradually. The endpoints and tools on this page are available once Benchmarks is
enabled for your organization; until then they answer
403 not_entitled.What a case is
A case is one input plus its expected outcome. Every case has acaseType:
A
turn case does not own a prompt. It points at an agent version
(prompt.agentVersionId), so the same case can be replayed against several
prompt revisions and models.
Authentication
Use an organization API key as a bearer token. The same key works for REST and MCP. Creating and editing cases is a write: the key must belong to an organization owner or admin, otherwise the request is refused with403.
Create cases
POST /v1/benchmarks/{id}/cases creates up to 100 cases per request and
returns them in data.
cases.json
Response (201)
Idempotency-Key header on batch creation so a retried request returns
the cases it created the first time instead of creating duplicates.
Case fields
A
turn case must check at least one thing: a reply expectation other than
none, or a tool expectation.
Tool-call expectations
toolExpectation checks whether the step called the right tool and whether
the call has the right form. Pick one of three shapes:
This case expects a payment plan to be created with three installments, in the
customer’s currency, and never a refund:
noExtraArguments: true to fail
calls that send any other argument. When a step both speaks and calls tools,
replyAt (with ordered: true) pins where the reply sits among the calls:
0 means before every call. Every listed tool must exist in the case’s
prompt.
Argument matchers
Separately from matchers, the arguments of every call must validate against the
tool’s JSON schema:
"amount": "240" fails a { "type": "number" } argument.
Skip or tune a judge for one case
Judges are defined on the benchmark. Get their ids fromGET /v1/benchmarks/{id} (validators[]); the API calls judges
“validators”.
skippedJudgeIdsopts this case out of judges that don’t make sense for it, such as a tone rubric on a tool-only step. Required judges can’t be skipped, and at least one judge must still run.judgeParamOverrideschanges a judge’s parameters for this case only. A judge labelled “Reply under words” with a default of 60 can be tightened to 10 for one case:
min / max. An override for a
judge you also skip is dropped.
Conversation cases
Audio cases (STT)
For an STT benchmark, each case is{ audio, referenceTranscript, keyTerms?, tags? }.
Audio never goes through the request body except for short clips:
- Stage the file with
POST /v1/recording-uploads, thenPUTthe bytes to the returneduploadUrl. - Create the case with
"audio": { "uploadId": "<id>" }.
"audio": { "url": "https://..." } (fetched by Zelto; the case
is preparing until the download finishes) or, for clips up to 3 MB,
"audio": { "base64": "...", "contentType": "audio/wav" }.
Add cases from production calls
POST /v1/benchmarks/{id}/cases/from-conversations turns up to 100
conversations into cases in one request, using the same
mapping as From production calls in the dashboard. Each case records the
call’s agent version, the variable values captured from the call, the active
node (for node-based agents), and the transcript before the evaluated turn.
Captured variables are masked in the response. Cases whose node couldn’t be
resolved come back as
draft. Use PATCH /v1/benchmarks/{id}/cases/{caseId} to
refine any case afterwards: change its expected outcome, add a tool expectation,
override a captured variable with variableOverrides, or set "status": "ready"
to accept a draft.
Add cases from traces
POST /v1/benchmarks/{id}/cases/from-traces creates up to 100 cases from
traces, the same as Add to benchmark on a trace or span.
- With a
spanId(an LLM or tool span), the case tests that one LLM call: the span’s own input messages become the history, and its output becomes the expected step. - Without a
spanId, the trace’s linked conversation is used, likefrom-conversationswithturn: "last_assistant".
turnSource: span_messages when the history came from
the span, or transcript when it came from the linked call. A trace with no linked
call needs a span that recorded its messages, otherwise that item fails with
422 no_turn_source.
Via MCP
The Zelto MCP server exposes the same operations as tools, so you can ask Claude, Cursor, or any MCP client to build a benchmark for you:
An example prompt:
list_benchmarks, list_conversations,
add_benchmark_cases_from_conversations, then update_benchmark_case for the
tool expectations.
Where new cases land
New and edited cases go to the benchmark’s draft, the working copy shown as “Draft changes” in the dashboard. Runs use the latest published version by default, so new cases are part of a run only after you publish:draft status and warns you about them.
Owners and admins can also run the draft directly; that run is labelled “draft run”.
Cases are never deleted: editing one creates a new revision, and retiring
one removes it from the next version while older runs keep their results.
Errors
Errors are JSON:{ "error": "<code>", "details": ... }.
Over MCP the same refusals come back as tool results with
isError: true; see
Error results.
Related
- Benchmarks: cases, judges, and versions.
- Agent versions: what
prompt.agentVersionIdpoints at. - MCP: connect a client and authenticate.

