Evals
Test an agent against real situations, including ones it has already been in.
An eval suite is a set of scenarios and how they are scored. A run places a real call per scenario against the agent, with a tester on the other end improvising towards a goal, and scores the transcript against the suite's criteria. A scripted caller only ever tests the path someone already thought of, which is why a scenario is a goal and a persona rather than a script.
The scenarios worth having come from production. Pick a source, look at real completed calls with the sentiment and resolution the analysis pipeline already found, import the interesting ones, and let triage work out what scenario each would make. Triage is batched and resumable because it is a model call per transcript.
Runs are asynchronous: starting one returns a run at `pending`. Poll it, or subscribe to `eval.run.completed` and be told.
List suites
Eval suites for an agent.
/api/v1/agents/{agentId}/evalsPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
Responses
{"data": {"evals": [{"id": "9d02…","name": "Pharmacy follow-up","status": "ready","iterationsPerScenario": 3,"scoringCriteria": [{"key": "accuracy","name": "Accuracy","weight": 0.4}],"runCount": 12,"latestRunScore": 8.1}]},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals" \-H "Authorization: Bearer YOUR_API_KEY"
Create a suite
A suite starts empty; add scenarios by hand or import them from real calls.
/api/v1/agents/{agentId}/evalsPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
Responses
{"data": {"id": "9d02…","name": "Pharmacy follow-up","status": "draft"},"meta": {"requestId": "req_…"}}
curl -X POST "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"name": "Pharmacy follow-up","iterationsPerScenario": 3}'
Get a suite
One suite, including its scoring criteria and its latest score.
/api/v1/agents/{agentId}/evals/{evalId}Path parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"id": "9d02…","name": "Pharmacy follow-up","status": "ready","latestRunScore": 8.1},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}" \-H "Authorization: Bearer YOUR_API_KEY"
Update a suite
Change a suite's settings. `noise` puts the tester's line under background sound; pass null to switch it off.
/api/v1/agents/{agentId}/evals/{evalId}Path parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"id": "9d02…","status": "ready"},"meta": {"requestId": "req_…"}}
curl -X PATCH "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"status": "ready","noise": {"type": "office","level": "moderate"}}'
Delete a suite
Remove a suite and everything under it.
/api/v1/agents/{agentId}/evals/{evalId}Path parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"deleted": true},"meta": {"requestId": "req_…"}}
curl -X DELETE "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}" \-H "Authorization: Bearer YOUR_API_KEY"
Add a scenario
A situation to put the agent in, expressed as a goal the tester pursues.
/api/v1/agents/{agentId}/evals/{evalId}/scenariosPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"id": "31fa…","name": "Prescription not ready"},"meta": {"requestId": "req_…"}}
curl -X POST "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/scenarios" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"name": "Prescription not ready","persona": "A busy pharmacy technician","goal": "Say the prescription is not ready and refuse to give a date.","category": "edge_case"}'
Import sources
Campaigns and call centers whose real calls can become scenarios.
/api/v1/agents/{agentId}/evals/{evalId}/imports/sourcesPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"outboundCampaigns": [{"id": "a1…","name": "September refill reminders","callCount": 812}],"inboundCampaigns": [],"callCenters": [{"id": "cc1…","name": "Member services","callCount": 4302}]},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/imports/sources" \-H "Authorization: Bearer YOUR_API_KEY"
Candidate calls
Completed calls from one source that could become scenarios, with the sentiment, intent and resolution already found — so you can take the unresolved and the unhappy rather than a random sample. Calls already imported are excluded.
/api/v1/agents/{agentId}/evals/{evalId}/imports/candidatesPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
sourceTyperequired | string | campaign or call_center. |
sourceIdrequired | string | The source's id. |
from | string | ISO date lower bound. |
to | string | ISO date upper bound. |
minDuration | number | Seconds. |
direction | string | inbound or outbound. |
search | string | Free text. |
page | number | Page number. Default: |
pageSize | number | Up to 100. Default: |
Responses
{"data": {"calls": [{"id": "5f1c…","direction": "outbound","durationSeconds": 184,"sentiment": "negative","callerIntent": "prescription_status","resolved": false}],"total": 41},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/imports/candidates" \-H "Authorization: Bearer YOUR_API_KEY"
List scenarios
Every scenario in a suite, written or imported.
/api/v1/agents/{agentId}/evals/{evalId}/scenariosPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"scenarios": [{"id": "31fa…","name": "Prescription not ready","goal": "Say the prescription is not ready and refuse to give a date.","category": "edge_case","isEnabled": true,"isAutoGenerated": false}]},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/scenarios" \-H "Authorization: Bearer YOUR_API_KEY"
List imports
Batches of real calls pulled into this suite, and how far triage has got.
/api/v1/agents/{agentId}/evals/{evalId}/importsPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"imports": [{"id": "77c0…","source": "production","label": "September refill reminders","status": "triaged","totalCalls": 18}]},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/imports" \-H "Authorization: Bearer YOUR_API_KEY"
Import calls
Record which calls to read. Ids that do not belong to the source are rejected rather than imported.
/api/v1/agents/{agentId}/evals/{evalId}/importsPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"id": "77c0…","status": "pending","totalCalls": 2,"triagedCalls": 0},"meta": {"requestId": "req_…"}}
curl -X POST "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/imports" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"sourceType": "campaign","sourceId": "a1…","callIds": ["5f1c…","6a2d…"]}'
Triage a batch
Read the next batch of imported calls and work out what scenario each would make. Call it until `remaining` is zero; each batch commits, so a retry resumes rather than restarts.
/api/v1/agents/{agentId}/evals/{evalId}/imports/{importId}/triagePath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
importIdrequired | string | Import id. |
Responses
{"data": {"import": {"id": "77c0…","triagedCalls": 6},"processed": 6,"remaining": 12},"meta": {"requestId": "req_…"}}
curl -X POST "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/imports/{importId}/triage" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"batchSize": 6}'
Review triaged calls
What triage found for each imported call — the summary, the category, and the scenario it suggests — so it can be reviewed before anything is created.
/api/v1/agents/{agentId}/evals/{evalId}/imports/{importId}/scenariosPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
importIdrequired | string | Import id. |
Responses
{"data": {"calls": [{"id": "b1…","triageStatus": "done","triage": {"category": "edge_case","summary": "Pharmacy would not commit to a date.","suggestedScenario": {"name": "Prescription not ready"}},"scenarioId": null}]},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/imports/{importId}/scenarios" \-H "Authorization: Bearer YOUR_API_KEY"
Create scenarios from calls
Turn triaged calls into scenarios. A call already turned into one is skipped rather than duplicated. GET the same path first to review what triage found.
/api/v1/agents/{agentId}/evals/{evalId}/imports/{importId}/scenariosPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
importIdrequired | string | Import id. |
Responses
{"data": {"created": 2,"scenarioIds": ["31fa…","31fb…"]},"meta": {"requestId": "req_…"}}
curl -X POST "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/imports/{importId}/scenarios" \-H "Authorization: Bearer YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"callIds": ["b1…","b2…"]}'
Start a run
Place a call per scenario per iteration. Returns immediately — the run takes minutes.
/api/v1/agents/{agentId}/evals/{evalId}/runsPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"id": "e91b…","runNumber": 13,"status": "pending","totalIterations": 9},"meta": {"requestId": "req_…"}}
curl -X POST "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/runs" \-H "Authorization: Bearer YOUR_API_KEY"
List runs
Every run of a suite, newest first.
/api/v1/agents/{agentId}/evals/{evalId}/runsPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
Responses
{"data": {"runs": [{"id": "e91b…","runNumber": 13,"status": "completed","agentVersion": 8,"overallScore": 8.1}]},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/runs" \-H "Authorization: Bearer YOUR_API_KEY"
Get a run
One run, with the per-criterion breakdown behind its headline score. A score with no breakdown says nothing about what to fix.
/api/v1/agents/{agentId}/evals/{evalId}/runs/{runId}Path parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
runIdrequired | string | Run id. |
Responses
{"data": {"run": {"id": "e91b…","status": "completed","agentVersion": 8,"overallScore": 8.1,"completedIterations": 9,"failedIterations": 0},"aggregates": {"criteria": [{"criterionKey": "accuracy","averageScore": 8.6,"maxScore": 10}]}},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/runs/{runId}" \-H "Authorization: Bearer YOUR_API_KEY"
Delete a run
Remove a run and its results.
/api/v1/agents/{agentId}/evals/{evalId}/runs/{runId}Path parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
runIdrequired | string | Run id. |
Responses
{"data": {"deleted": true},"meta": {"requestId": "req_…"}}
curl -X DELETE "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/runs/{runId}" \-H "Authorization: Bearer YOUR_API_KEY"
Run results
One result per scenario per iteration: the transcript of the call that was placed, what it scored on each criterion, and why. A score nobody can audit is a score nobody should act on.
/api/v1/agents/{agentId}/evals/{evalId}/runs/{runId}/resultsPath parameters
| Parameter | Type | Description |
|---|---|---|
agentIdrequired | string | Agent id. |
evalIdrequired | string | Suite id. |
runIdrequired | string | Run id. |
Responses
{"data": {"results": [{"id": "aa10…","evalScenarioId": "31fa…","iterationNumber": 1,"status": "completed","overallScore": 8.5,"durationSeconds": 96}]},"meta": {"requestId": "req_…"}}
curl -X GET "https://voice.evryhealth.com/api/v1/agents/{agentId}/evals/{evalId}/runs/{runId}/results" \-H "Authorization: Bearer YOUR_API_KEY"