# Create a new agent endpoint
Source: https://docs.roark.ai/api-reference/agent-endpoint/create-a-new-agent-endpoint
/api-reference/openapi.documented.json post /v1/agent/endpoint
Creates a new agent endpoint for the authenticated project.
# Get agent endpoint by ID
Source: https://docs.roark.ai/api-reference/agent-endpoint/get-agent-endpoint-by-id
/api-reference/openapi.documented.json get /v1/agent/endpoint/{endpointId}
Returns a specific agent endpoint by its ID.
# List agent endpoints
Source: https://docs.roark.ai/api-reference/agent-endpoint/list-agent-endpoints
/api-reference/openapi.documented.json get /v1/agent/endpoint
Returns a paginated list of agent endpoints for the authenticated project.
# Update an agent endpoint
Source: https://docs.roark.ai/api-reference/agent-endpoint/update-an-agent-endpoint
/api-reference/openapi.documented.json put /v1/agent/endpoint/{endpointId}
Updates an existing agent endpoint by its ID. Only environment and outboundDialType can be modified.
# Create a new agent
Source: https://docs.roark.ai/api-reference/agent/create-a-new-agent
/api-reference/openapi.documented.json post /v1/agent
Creates a new agent for the authenticated project.
# Delete an agent
Source: https://docs.roark.ai/api-reference/agent/delete-an-agent
/api-reference/openapi.documented.json delete /v1/agent/{agentId}
Soft-deletes an agent by its ID. The agent is hidden from all reads and stops being attributed to new calls, but its record and history are retained. Fails with 409 if the agent is still referenced by simulation run plans: cancel those run plans first. Note: if the agent syncs from a provider integration, also exclude it in the integration settings (or delete the integration) so its calls stop reaching Roark.
# Get agent by ID
Source: https://docs.roark.ai/api-reference/agent/get-agent-by-id
/api-reference/openapi.documented.json get /v1/agent/{agentId}
Returns a specific agent by its ID.
# List agents
Source: https://docs.roark.ai/api-reference/agent/list-agents
/api-reference/openapi.documented.json get /v1/agent
Returns a paginated list of agents for the authenticated project.
# Update an agent
Source: https://docs.roark.ai/api-reference/agent/update-an-agent
/api-reference/openapi.documented.json put /v1/agent/{agentId}
Updates an existing agent by its ID.
# Authorization
Source: https://docs.roark.ai/api-reference/authorization
How to authenticate API requests
### Overview
To securely access Roark's API endpoints, you need to authenticate your requests using your unique API key. This ensures that only authorized users can interact with our services.
Never expose your API key in client-side code, public repositories, or share it with unauthorized users. If you believe your key has been compromised, regenerate it immediately in the [dashboard](/documentation/getting-started/api-keys).
### Authentication Header
```bash Headers theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
Authorization: Bearer YOUR_API_KEY
```
```json Response theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"authenticated": true,
"account": "your-account-name"
}
```
### Example Requests
```bash cURL theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -X GET "https://api.roark.ai/health" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import requests
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get("https://api.roark.ai/health", headers=headers)
```
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const response = await fetch("https://api.roark.ai/health", {
headers: {
"Authorization": `Bearer YOUR_API_KEY`
}
});
```
### Security Best Practices
Store API keys securely in environment variables or a secure key management system
Periodically rotate your API keys to maintain security
### Need an API Key?
Follow our guide to generate your API key and start making authenticated requests
# Append tool invocations to a call
Source: https://docs.roark.ai/api-reference/call/append-tool-invocations-to-a-call
/api-reference/openapi.documented.json post /v1/call/{callId}/tool-invocations
Attach tool invocations that fired during a call to an already-existing call, asynchronously after the call was created. Use this when the tool-call data becomes available later than the call itself (e.g. a Roark simulation, or a call submitted via POST /v1/call before its tools were ready). Writes are idempotent: re-sending an invocation already present on the call (same tool name and timing) is skipped, so retries converge instead of double-counting. Optionally pass `metrics` to (re)score metrics over the newly attached tools, which triggers a billed metric collection job and requires the `metric:create` permission.
# Create a call
Source: https://docs.roark.ai/api-reference/call/create-a-call
/api-reference/openapi.documented.json post /v1/call
Create a new call with recording, transcript, agents, and customers
# Get a call by ID
Source: https://docs.roark.ai/api-reference/call/get-a-call-by-id
/api-reference/openapi.documented.json get /v1/call/{callId}
Retrieve an existing call by its unique identifier
# Get call transcript
Source: https://docs.roark.ai/api-reference/call/get-call-transcript
/api-reference/openapi.documented.json get /v1/call/{callId}/transcript
Fetch the full transcript for a specific call. Optionally specify a transcription source; otherwise the best available source is used automatically.
# List call metrics
Source: https://docs.roark.ai/api-reference/call/list-call-metrics
/api-reference/openapi.documented.json get /v1/call/{callId}/metrics
Fetch all call-level metrics for a specific call, including both system-generated and custom metrics. Only returns rows from the **latest** metric-collection job per metric — if the same metric has been recomputed, prior runs are excluded and remain in the metric history. By default returns only successfully computed metrics; pass `?status=all` to also include rows that resolved as NOT_APPLICABLE / DATA_MISSING / ERROR (the `value` field is omitted on those entries — check `captureStatus`).
# List call sentiment runs
Source: https://docs.roark.ai/api-reference/call/list-call-sentiment-runs
/api-reference/openapi.documented.json get /v1/call/{callId}/sentiment-run
Fetch detailed sentiment analysis results for a specific call, including emotional tone, key phrases, and sentiment scores.
# List calls
Source: https://docs.roark.ai/api-reference/call/list-calls
/api-reference/openapi.documented.json get /v1/call
Returns a paginated list of calls for the authenticated project.
# Create a chat
Source: https://docs.roark.ai/api-reference/chat/create-a-chat
/api-reference/openapi.documented.json post /v1/chat
Create a new chat with segments (messages and tool invocations)
# Get a chat by ID
Source: https://docs.roark.ai/api-reference/chat/get-a-chat-by-id
/api-reference/openapi.documented.json get /v1/chat/{id}
Retrieve an existing chat by its unique identifier
# Get chat transcript
Source: https://docs.roark.ai/api-reference/chat/get-chat-transcript
/api-reference/openapi.documented.json get /v1/chat/{id}/transcript
Fetch the full transcript (messages) for a specific chat.
# List chat metrics
Source: https://docs.roark.ai/api-reference/chat/list-chat-metrics
/api-reference/openapi.documented.json get /v1/chat/{id}/metrics
Fetch all metrics for a specific chat, including both system-generated and custom metrics. Only returns rows from the **latest** metric-collection job per metric — if the same metric has been recomputed, prior runs are excluded and remain in the metric history. By default returns only successfully computed metrics; pass `?status=all` to also include rows that resolved as NOT_APPLICABLE / DATA_MISSING / ERROR (the `value` field is omitted on those entries — check `captureStatus`).
# List chats
Source: https://docs.roark.ai/api-reference/chat/list-chats
/api-reference/openapi.documented.json get /v1/chat
Returns a paginated list of chats for the authenticated project.
# Exchange a CLI authorization code for an API key
Source: https://docs.roark.ai/api-reference/cli-auth/exchange-a-cli-authorization-code-for-an-api-key
/api-reference/openapi.documented.json post /v1/cli/auth/token
Completes the CLI browser-login flow: verifies the single-use authorization code and its PKCE code_verifier, then mints and returns the approved API key exactly once.
# Apply a config bundle
Source: https://docs.roark.ai/api-reference/config/apply-a-config-bundle
/api-reference/openapi.documented.json post /v1/config/apply
Reconcile a config-as-code bundle into the project. Submit the full desired set of resources; resources already managed by config are updated, new ones created, and (unless prune is false) config-managed resources absent from the bundle are deleted. Identity is by name — no ids in the bundle.
# Diff a config bundle
Source: https://docs.roark.ai/api-reference/config/diff-a-config-bundle
/api-reference/openapi.documented.json post /v1/config/diff
Dry run for a config-as-code apply: returns the projected changes (create / update / delete) for the submitted bundle without writing anything. Submit the full desired set of resources; identity is by name — no ids in the bundle. Run this before apply to preview what would change.
# Add an edge case
Source: https://docs.roark.ai/api-reference/customer-flow-edge-case/add-an-edge-case
/api-reference/openapi.documented.json post /v1/customer-flow/{flowId}/edge-case
Adds a variant to an IMPROV flow.
A scripted flow's variants are owned by the path engine, one per path through the graph, so they are
created by editing the graph through PUT /v1/customer-flow/{flowId}/graph rather than here.
Leave personaOverrideId or environmentId unset to inherit the happy path's.
# Promote an edge case to the happy path
Source: https://docs.roark.ai/api-reference/customer-flow-edge-case/promote-an-edge-case-to-the-happy-path
/api-reference/openapi.documented.json post /v1/customer-flow/{flowId}/edge-case/{edgeCaseId}/promote
Makes this edge case the flow's happy path, and the outgoing happy path an edge case. Its persona and environment are baked into it first, so edge cases that were inheriting keep the configuration they had.
# Remove an edge case
Source: https://docs.roark.ai/api-reference/customer-flow-edge-case/remove-an-edge-case
/api-reference/openapi.documented.json delete /v1/customer-flow/{flowId}/edge-case/{edgeCaseId}
Soft-deletes a variant. On a scripted flow the path engine re-creates a variant for any path still in the graph, so remove the path through PUT /graph instead if that is what you meant.
# Update an edge case
Source: https://docs.roark.ai/api-reference/customer-flow-edge-case/update-an-edge-case
/api-reference/openapi.documented.json put /v1/customer-flow/{flowId}/edge-case/{edgeCaseId}
Updates an edge case's title, persona, environment, brief, preceded-by link or expectations. Omitted fields are left alone; `additionalExpectations` replaces the set wholesale rather than appending. Promoting it to the happy path is a separate call, since that also demotes the incumbent.
# Create a customer flow
Source: https://docs.roark.ai/api-reference/customer-flow/create-a-customer-flow
/api-reference/openapi.documented.json post /v1/customer-flow
Creates a customer flow. A SCRIPTED flow carries a step graph and gets one way of running it per path through the graph; an IMPROV flow carries the briefs you send. Customer flows replace the older simulation scenarios, so build a flow for anything new.
# Delete a customer flow
Source: https://docs.roark.ai/api-reference/customer-flow/delete-a-customer-flow
/api-reference/openapi.documented.json delete /v1/customer-flow/{flowId}
Soft-deletes a customer flow along with its edge cases, expectations and (for scripted flows) its step graph. Run plans that linked it drop it from their test cases.
# Get customer flow by ID
Source: https://docs.roark.ai/api-reference/customer-flow/get-customer-flow-by-id
/api-reference/openapi.documented.json get /v1/customer-flow/{flowId}
Returns a customer flow with its happy path, edge cases, expectations and linked agents. Scripted flows also carry their step graph.
# List customer flows
Source: https://docs.roark.ai/api-reference/customer-flow/list-customer-flows
/api-reference/openapi.documented.json get /v1/customer-flow
Returns a paginated list of customer flows with their agents, expectations, happy path and edge cases. The step graph is the one field omitted: reading it walks the project's whole step graph, so it comes back from the single-flow endpoint instead. Customer flows are how a project describes what to test; they replace the older simulation scenarios.
# Replace a scripted flow's steps
Source: https://docs.roark.ai/api-reference/customer-flow/replace-a-scripted-flows-steps
/api-reference/openapi.documented.json put /v1/customer-flow/{flowId}/graph
Replaces a scripted flow's conversation graph with the tree you send. This is a full replace, not a merge:
a step you omit is removed.
Include `nodeId` on a step to update the existing one, omit it to create a new step. Where two branches
rejoin, keep the `mergeIntoNodeIds` references a read gave you. Dropping them un-merges those branches
and is refused unless `allowUnmerge` is set.
A change to the set of paths re-seeds how the flow runs, which the response reports as
`variantsReshaped` along with the resulting happy path and edge cases.
# Update a customer flow
Source: https://docs.roark.ai/api-reference/customer-flow/update-a-customer-flow
/api-reference/openapi.documented.json put /v1/customer-flow/{flowId}
Updates a flow's title, description, branching mode, linked agents or flow-level expectations. The step graph is replaced through PUT /graph.
# Update a flow's happy path
Source: https://docs.roark.ai/api-reference/customer-flow/update-a-flows-happy-path
/api-reference/openapi.documented.json put /v1/customer-flow/{flowId}/happy-path
Updates the happy path's title, persona, environment, brief or expectations. Omitted fields are left
alone; `additionalExpectations` replaces the set wholesale rather than appending.
Its persona and environment are what the edge cases inherit, so changing them here changes every edge
case that does not name its own.
# Get API health status
Source: https://docs.roark.ai/api-reference/health/get-api-health-status
/api-reference/openapi.documented.json get /health
Returns the health status of the API and its dependencies
# Create HTTP request definition
Source: https://docs.roark.ai/api-reference/http-request-definition/create-http-request-definition
/api-reference/openapi.documented.json post /v1/http-request-definition
Creates a new HTTP request definition. The signing secret is only returned in this response and cannot be retrieved later.
# Get HTTP request definition by ID
Source: https://docs.roark.ai/api-reference/http-request-definition/get-http-request-definition-by-id
/api-reference/openapi.documented.json get /v1/http-request-definition/{definitionId}
Returns a specific HTTP request definition by its ID.
# List HTTP request definitions
Source: https://docs.roark.ai/api-reference/http-request-definition/list-http-request-definitions
/api-reference/openapi.documented.json get /v1/http-request-definition
Returns a paginated list of HTTP request definitions for the authenticated project.
# Update HTTP request definition
Source: https://docs.roark.ai/api-reference/http-request-definition/update-http-request-definition
/api-reference/openapi.documented.json put /v1/http-request-definition/{definitionId}
Updates an existing HTTP request definition.
# Introduction
Source: https://docs.roark.ai/api-reference/introduction
Integrating with the Roark API
## Welcome to the Roark API
Our API provides programmatic access to all Roark features — from call monitoring and metrics collection to simulation testing.
Haven't generated an API key yet? [Generate one here](/documentation/getting-started/api-keys)
## Core Endpoints
Upload and analyze call recordings
Define metrics and collect results on calls
Test agents with synthetic callers
## Getting Started
Create your API key in the [dashboard](/documentation/getting-started/api-keys) to start making authenticated requests.
Explore our available endpoints and their functionalities in the sections above.
Test the API with a simple health check:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -X GET https://api.roark.ai/v1/health \
-H "Authorization: Bearer YOUR_API_KEY"
```
## SDKs and Libraries
Official Node.js SDK for Roark
Python integration library
## Request constraints
Each operation documents its own request constraints and pagination parameters
in this reference. The OpenAPI schema beside a field is the source of truth for
its length, numeric, and array bounds, including whether a list endpoint uses a
cursor or an offset.
## OpenAPI Specification
The full OpenAPI 3.1 document is served unauthenticated, and every limit the API
enforces appears in it as `maxLength`, `maximum` or `maxItems` on the field itself.
It is available at three equivalent URLs:
```
https://api.roark.ai/openapi.json
https://api.roark.ai/.well-known/openapi.json
https://api.roark.ai/doc
```
Explore our complete OpenAPI specification for detailed endpoint documentation
# Create issue
Source: https://docs.roark.ai/api-reference/issue/create-issue
/api-reference/openapi.documented.json post /v1/issue
Opens a new issue with `source: API`. The supplied evidence (calls / chats) is attached in the same transaction; an invalid reference fails the whole request rather than leaving a half-created issue.
# Get issue by ID
Source: https://docs.roark.ai/api-reference/issue/get-issue-by-id
/api-reference/openapi.documented.json get /v1/issue/{issueId}
Returns a single issue with its evidence rows.
# List issues
Source: https://docs.roark.ai/api-reference/issue/list-issues
/api-reference/openapi.documented.json get /v1/issue
Returns the project’s issues, ordered newest-first. Supports filtering by status, source, and severity, with offset pagination.
# Create a knowledge base
Source: https://docs.roark.ai/api-reference/knowledge-base/create-a-knowledge-base
/api-reference/openapi.documented.json post /v1/knowledge-base
Creates a knowledge base. TEXT and JSON sources accept inline `content`. FILE sources require `filename`, `mimeType`, and base64-encoded `contentBase64` — the file is decoded server-side, stored in S3, and (for PDFs) text-extracted inline before the response returns.
# Get a knowledge base by ID
Source: https://docs.roark.ai/api-reference/knowledge-base/get-a-knowledge-base-by-id
/api-reference/openapi.documented.json get /v1/knowledge-base/{id}
Returns metadata for a single knowledge base. Does not include content — content is read by metrics at evaluation time and is not exposed over the public API.
# List knowledge bases
Source: https://docs.roark.ai/api-reference/knowledge-base/list-knowledge-bases
/api-reference/openapi.documented.json get /v1/knowledge-base
Returns a cursor-paginated list of knowledge bases for the authenticated project. Soft-deleted knowledge bases are excluded.
# Create and run a metric collection job
Source: https://docs.roark.ai/api-reference/metric-collection-job/create-and-run-a-metric-collection-job
/api-reference/openapi.documented.json post /v1/metric/collection-jobs
Creates a metric collection job for the specified calls or chats and metrics, then triggers processing. Provide exactly one of callIds or chatIds.
# Get metric collection job by ID
Source: https://docs.roark.ai/api-reference/metric-collection-job/get-metric-collection-job-by-id
/api-reference/openapi.documented.json get /v1/metric/collection-jobs/{jobId}
Returns a specific metric collection job with progress information.
# Get metric values produced by a metric collection job
Source: https://docs.roark.ai/api-reference/metric-collection-job/get-metric-values-produced-by-a-metric-collection-job
/api-reference/openapi.documented.json get /v1/metric/collection-jobs/{jobId}/metric-values
Returns the metric values produced by the specified job, grouped by metric definition. Unlike `GET /v1/call/:callId/metrics` (which returns the latest values on a call, regardless of which job computed them), this endpoint returns exactly the values *this* job produced — including for calls whose live values have since been overwritten by a later job. By default returns only SUCCESS rows; pass `?status=all` to also include NOT_APPLICABLE / DATA_MISSING / ERROR.
# List metric collection jobs
Source: https://docs.roark.ai/api-reference/metric-collection-job/list-metric-collection-jobs
/api-reference/openapi.documented.json get /v1/metric/collection-jobs
Returns a paginated list of metric collection jobs for the project.
# Retry a metric collection job
Source: https://docs.roark.ai/api-reference/metric-collection-job/retry-a-metric-collection-job
/api-reference/openapi.documented.json post /v1/metric/collection-jobs/{jobId}/retry
Creates a new metric collection job using the same conversations and metrics as a previous job, then triggers processing. The previous job must be in a terminal state (COMPLETED, FAILED, or CANCELED). Returns the newly created job — track its id for downstream fetches.
# Create a metric policy
Source: https://docs.roark.ai/api-reference/metric-policy/create-a-metric-policy
/api-reference/openapi.documented.json post /v1/metric/policies
Creates a new metric policy. Policies define which metrics to collect and under what conditions.
# Delete a metric policy
Source: https://docs.roark.ai/api-reference/metric-policy/delete-a-metric-policy
/api-reference/openapi.documented.json delete /v1/metric/policies/{policyId}
Soft-deletes a metric policy. System policies cannot be deleted.
# Get metric policy by ID
Source: https://docs.roark.ai/api-reference/metric-policy/get-metric-policy-by-id
/api-reference/openapi.documented.json get /v1/metric/policies/{policyId}
Returns a specific metric policy with its conditions and metrics.
# List metric policies
Source: https://docs.roark.ai/api-reference/metric-policy/list-metric-policies
/api-reference/openapi.documented.json get /v1/metric/policies
Returns a paginated list of metric policies for the project, including system policies.
# Update a metric policy
Source: https://docs.roark.ai/api-reference/metric-policy/update-a-metric-policy
/api-reference/openapi.documented.json put /v1/metric/policies/{policyId}
Updates an existing metric policy. System policies cannot be modified.
# Create a metric variant
Source: https://docs.roark.ai/api-reference/metric/create-a-metric-variant
/api-reference/openapi.documented.json post /v1/metric/definitions/{idOrSlug}/variants
Add a configuration of this metric for your organization, seeded from its Default. Edit it with
PUT to change what it measures, then pin it where you want it used.
Threshold metrics have no variants: their configuration comes from the metric they derive from.
Metrics in a package that manages its own variants reject this too.
# Create a threshold on a metric
Source: https://docs.roark.ai/api-reference/metric/create-a-threshold-on-a-metric
/api-reference/openapi.documented.json post /v1/metric/definitions/{idOrSlug}/thresholds
Create a boolean threshold derived from an existing metric. The source metric is addressed by its UUID or its stable `slug`. The threshold fires when the source metric meets the comparison condition. Scope and supported contexts are inherited from the source metric.
# Create custom metric definition
Source: https://docs.roark.ai/api-reference/metric/create-custom-metric-definition
/api-reference/openapi.documented.json post /v1/metric/definitions
Create a new metric definition. The `calculationType` field selects the variant: LLM_JUDGE (LLM-evaluated), FORMULA (computed from a math expression over other metrics), or PATTERN (detects a trigger→outcome pattern within a window). To create a threshold on top of an existing metric, use `POST /metric/definitions/{idOrSlug}/thresholds` instead.
# Delete a metric definition
Source: https://docs.roark.ai/api-reference/metric/delete-a-metric-definition
/api-reference/openapi.documented.json delete /v1/metric/definitions/{idOrSlug}
Archives (soft-deletes) a custom metric definition, addressed by its UUID or its stable `slug`. The metric is hidden from all reads and stops being collected, but its record and previously collected values are retained. System metrics cannot be deleted. Fails with 409 if the metric is still used as a source by a derived metric (delete those first).
# Delete a metric variant
Source: https://docs.roark.ai/api-reference/metric/delete-a-metric-variant
/api-reference/openapi.documented.json delete /v1/metric/definitions/{idOrSlug}/variants/{variantId}
Remove one of your organization’s variants. Anything pinned to it falls back to the Default, so
deleting a fork of a Roark variant returns you to Roark’s configuration.
Roark’s own variants cannot be deleted, and neither can a Default. Values already collected under
the deleted variant are retained.
# Get a metric definition
Source: https://docs.roark.ai/api-reference/metric/get-a-metric-definition
/api-reference/openapi.documented.json get /v1/metric/definitions/{idOrSlug}
Fetch a single metric definition by its UUID or its stable `slug` (e.g. `customer_satisfaction`). Resolution is scoped to the project — a project-owned metric wins over an org-wide one, which wins over a system metric of the same `slug`.
# Get a metric variant
Source: https://docs.roark.ai/api-reference/metric/get-a-metric-variant
/api-reference/openapi.documented.json get /v1/metric/definitions/{idOrSlug}/variants/{variantId}
One configuration of this metric, by id.
# List a metric’s variants
Source: https://docs.roark.ai/api-reference/metric/list-a-metric’s-variants
/api-reference/openapi.documented.json get /v1/metric/definitions/{idOrSlug}/variants
Every configuration of this metric your organization can use: Roark’s own variants and any your
organization has added or forked. `isDefault` marks the one the metric is scored with when nothing
pins another; pass any variant’s `id` as `sourceVariantId` to pin it on a derived metric.
Auto-managed variants (the ones a package materializes for you) are not listed: they are engine state,
not configuration you author.
# List metric definitions
Source: https://docs.roark.ai/api-reference/metric/list-metric-definitions
/api-reference/openapi.documented.json get /v1/metric/definitions
Fetch metric definitions available in the project, including both system-generated and custom metrics. Results are ordered by immutable definition ID; pass `nextCursor` to retrieve the following page.
# Update a metric definition
Source: https://docs.roark.ai/api-reference/metric/update-a-metric-definition
/api-reference/openapi.documented.json put /v1/metric/definitions/{idOrSlug}
Update the editable subset of a custom metric definition, addressed by its UUID or its stable `slug`. Only the supplied fields are changed; omitted fields are left unchanged. Every update creates a new immutable version; the response carries the advanced `versionId`. Immutable fields (scope, outputType, calcType, …) are rejected, and which fields are editable depends on the metric (e.g. derived metrics only allow `name`). Roark's own metrics are rejected here: this endpoint edits the shared definition, which every workspace sees. To change one for your workspace alone, edit its variant with PUT /v1/metric/definitions/{idOrSlug}/variants/{variantId}, which forks it for you and leaves every other workspace on the original.
# Update a metric variant
Source: https://docs.roark.ai/api-reference/metric/update-a-metric-variant
/api-reference/openapi.documented.json put /v1/metric/definitions/{idOrSlug}/variants/{variantId}
Rename a variant, change its configuration, or both. Every configuration change creates a new
immutable version and advances `versionId`; the response carries the advanced value.
**Editing one of Roark’s own variants forks it for your organization.** The response carries the
new variant’s `id`, which will differ from the one in the path, and `isSystem` becomes false. Roark’s
variant is untouched and other organizations keep it. DELETE your fork to go back to it.
Which fields are editable depends on the metric: some Roark metrics lock their prompt or output
configuration, and a locked field is rejected rather than ignored.
# Get environment by ID
Source: https://docs.roark.ai/api-reference/simulation-environment/get-environment-by-id
/api-reference/openapi.documented.json get /v1/simulation/environment/{environmentId}
Returns a single environment by its ID.
# List environments
Source: https://docs.roark.ai/api-reference/simulation-environment/list-environments
/api-reference/openapi.documented.json get /v1/simulation/environment
Returns a paginated list of environments: the project's own plus the environments Roark curates and shares across every project. Reference one by id when setting a customer flow variant's environment.
# Get simulation by ID
Source: https://docs.roark.ai/api-reference/simulation-job/get-simulation-by-id
/api-reference/openapi.documented.json get /v1/simulation/job/{jobId}
Get a individual simulation run directly by its ID. This is generally part of a larger simulation run plan job.
# Lookup by phone number
Source: https://docs.roark.ai/api-reference/simulation-job/lookup-by-phone-number
/api-reference/openapi.documented.json get /v1/simulation/job/lookup
Find the matching simulation using the number used by the Roark simulation agent.
# Create a new persona
Source: https://docs.roark.ai/api-reference/simulation-persona/create-a-new-persona
/api-reference/openapi.documented.json post /v1/persona
Creates a new persona for the authenticated project.
# Delete a persona
Source: https://docs.roark.ai/api-reference/simulation-persona/delete-a-persona
/api-reference/openapi.documented.json delete /v1/persona/{personaId}
Soft-deletes a persona by its ID. The persona is hidden from all reads and stops being available to new simulations, but its record and history are retained. System personas cannot be deleted.
# Get persona by ID
Source: https://docs.roark.ai/api-reference/simulation-persona/get-persona-by-id
/api-reference/openapi.documented.json get /v1/persona/{personaId}
Returns a specific persona by its ID.
# List personas
Source: https://docs.roark.ai/api-reference/simulation-persona/list-personas
/api-reference/openapi.documented.json get /v1/persona
Returns a paginated list of personas for the authenticated project.
# Update a persona
Source: https://docs.roark.ai/api-reference/simulation-persona/update-a-persona
/api-reference/openapi.documented.json put /v1/persona/{personaId}
Updates an existing persona by its ID.
# Get simulation plan job
Source: https://docs.roark.ai/api-reference/simulation-run-plan-job/get-simulation-plan-job
/api-reference/openapi.documented.json get /v1/simulation/plan/job/{jobId}
Retrieve details of a simulation plan job including all associated simulation jobs (calls)
# List simulation plan jobs
Source: https://docs.roark.ai/api-reference/simulation-run-plan-job/list-simulation-plan-jobs
/api-reference/openapi.documented.json get /v1/simulation/plan/jobs
Returns a paginated list of simulation run plan jobs. Filter by status, plan ID, or label to find specific simulation batches.
# Run a simulation plan
Source: https://docs.roark.ai/api-reference/simulation-run-plan-job/run-a-simulation-plan
/api-reference/openapi.documented.json post /v1/simulation/plan/{planId}/job
Deprecated: use POST /v1/simulation/run, which does the same thing and can also take the
plan configuration inline, so a one-off run does not have to create a plan first.
Creates and executes a job for an existing simulation run plan. Optionally provide runtime
variables to override plan-defined variables.
# Create a run plan
Source: https://docs.roark.ai/api-reference/simulation-run-plan/create-a-run-plan
/api-reference/openapi.documented.json post /v1/simulation/plan
Creates a new simulation run plan.
To run a simulation, use POST /v1/simulation/run instead: it starts a run from a plan or
from an inline configuration, and takes runtime variables. Create a plan here when you want
a reusable, named one to run later.
# Delete a run plan
Source: https://docs.roark.ai/api-reference/simulation-run-plan/delete-a-run-plan
/api-reference/openapi.documented.json delete /v1/simulation/plan/{planId}
Soft-deletes a simulation run plan by its ID.
# Get run plan by ID
Source: https://docs.roark.ai/api-reference/simulation-run-plan/get-run-plan-by-id
/api-reference/openapi.documented.json get /v1/simulation/plan/{planId}
Returns a specific simulation run plan by its ID.
# List run plans
Source: https://docs.roark.ai/api-reference/simulation-run-plan/list-run-plans
/api-reference/openapi.documented.json get /v1/simulation/plan
Returns a paginated list of simulation run plans. Optionally filter by search text or agent ID.
# Update a run plan
Source: https://docs.roark.ai/api-reference/simulation-run-plan/update-a-run-plan
/api-reference/openapi.documented.json put /v1/simulation/plan/{planId}
Updates an existing simulation run plan by its ID.
# Run a simulation
Source: https://docs.roark.ai/api-reference/simulation/run-a-simulation
/api-reference/openapi.documented.json post /v1/simulation/run
Starts a simulation and returns the run.
Send `plan` to describe a simulation and run it once. Add `saveAsPlan` to keep that
configuration as a reusable run plan. Send `planId` instead to run a plan you already have.
# Create webhook
Source: https://docs.roark.ai/api-reference/webhook/create-webhook
/api-reference/openapi.documented.json post /v1/webhook
Creates a new webhook with event subscriptions. The signing secret is only returned in this response.
# Delete webhook
Source: https://docs.roark.ai/api-reference/webhook/delete-webhook
/api-reference/openapi.documented.json delete /v1/webhook/{webhookId}
Deletes a webhook and all its event subscriptions.
# Event
Source: https://docs.roark.ai/api-reference/webhook/event
/api-reference/openapi.documented.json webhook Webhook
Roark POSTs a JSON payload to every endpoint subscribed to one of the events listed below. Every payload uses the same envelope (`event`, `version`, `timestamp`, `data`); the `event` field is the discriminator that selects the matching `data` shape.
Acknowledge with any 2xx response within 10 seconds. Non-2xx responses and timeouts are retried with exponential backoff.
**Events**
- `call.analysis.completed`
- `call.analysis.failed`
- `call.analysis.cancelled`
- `call.evaluation.completed`
- `call.evaluation.failed`
- `simulation.run_plan_job.started`
- `simulation.run_plan_job.completed`
- `simulation.run_plan_job.failed`
- `simulation.run_plan_job.cancelled`
- `simulation.job.started`
- `simulation.job.completed`
- `simulation.job.failed`
- `simulation.job.cancelled`
- `metric_collection.job.completed`
- `metric_collection.job.failed`
- `chat.analysis.completed`
- `chat.analysis.failed`
- `issue.opened`
- `issue.resolved`
# Get webhook by ID
Source: https://docs.roark.ai/api-reference/webhook/get-webhook-by-id
/api-reference/openapi.documented.json get /v1/webhook/{webhookId}
Returns a specific webhook with its event subscriptions.
# List webhooks
Source: https://docs.roark.ai/api-reference/webhook/list-webhooks
/api-reference/openapi.documented.json get /v1/webhook
Returns a paginated list of webhooks with their event subscriptions.
# Agents
Source: https://docs.roark.ai/documentation/config-as-code/agents
Define voice agents as config
A voice agent. Thin by design; endpoints are nested and fanned out on apply. References nothing.
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: agent
name: frontdesk
description: Bright Smiles front desk booking agent
customId: bright-smiles-frontdesk
endpoints:
- name: outbound-primary
value: '+15551234567'
direction: INCOMING_AND_OUTGOING
```
* `name` is the local key (`^[a-z0-9][a-z0-9-_.]*$`) and, with the kind, forms the config identity.
* `endpoints[]` each take a `name`, a `value` (E.164 phone number), a `direction` (`INCOMING`, `OUTGOING`, or `INCOMING_AND_OUTGOING`), and an optional `environment`.
* Other resources reference an agent by its `name` (e.g. a flow's `agents:` list, or a collector's `AGENT` filter).
For the full field reference of every kind, see the [Config DSL reference](https://schema.roark.ai/roark-config.schema.json) schema.
# Alerts
Source: https://docs.roark.ai/documentation/config-as-code/alerts
Define alerts (monitors) as config: threshold, event, and simulation triggers
An alert (monitor) reacts to a trigger by opening an issue and/or notifying Slack channels and webhooks. Pick one of three trigger types with `trigger.type`; the type is immutable once created (a change is delete-and-recreate).
## Threshold
Watch a metric aggregate over a rolling window. Opens an issue when it crosses the threshold, and optionally notifies Slack/webhooks. `metric` is the metric's slug (a custom metric you defined, or a Roark system metric); the variant defaults to the metric's default.
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: alert
name: high-frustration
trigger:
type: threshold
metric: frustration_score # metric slug (its metricId)
aggregation: MEAN # COUNT | RATE_PER_MINUTE | MEAN
windowMinutes: 60
operator: GT # GT | GTE | LT | LTE
thresholdValue: 3
minSampleSize: 5 # optional; skip windows with fewer data points
actions:
slack:
- channelId: C0123ABCXYZ # Slack-native channel id
channelName: '#agent-quality'
webhooks:
- https://hooks.example.com/roark/alerts
```
## Event
Fire on platform events (call/chat analysis, simulation jobs, metric collection, issues).
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: alert
name: analysis-failures
trigger:
type: event
events:
- CALL_ANALYSIS_FAILED
- CHAT_ANALYSIS_FAILED
actions:
slack:
- channelId: C0123ABCXYZ
channelName: '#platform-alerts'
```
## Simulation
Notify on a simulation run-plan job outcome. `runPlan` (by name) scopes it to one run plan; omit for project-wide. A simulation alert requires exactly one Slack channel and does not support webhooks.
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: alert
name: nightly-suite-result
trigger:
type: simulation
conditions: [FAILURE, THRESHOLD_FAILED] # SUCCESS | FAILURE | THRESHOLD_FAILED
runPlan: nightly-regression
deliveryFormat: MESSAGE # MESSAGE | PDF
actions:
slack:
- channelId: C0999SIMS
channelName: '#sim-results'
```
* **Threshold alerts always open an issue**; Slack and webhook delivery are optional additions.
* **Slack channels** are referenced by their Slack-native `channelId` (e.g. `C0123ABC`) plus a `channelName`. The Slack workspace is resolved from the project's connected Slack integration, so no UUIDs go in config.
* **Webhooks** must be public `http(s)` URLs.
Referencing a Slack channel requires a **Slack integration connected to the project** in Roark first. Connect Slack under the project's integration settings, then reference the channel by id here.
For the full field reference of every kind, see the [Config DSL reference](https://schema.roark.ai/roark-config.schema.json) schema.
# Collectors
Source: https://docs.roark.ai/documentation/config-as-code/collectors
Decide which metrics get collected on which conversations
Decides which metrics get collected on which conversations (the config form of a [collector](/documentation/metrics/metric-collectors)). Its `metrics:` list references both custom metrics you defined and Roark's system metrics, all by slug.
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: collector
name: consent-on-frontdesk
modality: call
status: ACTIVE
metrics:
- consent_collection_consent_obtained
- refund_policy_accuracy
filters:
- conditions:
- type: AGENT
key: frontdesk
operator: EQUALS
```
* `modality` is `call` or `chat` and is immutable once created (make a new collector to switch).
* **Metrics** are referenced by their stable slug, not a UUID, and must be visible to the project and support the collector's `modality`.
* **Filters** are condition groups: groups OR together, conditions within a group AND. An `AGENT` condition's `key` is a config-managed agent name (resolved on apply); every other type (`CALL_SOURCE`, `CALL_PROPERTY`, `INTEGRATION`) uses its `key`/`value` verbatim. Omit `filters` to score every matching conversation.
For the full field reference of every kind, see the [Config DSL reference](https://schema.roark.ai/roark-config.schema.json) schema.
# Flows
Source: https://docs.roark.ai/documentation/config-as-code/flows
Define simulation flows as config (improv and scripted)
A simulation flow. Both types share `kind: flow` and are discriminated by `type`. Flows reference agents, a persona, and an environment by name.
## Improv (`type: improv`)
An improvised simulation with a happy path and edge-case variants.
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: flow
type: improv
name: frustrated-rebooking
agents: [frontdesk]
happyPath:
persona: frustrated-caller
environment: Quiet line
prompt: You call to rebook the cleaning that was cancelled on you.
edgeCases:
- name: escalates-to-manager
prompt: file://prompts/escalates-to-manager.md
expectations:
- Agent offers to escalate rather than arguing
```
* `happyPath` requires a `persona` and `environment` (by name); `prompt` and `expectations` are optional.
* Each `edgeCases[]` entry inherits the happy path unless it overrides `persona` / `environment` / `prompt`, and can add its own `expectations`.
## Scripted (`type: scripted`)
A step-by-step conversation graph (branches, merges, DTMF, voicemail, scenario links). Each apply replaces the whole graph.
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: flow
type: scripted
name: booking-scripted
agents: [frontdesk]
branchingMode: ADAPTIVE
graph:
- ref: greeting
type: AGENT_TURN
content: Thanks for calling, how can I help?
steps:
- ref: request
type: CUSTOMER_TURN
content: I'd like to book a cleaning.
```
* Each node has a `type` and an optional `ref` label so other nodes can rejoin it via `mergeInto` (DAG merge edges). `steps` are a node's successors (more than one = a branch).
* No UUIDs: identity is the `ref`, and each apply replaces the whole graph.
* The environment is referenced by its display name and must already exist in Roark.
For the full field reference of every kind, see the [Config DSL reference](https://schema.roark.ai/roark-config.schema.json) schema.
# Metrics
Source: https://docs.roark.ai/documentation/config-as-code/metrics
Define custom LLM-judged metrics as config
A [custom metric](/documentation/metrics/custom-metrics) you author, graded by an LLM against a prompt. Reference it from a collector by its `name`.
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: metric
name: refund_policy_accuracy # the slug a collector references
displayName: Refund Policy Accuracy
type: BOOLEAN # BOOLEAN | SCALE | NUMERIC | TEXT | CLASSIFICATION
prompt: |
Using {{transcript}} and {{world_context}}, did the agent state the refund policy correctly?
trueLabel: Accurate
falseLabel: Inaccurate
```
* `name` is the metric's stable slug (its `metricId`), exactly what a collector references. `displayName` is the human name shown in the UI.
* The **prompt** is the grading criteria and must reference a transcript source (e.g. `{{transcript}}`) and `{{world_context}}`. Inline or `file://`.
* `name`, `type`, and `scope` are immutable once created (a change is create-new). Editing the prompt, labels, bounds, or options creates a new version.
* SCALE metrics add `scaleMin` / `scaleMax` (+ optional `scaleLabels`); CLASSIFICATION metrics add `options` (+ optional `maxSelections`); `scope: PER_PARTICIPANT` requires a `participantRole`.
**System metrics** (Roark's built-in metrics) are managed by Roark, not by you: you never define
them in config. To collect one, just reference its slug in a collector's `metrics:` list, exactly
like a custom metric. The same is true for derived metrics (thresholds, formulas, patterns), which
are not yet definable in config.
For the full field reference of every kind, see the [Config DSL reference](https://schema.roark.ai/roark-config.schema.json) schema.
# Overview
Source: https://docs.roark.ai/documentation/config-as-code/overview
Define your Roark agents, personas, flows, metrics, collectors, and alerts as YAML in git and deploy them with one apply
Config as Code lets you define your Roark resources - agents, personas, simulation flows, custom metrics, collectors, and alerts - as YAML files in your own git repository, then deploy them with a single apply. Your config repo is the source of truth: Roark reconciles the live project to match what you submitted, creating what's new, updating what changed, and removing what you deleted.
## Quickstart
Put each resource in a YAML file under a directory, then apply. Install the [CLI](/documentation/sdks/cli) and set `ROARK_API_BEARER_TOKEN` first (the key needs `config:apply`).
```yaml roark/agents/frontdesk.yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
kind: agent
name: frontdesk
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark config diff ./roark # preview the changes
roark config apply ./roark # apply them
```
That's the whole loop. Add personas, flows, metrics, collectors, and alerts the same way; each kind has its own reference page below.
***
Config as Code manages **resource definitions**. It does not run simulations or place calls; you trigger those as usual once the resources exist (see [CI/CD](/documentation/simulation-testing/ci-cd) to apply config and start a run in one pipeline).
You write only a human-readable `name` for each resource. Roark derives a stable identity (`configKey = /`) and resolves cross-references by name, so there are no UUIDs in your files and no state file to keep in sync.
The easiest way to run Config as Code. `roark config diff ./roark` and `roark config apply ./roark` bundle your directory (resolving `file://` prompts) and submit it for you. See the CLI page for install, auth, and a CI example.
***
## How it works
You submit the full desired set of resources to a single endpoint. Roark:
1. **Parses and validates** every resource against the schema.
2. **Diffs** the submitted set against the resources this project already manages via config.
3. **Reconciles**: creates new resources, updates changed ones, and (unless you opt out) deletes config-managed resources you removed from the submission.
There are two endpoints:
| Endpoint | What it does |
| :---------------------- | :---------------------------------------------------------------- |
| `POST /v1/config/diff` | **Dry run.** Returns the changes that *would* be made. No writes. |
| `POST /v1/config/apply` | **Applies** the changes and returns what happened. |
Always run `diff` first to preview the changes, then `apply`.
***
## Prerequisites
* A Roark API key with the **`config:apply`** permission. Generate one from [API Keys](/documentation/getting-started/api-keys) and confirm it carries `config:apply`.
* A git repository to hold your config files (any layout; Roark reads the files you submit).
* The **Roark CLI** (recommended): `npm install -g @roarkanalytics/cli`, or run it on demand with `npx @roarkanalytics/cli`. The CLI builds the bundle from your config directory and drives `diff`/`apply` for you, so it's the easiest way to deploy. Raw HTTP and the SDKs work too.
The API key is scoped to a single project. Everything you apply lands in that project.
***
## Repository layout
One file per resource, discriminated by `kind`. A conventional layout:
```
roark/
agents/frontdesk.yaml
personas/frustrated-caller.yaml
flows/frustrated-rebooking.yaml # improv flow
flows/booking-scripted.yaml # scripted-graph flow
metrics/refund-policy-accuracy.yaml
collectors/consent-on-frontdesk.yaml
alerts/high-frustration.yaml # threshold / event / simulation alert
prompts/escalates-to-manager.md # referenced by file://
```
Add this header to any resource file for editor autocomplete and validation:
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
```
***
## Resource kinds
One file per resource, discriminated by `kind`. Each kind has its own reference page with fields and examples:
Voice agents and their phone endpoints.
The simulated caller for a flow.
Simulation flows: improvised or scripted graphs.
Custom LLM-judged metric definitions.
Which metrics get collected on which conversations.
Alerts (monitors): threshold, event, and simulation triggers.
For the full field reference of every kind, see the [Config DSL reference](https://schema.roark.ai/roark-config.schema.json) schema.
***
## Deploying
The easiest way to deploy is the **Roark CLI**. Point it at your config directory and it builds the bundle for you (reading every YAML file and inlining `file://` prompts), so there is no JSON body to assemble by hand.
The [CLI](/documentation/sdks/cli) does the bundling for you: `roark config diff ./roark` and `roark config apply ./roark` take the directory directly, resolve `file://` prompt references, and submit the result. The raw requests below are what it sends.
Give the CLI the project API key that carries `config:apply`:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
export ROARK_API_BEARER_TOKEN=""
# or store it once: roark auth login
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark config diff ./roark
```
The CLI reads every resource under `./roark`, builds the bundle, and prints one line per change (`+` create, `~` update, `-` delete) with a tally:
```text theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
+ collector/consent-on-frontdesk
~ agent/frontdesk
1 to create, 1 to update, 0 to delete
```
Resources already in sync are no-ops and aren't listed, so a project that fully matches your config prints `0 to create, 0 to update, 0 to delete`.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark config apply ./roark
```
`apply` previews the same diff, asks you to confirm, then reconciles and reports what it did. Pass `--yes` to skip the prompt in CI, and `--no-prune` for an additive-only apply that never deletes.
### Using raw HTTP
If you'd rather call the API directly, bundle your resources into a single JSON body: `{ "resources": [...], "prune": true }`, where each entry is one resource in the same shape as its YAML. `POST` it to `/v1/config/diff` first, then `/v1/config/apply`.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -X POST https://api.roark.ai/v1/config/diff \
-H "Authorization: Bearer $ROARK_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @bundle.json
```
The response lists each projected change with an `op` (`create`, `update`, or `delete`) plus a summary; in-sync resources are counted in `summary.noop` and omitted from `changes`:
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"data": {
"changes": [
{ "configKey": "collector/consent-on-frontdesk", "kind": "collector", "name": "consent-on-frontdesk", "op": "create" }
],
"summary": { "create": 1, "update": 0, "delete": 0, "noop": 0 }
}
}
```
`apply` takes the same body against `/v1/config/apply`; each change comes back with a `status` (`applied` or `failed`) and, on success, the resource `id`.
These endpoints are available in the [Node.js](/documentation/sdks/node-sdk) and [Python](/documentation/sdks/python-sdk) SDKs as `config.diff` and `config.apply`, taking the same bundle, and in the [CLI](/documentation/sdks/cli) as `roark config diff` and `roark config apply`, taking a directory.
***
## Apply semantics
* **Identity is by name.** Re-submitting an unchanged resource updates it in place; it never creates a duplicate. Renaming a resource is a delete of the old name plus a create of the new one.
* **Cross-references resolve by name** within the same submission (a flow's `agents:`/`persona:`, a collector's `AGENT` filter). The referenced resource must be in the bundle or already config-managed in the project.
* **Prune deletes what you removed.** By default, config-managed resources absent from the submission are deleted so the project matches your repo exactly. To layer additive changes without deleting, send `"prune": false`.
* **Prompts are code.** Any prompt field takes an inline string or `file://relative/path.md`, resolved relative to your config root and inlined before you submit.
* **Idempotent.** Applying the same bundle twice converges to the same state. An unchanged resource is a no-op on the next `diff`/`apply`, not a rewrite, so a re-run of an in-sync project reports no changes.
With `prune` enabled (the default), a resource you delete from your repo is deleted from Roark on the next apply. Submit the **full** desired set every time, or use `"prune": false` for additive-only applies.
***
## Config-managed resources in the UI
A resource created by config is **read-only in the dashboard** and carries a "managed by config" badge. To change it, edit your config and re-apply.
If you need to hand a resource back to manual UI editing, **detach** it (from the resource's menu in the dashboard). Detaching clears its config ownership:
* A later apply that still lists it will re-adopt it.
* A later apply that omits it will simply leave it alone (it is no longer config-managed, so prune won't touch it).
***
## Recommended workflow
1. Keep your `roark/` config in a git repo, reviewed via pull requests.
2. In CI, run `roark config diff ./roark` on every PR and post the output for review.
3. On merge to your main branch, run `roark config apply ./roark --yes`.
Both CI steps read the API key from `ROARK_API_BEARER_TOKEN` (store it as a secret with `config:apply`). This gives you versioned, reviewable, reproducible Roark resources with a full audit trail in git.
For a copy-pasteable GitHub Actions workflow that does exactly this, see [Using the CLI in CI](/documentation/sdks/cli#using-the-cli-in-ci).
# Personas
Source: https://docs.roark.ai/documentation/config-as-code/personas
Define the simulated caller as config
The simulated caller for a flow. Self-contained (references nothing).
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://schema.roark.ai/roark-config.schema.json
kind: persona
name: frustrated-caller
displayName: Dana Whitfield
language: EN
accent: US
gender: FEMALE
baseEmotion: FRUSTRATED
backstoryPrompt: file://prompts/frustrated-caller.md
```
* `name` is the local key; `displayName` is the name the agent hears (defaults to `name`).
* `language`, `accent`, and `gender` are required; behavioral knobs (`baseEmotion`, `speechPace`, `speechClarity`, `intentClarity`, `confirmationStyle`, `memoryReliability`, `responseTiming`, `backgroundNoise`, and the idle-message fields) are optional and default sensibly.
* `backstoryPrompt` is the caller's background: inline or a `file://` reference resolved from your config root.
* A flow references a persona by its `name`.
For the full field reference of every kind, see the [Config DSL reference](https://schema.roark.ai/roark-config.schema.json) schema.
# Data Retention
Source: https://docs.roark.ai/documentation/enterprise/data-retention
Control how long Roark keeps your call data, and whether expired data is hidden or permanently deleted
Data retention lets you control how long Roark keeps your calls and their metrics, and what happens when they expire. It's configured **per project**.
## Configure retention
Go to **Settings → Project → Data Controls** and set two things:
How long to keep calls after they're created. Choose 60, 90, 180, or 365 days, or **Indefinite** to keep data forever. Defaults to **Indefinite**.
What happens to a call once it passes the retention period:
* **Hide from dashboard (recoverable)** - the call is soft-deleted: hidden from the dashboard but still stored on the backend, so it can be recovered. This is the default.
* **Permanently delete (irreversible)** - the call is physically purged everywhere: recordings, transcripts, metrics, and traces.
## How expiry works
A call "expires" once its age exceeds the retention period. What happens next depends on the mode:
* **Hide** - expired calls are soft-deleted (removed from the dashboard but recoverable).
* **Permanent** - expired calls are hidden first, then **physically purged 30 days later** across all systems (Postgres, the metric store, recordings in object storage, analytics, and traces). This grace period gives you a window to catch a misconfiguration before data is gone for good.
Permanent deletion cannot be undone. Once a call is physically purged, its recordings, transcripts, metrics, and traces are gone across every system.
Retention applies going forward from when you set it. Setting a shorter period will begin expiring older calls that already fall outside the new window.
# API Keys
Source: https://docs.roark.ai/documentation/getting-started/api-keys
Generate and manage authentication keys for Roark APIs
## Overview
API keys authenticate your requests to Roark's API, enabling secure access to monitoring, testing, and analytics features.
API keys are essential for all integrations - whether you're using our SDKs, webhooks, or direct API calls.
## Generating an API Key
Go to the API Keys section in your dashboard
Click the "Create API Key" button
Choose your access level:
* **Read-Only**: Retrieve data only
* **Full Access**: Read and write operations
Copy and store your key immediately - it won't be shown again
## Using Your API Key
All API requests require authentication via the Authorization header:
```bash cURL theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -X GET "https://api.roark.ai/health" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import requests
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get("https://api.roark.ai/health", headers=headers)
```
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const response = await fetch("https://api.roark.ai/health", {
headers: {
"Authorization": `Bearer YOUR_API_KEY`
}
});
```
```javascript Node.js theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const axios = require('axios');
const config = {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
};
const response = await axios.get('https://api.roark.ai/health', config);
```
## Security Best Practices
Never expose your API key in client-side code, public repositories, or logs. If compromised, regenerate immediately in your dashboard.
Store keys in `.env` files or secure vaults, never hardcode them
Regenerate keys periodically to maintain security
Use read-only keys when write access isn't needed
Check last-used timestamps to detect unauthorized access
## Common Integration Patterns
Initialize SDKs with your key:
```javascript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const roark = new RoarkClient({
apiKey: process.env.ROARK_API_KEY
});
```
Store as secret in your pipeline:
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
env:
ROARK_API_KEY: ${{ secrets.ROARK_API_KEY }}
```
## Need Help?
Explore all available endpoints and parameters
Step-by-step platform integration tutorials
# Welcome to Roark
Source: https://docs.roark.ai/documentation/getting-started/introduction
The quality platform for voice and chat AI
Roark is where voice and chat agents prove they're ready: **simulate** every failure mode before launch, **analyze** every production call once you're live, and score them all with metrics powered by **Roark Prism**, our purpose-built evaluation model.
Break it in staging, not in production. Test agents with synthetic callers across flows, personas, accents, and edge cases before you ship.
Every production call scored, filed, and traced. Transcribed and measured against your metrics in real time.
Label calls, set ground truth, and align every metric with your team's judgment.
From caught issue to drafted fix to proven deploy, with you in the loop.
***
## Your first simulation
Pick a path. Each one takes you from nothing to a running simulation, reusing a built-in persona and environment so there's nothing extra to set up.
Copy, paste, run with [`@roarkanalytics/sdk`](/documentation/sdks/node-sdk):
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import Roark from '@roarkanalytics/sdk'
const client = new Roark({ bearerToken: process.env.ROARK_API_BEARER_TOKEN })
// An agent and how to reach it
const agent = await client.agent.create({ name: 'Front Desk' })
const endpoint = await client.agentEndpoint.create({
agentId: agent.data.id,
value: '+15551234567',
direction: 'INCOMING_AND_OUTGOING',
})
// Reuse a built-in persona and the "Quiet line" environment
const personas = await client.simulationPersona.list({ limit: 50 })
const envs = await client.simulationEnvironment.list({ limit: 50 })
const quietLine = envs.data.find((e) => e.name === 'Quiet line')!
// The conversation to test
const flow = await client.customerFlow.create({
type: 'IMPROV',
title: 'Rebooking',
agentIds: [agent.data.id],
happyPath: {
title: 'Frustrated rebooking',
personaOverrideId: personas.data[0].id,
environmentId: quietLine.id,
prompt: 'You call to rebook a cleaning that was cancelled on you.',
},
agentExpectations: [{ prompt: 'Agent offers a concrete alternative appointment time' }],
})
// Run it
const run = await client.simulation.run({
plan: {
direction: 'INBOUND',
maxSimulationDurationSeconds: 300,
agentEndpoints: [{ id: endpoint.data.id }],
flows: [{ id: flow.data.id, happyPath: true }],
metrics: [{ slug: 'task_completion' }],
},
})
console.log(run.data.simulationRunPlanJobId)
```
Watch it in the dashboard, or poll `client.simulationRunPlanJob.getByID(run.data.simulationRunPlanJobId)`.
Same flow with [`roark_analytics`](/documentation/sdks/python-sdk):
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import os
from roark_analytics import Roark
client = Roark(bearer_token=os.environ["ROARK_API_BEARER_TOKEN"])
# An agent and how to reach it
agent = client.agent.create(name="Front Desk")
endpoint = client.agent_endpoint.create(
agent_id=agent.data.id,
value="+15551234567",
direction="INCOMING_AND_OUTGOING",
)
# Reuse a built-in persona and the "Quiet line" environment
personas = client.simulation_persona.list(limit=50)
envs = client.simulation_environment.list(limit=50)
quiet_line = next(e for e in envs.data if e.name == "Quiet line")
# The conversation to test
flow = client.customer_flow.create(
type="IMPROV",
title="Rebooking",
agent_ids=[agent.data.id],
happy_path={
"title": "Frustrated rebooking",
"personaOverrideId": personas.data[0].id,
"environmentId": quiet_line.id,
"prompt": "You call to rebook a cleaning that was cancelled on you.",
},
agent_expectations=[{"prompt": "Agent offers a concrete alternative appointment time"}],
)
# Run it
run = client.simulation.run(
plan={
"direction": "INBOUND",
"maxSimulationDurationSeconds": 300,
"agentEndpoints": [{"id": endpoint.data.id}],
"flows": [{"id": flow.data.id, "happyPath": True}],
"metrics": [{"slug": "task_completion"}],
},
)
print(run.data.simulation_run_plan_job_id)
```
Drive the same steps from your terminal (install the [CLI](/documentation/sdks/cli) and set `ROARK_API_BEARER_TOKEN`). Commands print JSON, so pipe through `jq`:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
AGENT_ID=$(roark agent create --name "Front Desk" | jq -r '.data.id')
ENDPOINT_ID=$(roark agent endpoint create --agent-id "$AGENT_ID" \
--value "+15551234567" --direction INCOMING_AND_OUTGOING | jq -r '.data.id')
PERSONA_ID=$(roark simulation persona list --limit 50 | jq -r '.data[0].id')
ENV_ID=$(roark simulation environment list --limit 50 \
| jq -r '.data[] | select(.name=="Quiet line") | .id')
FLOW_ID=$(roark customer-flow create --data "$(jq -nc \
--arg a "$AGENT_ID" --arg p "$PERSONA_ID" --arg e "$ENV_ID" '{
type:"IMPROV", title:"Rebooking", agentIds:[$a],
happyPath:{ title:"Frustrated rebooking", personaOverrideId:$p, environmentId:$e,
prompt:"You call to rebook a cleaning that was cancelled on you." }
}')" | jq -r '.data.id')
roark simulation run --data "$(jq -nc --arg ep "$ENDPOINT_ID" --arg f "$FLOW_ID" '{
plan:{ direction:"INBOUND", maxSimulationDurationSeconds:300,
agentEndpoints:[{id:$ep}], flows:[{id:$f, happyPath:true}],
metrics:[{slug:"task_completion"}] }
}')"
```
Keep your test suite in git as YAML and apply it with the [CLI](/documentation/sdks/cli):
```yaml roark/flows/rebooking.yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
kind: flow
type: improv
name: rebooking
agents: [frontdesk]
happyPath:
persona: frustrated-caller
environment: Quiet line
prompt: You call to rebook a cleaning that was cancelled on you.
expectations:
- Agent offers a concrete alternative appointment time
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark config apply ./roark
```
Config defines your resources; launch a run from the CLI, an SDK, or the dashboard. Full example (agents + personas + flows) and how to run it in CI: **[Config as Code](/documentation/config-as-code/overview)** and **[CI/CD](/documentation/simulation-testing/ci-cd)**.
No code:
1. **Connect your agent** under [Agents](/documentation/integrations/overview) (Vapi, Retell, ElevenLabs, LiveKit, and more).
2. **Create a [customer flow](/documentation/simulation-testing/customer-flows)** for the conversation to test (pick a built-in [persona](/documentation/simulation-testing/personas)).
3. **New Run**: pick the agent, attach the flow, add pass/fail [checks](/documentation/metrics/thresholds), launch.
4. **Read the verdict** and per-conversation scores.
***
## Explore the docs
Customer flows, personas, templates, plans, and schedules
Define agents, personas, flows, metrics, collectors, and alerts as YAML in git
Call history, traces, reports, and dashboards
The metric library, Studio, collectors, and thresholds
Vapi, Retell, ElevenLabs, Leaping, LiveKit, and custom
Drive Roark from your terminal and CI pipelines
Node.js, Python, and MCP Server
REST API documentation
# Bland AI
Source: https://docs.roark.ai/documentation/integrations/bland
Sync Bland AI agents and analyze voice AI conversations
Live monitoring Voice simulations Chat simulations
## Overview
The Bland AI integration syncs your voice agents and their calls into Roark. Agents and calls are pulled on a recurring schedule so every conversation is analyzed and evaluated automatically.
***
## Prerequisites
Before setting up the integration, ensure you have:
* A Bland AI account with active agents
* A Bland AI API key (found in your [Bland AI dashboard](https://app.bland.ai))
* Call recording enabled in your Bland AI send call payload (see [Call Payload Configuration](#call-payload-configuration) below)
***
## Call Payload Configuration
To sync calls with Roark, you must include two fields in your Bland AI send call payload:
| Field | Type | Description |
| :--------------- | :-------- | :------------------------------------------------------------------------------------------------------------------------------ |
| **`persona_id`** | `string` | The agent/persona ID. Roark uses this to match calls to agents and determine which calls to sync based on your selected agents. |
| **`record`** | `boolean` | Must be set to `true` so the call is recorded and Roark can access the audio for analysis. |
Calls without `record: true` will not have audio available and cannot be synced into Roark. Calls without a `persona_id` will not be matched to any agent.
***
## Setup Instructions
### Step 1: Create Integration
1. Navigate to **Agents** in your Roark dashboard
2. Click **Connect Agent** in the top right, choose **Existing Platform**, and select **Bland AI**
3. Enter your configuration:
| Field | Description |
| :------------------- | :--------------------------------- |
| **Integration Name** | Friendly name for this integration |
| **API Key** | Your Bland AI API key |
The system will validate your API key and fetch available agents.
### Step 2: Select Agents
Choose which Bland AI agents to monitor:
* **All Agents**: Sync calls from every agent in your account
* **Selected Agents**: Choose specific agents to monitor
Roark syncs calls based on the `persona_id` field in your call payload. Only calls matching your selected agents will be synced.
### Step 3: Activate Integration
Review your settings and click **Create Integration** to begin syncing.
***
## What Gets Synced
Bland AI integrations sync the following data:
* **Calls**: Conversation audio, transcripts, and metadata
* **Agents**: Agent/persona configurations
* **Transcripts**: Full conversation transcripts with speaker labels
* **Tool Calls**: Function/tool invocations during conversations
* **Call Metadata**: Duration, status, participant information
***
## How Sync Works
Bland AI uses **pull-based synchronization**: Roark periodically fetches new calls from the Bland AI API rather than receiving webhooks.
| Phase | What Happens |
| :------------- | :------------------------------------------------------------------------------------------------------------------------- |
| **Agent Sync** | Fetches all agents, updates names and configurations |
| **Call Sync** | Fetches conversations since last sync, downloads recordings, parses transcripts and tool invocations, creates call records |
Roark matches incoming calls to agents using the `persona_id` included in your send call payload. Only calls with `record: true` and a valid `persona_id` are synced.
***
## Monitoring Integration Health
Track your integration status in the Roark dashboard:
* **Syncing**: Actively importing calls
* **Active**: Running on schedule, pulling new calls periodically
* **Paused**: Sync temporarily halted
* **Error**: Configuration or connection issues
View sync statistics including total calls synced, last sync timestamp, and job history.
***
## Agent Management
Synced agents appear in:
* **Simulation agent selection**: Use for testing
* **Agent performance reports**: Track metrics per agent
* **Comparison dashboards**: Analyze across agents
Agent data updates automatically when modified in Bland AI.
***
## Next Steps
Test synced agents with simulations
Browse and analyze synced calls
Automate metric collection on calls
Explore other integrations
# Custom Integrations
Source: https://docs.roark.ai/documentation/integrations/custom-integrations
Send calls from any platform using our API and SDKs
## Overview
Not using one of our pre-built integrations? No problem. Send call data from any voice AI platform or custom application. Just provide an audio file and Roark handles the rest: transcription, speech analysis, sentiment detection, and metric collection.
***
## Send a Call
Install the [Node.js SDK](/documentation/sdks/node-sdk):
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
npm install @roarkanalytics/sdk
```
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import Roark from '@roarkanalytics/sdk'
const client = new Roark({
bearerToken: process.env.ROARK_API_BEARER_TOKEN,
})
const call = await client.call.create({
recordingUrl: 'https://your-storage.com/call-recording.mp3',
startedAt: '2024-01-15T10:00:00Z',
interfaceType: 'PHONE',
callDirection: 'INBOUND',
agent: {
name: 'Support Agent',
customId: 'custom-agent-1',
},
customer: {
phoneNumberE164: '+1234567890',
},
properties: {
department: 'sales',
region: 'us-east',
},
})
```
Install the [Python SDK](/documentation/sdks/python-sdk):
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
pip install roark-analytics
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import os
from roark_analytics import Roark
client = Roark(
bearer_token=os.environ.get("ROARK_API_BEARER_TOKEN"),
)
call = client.call.create(
recording_url="https://your-storage.com/call-recording.mp3",
started_at="2024-01-15T10:00:00Z",
interface_type="PHONE",
call_direction="INBOUND",
agent={
"name": "Support Agent",
"customId": "custom-agent-1",
},
customer={
"phoneNumberE164": "+1234567890",
},
properties={
"department": "sales",
"region": "us-east",
},
)
```
Use the [REST API](/api-reference/introduction) directly:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl --request POST \
--url https://api.roark.ai/v1/call \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"recordingUrl": "https://your-storage.com/call-recording.mp3",
"startedAt": "2024-01-15T10:00:00Z",
"interfaceType": "PHONE",
"callDirection": "INBOUND",
"agent": {
"name": "Support Agent",
"customId": "custom-agent-1"
},
"customer": {
"phoneNumberE164": "+1234567890"
},
"properties": {
"department": "sales",
"region": "us-east"
}
}'
```
Once a call is created, it appears in [Call History](/documentation/observability/live-monitoring) and is automatically transcribed and analyzed.
***
## What You Can Send
| Data | Description |
| :----------------------- | :----------------------------------------------------------- |
| **Audio file** | MP3, WAV, M4A, or FLAC (via public URL or base64) |
| **Metadata** | Agent info, customer details, call direction, interface type |
| **Custom properties** | Arbitrary key-value pairs for filtering and segmentation |
| **Tool calls** | Function/tool invocations made during the conversation |
| **Pre-transcribed text** | If you already have a transcript, send it along |
### Audio Requirements
| Spec | Requirement |
| :-------------- | :---------------------------------------- |
| **Format** | MP3, WAV, M4A, or FLAC |
| **Sample Rate** | 8kHz or higher |
| **Channels** | Mono or stereo |
| **Duration** | Up to 2 hours |
| **File Size** | Max 500MB |
| **Access** | Publicly accessible URL or base64 encoded |
***
## What Happens Next
Once a call is ingested, Roark automatically:
1. **Transcribes and analyzes** the conversation: speech patterns, sentiment, 64+ emotions, interruptions, and more
2. **Runs metric collectors**: any active [metric collectors](/documentation/metrics/metric-collectors) collect metrics automatically (LLM evaluations, compliance checks, custom KPIs)
3. **Makes it searchable**: the call appears in [Call History](/documentation/observability/live-monitoring) with full filtering by properties, agent, duration, and more
***
## Use With Simulations
Agents created via the API can also be used in [simulations](/documentation/simulation-testing/overview). When you send calls with an `agent.customId`, Roark creates or matches an agent record that you can then target in simulation run plans, useful for testing custom-built voice agents that aren't on a pre-built platform.
***
## MCP Server
If you use an AI-powered development environment, the [Roark MCP Server](/documentation/sdks/mcp-server) gives your AI assistant direct access to the Roark API (including creating calls, querying metrics, and managing agents) without writing integration code manually.
***
## Getting Started
Create an API key in your [Roark dashboard](/documentation/getting-started/api-keys)
Upload an audio file using the SDK or REST API above
Set up [metric collectors](/documentation/metrics/metric-collectors) to automatically collect metrics on every call
Browse calls in [Call History](/documentation/observability/live-monitoring), build [reports](/documentation/observability/reports), and create [dashboards](/documentation/observability/dashboards)
***
## Resources
Send OpenTelemetry traces from any platform
Full SDK guide and reference
Full SDK guide and reference
Complete REST API documentation
# ElevenLabs
Source: https://docs.roark.ai/documentation/integrations/elevenlabs
Sync ElevenLabs conversational AI agents and run voice and chat simulations
Live monitoring Voice simulations Chat simulations
## Overview
The ElevenLabs integration syncs your conversational AI agents and their calls into Roark. Agents, prompts, phone numbers, and tools are imported automatically, and calls are pulled on a recurring schedule so every conversation is analyzed.
A single ElevenLabs integration unlocks all three of Roark's capabilities for connected agents:
* **Live call monitoring**: pull conversation audio, transcripts, and metadata on a recurring schedule.
* **Voice simulations**: drive synthesized voice conversations against synced agents over phone or WebRTC.
* **Chat simulations**: drive [text-based simulations](/documentation/simulation-testing/chat-simulations) against synced agents over the ElevenLabs Conversational AI WebSocket. Roark auto-creates a chat endpoint for each agent on integration setup, so once you've connected ElevenLabs you can use any synced agent in a chat run plan immediately.
***
## Prerequisites
Before setting up the integration, ensure you have:
* An ElevenLabs account with active conversational AI agents
* An ElevenLabs API key (found in your ElevenLabs dashboard)
* Agents with **voice recording enabled** (agents with Zero Retention Mode or recording disabled cannot sync calls)
***
## Setup Instructions
### Step 1: Create Integration
1. Navigate to **Agents** in your Roark dashboard
2. Click **Connect Agent** in the top right, choose **Existing Platform**, and select **ElevenLabs**
3. Enter your configuration:
| Field | Description |
| :------------------- | :--------------------------------- |
| **Integration Name** | Friendly name for this integration |
| **API Key** | Your ElevenLabs API key |
The system will validate your API key and fetch available agents.
### Step 2: Configure Historical Sync
Choose how far back to import existing calls:
* **Default**: Last 90 days of call history
* **Custom**: Select a specific date range
* **Skip**: Only sync new calls going forward
Historical sync runs as a backfill job during initial setup. Ongoing calls are synced automatically on a recurring schedule.
### Step 3: Select Agents
Choose which ElevenLabs agents to monitor:
* **All Agents**: Sync calls from every agent in your account
* **Selected Agents**: Choose specific agents to monitor
The agent selector shows each agent's name and ID. Agents with Zero Retention Mode enabled or voice recording disabled will be flagged as unavailable for call sync.
### Step 4: Activate Integration
Review your settings and click **Create Integration** to begin syncing.
***
## What Gets Synced
ElevenLabs integrations sync comprehensive data:
* **Calls**: Conversation audio, transcripts, and metadata
* **Agents**: Agent configurations and settings
* **Prompts**: System prompts configured for each agent
* **Phone Numbers**: Phone number endpoints assigned to agents
* **Tools**: Tool definitions available to agents
* **Call Metadata**: Duration, direction (inbound/outbound), interface type (phone/web), termination reason
***
## How Sync Works
ElevenLabs uses **pull-based synchronization**: Roark periodically fetches new calls from the ElevenLabs API rather than receiving webhooks.
| Phase | What Happens |
| :--------------- | :---------------------------------------------------------------------------------------------------- |
| **Agent Sync** | Fetches all agents, updates names and configurations, syncs prompts, phone numbers, and tools |
| **Health Check** | Verifies each agent's recording and privacy settings are compatible |
| **Call Sync** | Fetches conversations since last sync, downloads audio, parses tool invocations, creates call records |
Calls must be at least 10 seconds long and have a successful status to be synced.
***
## Agent Privacy Settings
ElevenLabs agents have privacy controls that affect what Roark can sync:
* **Zero Retention Mode** enabled → Call recordings unavailable, calls cannot be synced
* **Record Voice** disabled → Same as above
During health checks, Roark flags agents with these settings so you know which agents can and cannot sync call data.
***
## Monitoring Integration Health
Track your integration status in the Roark dashboard:
* **Syncing**: Actively importing calls
* **Active**: Running on schedule, pulling new calls periodically
* **Paused**: Sync temporarily halted
* **Error**: Configuration or connection issues
View sync statistics including total calls synced, last sync timestamp, and job history.
***
## Next Steps
Test synced agents with simulations
Browse and analyze synced calls
Automate metric collection on calls
Explore other integrations
# Exporting to Snowflake
Source: https://docs.roark.ai/documentation/integrations/exporting-to-snowflake
Sync your Roark call analysis into a data warehouse to join it against your own operational data
## Overview
Roark analyzes every call and produces structured metric values (frustration, sentiment, task completion, latency, custom metrics, and more). A common next step is to land that analysis in your own data warehouse so you can join it against your operational data, for example correlating call frustration with downstream business outcomes.
This guide covers the recommended pattern for syncing Roark analysis into **Snowflake** using the tools available today. The same approach works for BigQuery, Redshift, or Databricks: only the load step changes.
Want a fully managed connector or a scheduled file drop into your own bucket? See [Deeper integrations](#deeper-integrations) at the bottom of this page and [reach out](/documentation/resources/support). We are actively expanding warehouse-export options.
***
## The recommended pattern
The pattern is **event-driven pull**: Roark notifies you when a call finishes analysis, you pull the results from the API, land the raw JSON in cloud storage, and load it into Snowflake.
```
Roark webhook ──▶ your handler ──▶ fetch metrics/transcript ──▶ S3 (raw JSON) ──▶ Snowpipe ──▶ Snowflake
```
Roark webhooks are a **notification, not the payload**. When a call is analyzed you receive the `callId`, not the metric values themselves, so your handler makes one follow-up API call to fetch the analysis. This keeps payloads small and lets you pull exactly the fields you need.
Generate a key scoped to the project you want to export. Read endpoints require the `call:read`, `metric:read`, and `transcript:read` permissions. See [API Keys](/documentation/getting-started/api-keys).
Add a webhook endpoint and subscribe to `call.analysis.completed`. See [Webhooks](/documentation/integrations/webhooks).
In your handler, call the metrics (and optionally transcript) endpoints for the `callId` you received.
Write each call's payload to S3 (or GCS / Azure Blob). Store it as raw JSON so schema changes never break ingestion.
Point Snowpipe at the bucket to auto-ingest, then flatten into modeled tables.
***
## What you can export
All data is served from the Customer API at `https://api.roark.ai/v1` and is scoped to the project your API key belongs to.
| Data | Endpoint | Permission |
| ---------------------------------------- | ------------------------------------------------------ | ----------------- |
| List of calls (paginated) | `GET /v1/call` | `call:read` |
| Single call detail | `GET /v1/call/{callId}` | `call:read` |
| Metric values for a call | `GET /v1/call/{callId}/metrics` | `metric:read` |
| Transcript for a call | `GET /v1/call/{callId}/transcript` | `transcript:read` |
| Metric values for a whole collection job | `GET /v1/metric/collection-jobs/{jobId}/metric-values` | `metric:read` |
| Metric definitions (catalog) | `GET /v1/metric/definitions` | `metric:read` |
Chats have the identical set of endpoints under `/v1/chat`.
Every call carries an `externalId` (your own correlation ID, set when the call is ingested) and a `properties` map of custom key-values. These are your join keys back to operational data in your warehouse.
***
## Approach A: Event-driven (recommended for ongoing sync)
Best for keeping the warehouse continuously fresh with low latency.
```python Python (webhook handler) theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
@app.route('/webhooks/roark', methods=['POST'])
def handle_webhook():
payload = request.json
if payload['event'] == 'call.analysis.completed':
call_id = payload['data']['callId']
# Pull the analysis Roark just finished
call = get(f'/v1/call/{call_id}')
metrics = get(f'/v1/call/{call_id}/metrics')
# Land raw JSON in S3, keyed by call id
s3.put_object(
Bucket='my-roark-export',
Key=f'calls/{call_id}.json',
Body=json.dumps({'call': call, 'metrics': metrics}),
)
return '', 200
def get(path):
r = requests.get(
f'https://api.roark.ai{path}',
headers={'Authorization': f'Bearer {ROARK_API_KEY}'},
timeout=10,
)
r.raise_for_status()
return r.json()
```
```typescript TypeScript (webhook handler) theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
app.post('/webhooks/roark', async (req, res) => {
const { event, data } = req.body
if (event === 'call.analysis.completed') {
const callId = data.callId
const call = await get(`/v1/call/${callId}`)
const metrics = await get(`/v1/call/${callId}/metrics`)
await s3.putObject({
Bucket: 'my-roark-export',
Key: `calls/${callId}.json`,
Body: JSON.stringify({ call, metrics }),
})
}
res.sendStatus(200)
})
const get = async (path: string) => {
const r = await fetch(`https://api.roark.ai${path}`, {
headers: { Authorization: `Bearer ${process.env.ROARK_API_KEY}` },
})
if (!r.ok) throw new Error(`${path} -> ${r.status}`)
return r.json()
}
```
***
## Approach B: Scheduled backfill / poll
Best for the initial backfill of historical data, or as a simpler alternative to webhooks. Page through the calls list on a schedule and fetch metrics per call.
The list endpoint uses keyset (cursor) pagination. Sort by `startedAt` and walk the cursor to move forward in time.
```python Python (paginated pull) theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
def export_all_calls():
cursor = None
while True:
params = {'limit': 100, 'sortBy': 'startedAt', 'sortDirection': 'asc'}
if cursor:
params['after'] = cursor
page = get('/v1/call', params=params)
for call in page['data']:
metrics = get(f"/v1/call/{call['id']}/metrics")
write_to_s3(call, metrics)
if not page['hasMore']:
break
cursor = page['nextCursor']
```
```bash cURL (single page) theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -G "https://api.roark.ai/v1/call" \
-H "Authorization: Bearer $ROARK_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "sortBy=startedAt" \
--data-urlencode "sortDirection=asc" \
--data-urlencode "after=$CURSOR"
```
For a large backfill you can also pull metric values a whole job at a time with `GET /v1/metric/collection-jobs/{jobId}/metric-values`, which returns every metric value for that collection job in one paginated stream rather than one request per call.
***
## Loading into Snowflake
Land the raw JSON in an external stage, ingest into a `VARIANT` staging table, then flatten into modeled tables with `LATERAL FLATTEN`. Storing the raw payload first means a new metric or field never breaks your pipeline.
```sql theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
create or replace stage roark_stage
url = 's3://my-roark-export/calls/'
storage_integration = my_s3_integration
file_format = (type = json);
```
```sql theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
create table roark_raw (raw variant, loaded_at timestamp_ntz default current_timestamp());
create or replace pipe roark_pipe auto_ingest = true as
copy into roark_raw (raw) from @roark_stage;
```
```sql theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
create or replace view roark_call_metrics as
select
raw:call:id::string as call_id,
raw:call:externalId::string as external_id, -- your join key
raw:call:startedAt::timestamp as started_at,
raw:call:durationMs::number as duration_ms,
raw:call:callDirection::string as direction,
m.value:slug::string as metric_slug,
m.value:name::string as metric_name,
m.value:type::string as metric_type,
m.value:value::variant as metric_value,
m.value:captureStatus::string as capture_status
from roark_raw,
lateral flatten(input => raw:metrics:data) m;
```
```sql theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
-- Does call frustration predict whether a caregiver takes a shift?
select
f.metric_value::float as frustration,
s.shift_accepted
from roark_call_metrics f
join shifts s
on s.roark_external_id = f.external_id
where f.metric_slug = 'frustration';
```
Model the flattened view once per metric type you care about, or keep one long-format `(call_id, metric_slug, metric_value)` table and pivot in your BI layer. Long format handles new metrics with zero schema changes.
***
## Things to know
* **Idempotency.** Keying S3 objects and Snowflake rows by `callId` (plus the metric collection job id) makes re-delivery and backfills safe to re-run without double counting.
* **Webhook delivery.** Roark retries failed deliveries up to 3 times with backoff. Respond `200` within 5 seconds and process asynchronously. See [Webhooks](/documentation/integrations/webhooks).
* **Date filtering.** The calls list does not yet accept an explicit `from`/`to` date range. Approximate a range by sorting on `startedAt` and walking the cursor until you pass your window.
* **Transcripts** are fetched per call from the transcript endpoint; they are not included in the metrics payload.
* **Rate limits and pagination.** Use `limit=100` and honor `nextCursor`/`hasMore` rather than assuming a fixed page count.
***
## Deeper integrations
The pattern above uses only what is available today. If you need something more managed, we are building toward it and would like to scope it with you:
A Fivetran / Airbyte-style connector with incremental, updated-since cursors so your warehouse stays in sync automatically.
Roark writes incremental Parquet or CSV to your own S3 bucket on a schedule, ready for Snowpipe.
A hands-off Snowflake Secure Data Share so there is nothing to run on your side.
Tell us your volume, latency needs, and the joins you want to run.
***
## Next steps
Set up the analysis-completed notification
Create a read-only key for the export
Full endpoint and parameter reference
Analyze the same data inside Roark
# Google CES
Source: https://docs.roark.ai/documentation/integrations/google-ces
Run chat simulations against Google Customer Engagement Suite apps
Chat simulations Live monitoring Voice simulations
## Overview
The Google CES integration connects a [Google Customer Engagement Suite](https://docs.cloud.google.com/customer-engagement-ai/) app to Roark for [chat simulations](/documentation/simulation-testing/chat-simulations). Roark sends each persona turn to the CES [`runSession` endpoint](https://docs.cloud.google.com/customer-engagement-ai/conversational-agents/ps/reference/rest/v1/projects.locations.apps.sessions/runSession) and records the agent's reply as part of the chat transcript.
Authentication uses [Workload Identity Federation (WIF)](https://docs.cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds) so **no long-lived service account keys ever leave your GCP tenant**. Roark mints a short-lived access token at simulation time using credentials your team controls and can revoke at any time.
***
## Prerequisites
Before getting started, you'll need:
* A Google Cloud project with a published Customer Engagement Suite app
* Permissions in that project to create a Workload Identity Pool and configure a service account with access to your CES app
* The CES app coordinates: **Location**, **App ID**, **Version ID**, **Deployment ID**
***
## Setting Up Workload Identity Federation
WIF setup requires a few values from Roark to authorize in your Workload Identity Pool. **Reach out to us and we'll walk you through it end-to-end**: typically a single back-and-forth to share the values you need, after which you generate the WIF credential JSON on your side and paste it into Roark.
Email **[support@roark.ai](mailto:support@roark.ai)** and we'll get you set up. Include your GCP project ID and CES app coordinates if you have them handy.
Once WIF is configured on your side, you'll have a credential configuration JSON document (`type: "external_account"`) ready to paste into Roark.
***
## Creating the Integration in Roark
Navigate to **Agents**, click **Connect Agent** in the top right, choose **Existing Platform**, and select **Google CES**.
| Field | Description |
| :-------------------- | :---------------------------------------------------------------------------------------------------- |
| **Integration Name** | Friendly name for this integration |
| **WIF Config JSON** | The full external-account credential JSON from your Workload Identity Pool. Stored encrypted at rest. |
| **CES Location** | GCP region for your CES app (e.g. `us`, `global`) |
| **CES App ID** | UUID of the CES app, from the CES console |
| **CES Version ID** | UUID of the app version to run |
| **CES Deployment ID** | UUID of the deployment to target |
On save, Roark validates the WIF document's shape, stores it encrypted, and auto-provisions:
* One **Roark agent** named after the integration
* One **`GOOGLE_CES` chat endpoint** linked to the integration
The endpoint is immediately usable in run plans.
***
## How Authentication Works at Runtime
When a chat simulation runs against a Google CES endpoint, Roark mints a fresh access token for the request rather than storing one:
1. Roark uses your WIF configuration to obtain a federated token from Google STS.
2. The federated token is exchanged for a short-lived OAuth access token that impersonates your configured service account.
3. Roark sends `Authorization: Bearer ` on each call to `…/sessions/{sessionId}:runSession`.
Tokens are short-lived (≈1 hour) and live only in memory for the duration of the simulation. You can revoke access at any time by removing Roark from your Workload Identity Pool.
***
## How Conversations Are Driven
Each simulation generates a unique session ID per run. For every persona turn, Roark POSTs to:
```
POST https://-ces.googleapis.com/v1beta/projects//locations//apps//sessions/:runSession
```
with a body referencing your `app_version` and `deployment` plus the user input text. The agent's reply is read from `outputs[0].text` and added to the chat transcript.
CES creates the session on the first call with a new session ID and resumes it on subsequent calls with the same ID, so no separate bootstrap call is needed.
***
## What's Not Supported
* **Voice simulations**: Google CES integrations are chat-only.
* **Live monitoring / call import**: Roark does not pull historical or live conversations from CES. Use chat simulations to evaluate the agent.
***
## Next Steps
Test your CES app with persona-driven chats
Define the conversations to test
Automate evaluation on every chat
Explore other integrations
# IP Whitelisting
Source: https://docs.roark.ai/documentation/integrations/ip-whitelisting
Whitelist Roark Analytics production IPs for firewalls, webhooks, and presigned URLs
If you need to whitelist Roark Analytics' IP addresses for accessing your infrastructure (for example, presigned URLs or private APIs), use the following static IP addresses.
***
## Production Environment
All outbound requests from Roark Analytics production services originate from these NAT gateway IPs:
* 54.175.209.200
* 54.161.212.170
Important: You must whitelist both IP addresses. Traffic may route through either one depending on availability zone placement.
***
## Use Cases
Whitelist these IPs when:
* Providing presigned URLs for audio files or recordings
* Exposing webhook endpoints with IP restrictions
* Configuring firewall rules for API integrations
* Setting up private API access
***
## Region
These IPs are for the US-EAST-1 region where Roark Analytics production services are hosted.
# Kore AI
Source: https://docs.roark.ai/documentation/integrations/kore
Run chat simulations against Kore AI Agent Platform apps
Chat simulations Live monitoring Voice simulations
## Overview
The Kore AI integration connects an app on the [Kore AI Agent Platform](https://docs.kore.ai/agent-platform/) to Roark for [chat simulations](/documentation/simulation-testing/chat-simulations). Roark talks to your app over the Agent Platform v2 HTTP API, bootstrapping a session, sending each persona turn through `/runs/execute`, and tearing the session down when the conversation ends.
***
## Prerequisites
Before connecting, you'll need from the Kore AI Agent Platform:
* An **App ID** (UUID) for the agent app you want to test
* An **Environment name** the app is deployed to (e.g. `production`)
* An **API key** (`x-api-key`) with permission to create sessions and execute runs against that app
* The **base URL** of your Kore tenant, if you're on a non-default or self-hosted Kore domain. Most customers can leave this blank and Roark will use `https://agent-platform.kore.ai/api/v2`.
See the [Kore AI Agent Platform API reference](https://docs.kore.ai/agent-platform/apis/agentic-apps/overview/) for where to find these in the Kore console.
***
## Creating the Integration in Roark
Navigate to **Agents**, click **Connect Agent** in the top right, choose **Existing Platform**, and select **Kore AI**.
| Field | Description |
| :------------------------ | :----------------------------------------------------------------------------------------------------------------------------- |
| **Integration Name** | Friendly name for this integration |
| **API Key** | Your Kore `x-api-key`. Stored encrypted at rest. |
| **App ID** | UUID of the Kore agent app |
| **Environment** | Environment name the app is deployed to (e.g. `production`) |
| **Base URL** *(optional)* | Override the Kore API base URL for enterprise or self-hosted deployments. Defaults to `https://agent-platform.kore.ai/api/v2`. |
On save, Roark stores the credentials encrypted and auto-provisions:
* One **Roark agent** named after the integration
* One **`KORE` chat endpoint** linked to the integration
The endpoint is immediately usable in run plans. Credentials never leave the integration. The endpoint references them by ID.
In edit mode, leave the **API Key** field blank to keep the existing key. Only fill it in if you want to rotate the credential.
***
## How Conversations Are Driven
Each chat simulation runs through a three-leg session lifecycle against your Kore app:
| Phase | Request |
| :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Bootstrap** | `POST {baseUrl}/apps/{appId}/environments/{env}/sessions`: creates a session and returns a session reference. |
| **Per turn** | `POST {baseUrl}/apps/{appId}/environments/{env}/runs/execute`: sends the persona's message as text input under the bootstrapped session, and reads the agent's reply from `outputs[0].content`. |
| **Teardown** | `POST {baseUrl}/apps/{appId}/environments/{env}/sessions/terminate`: closes the session when the chat ends. |
All requests are sent with your `x-api-key` in the header. The API key is decrypted in memory only for the lifetime of the chat session and is never logged.
***
## What's Not Supported
* **Voice simulations**: Kore AI integrations are chat-only in Roark.
* **Live monitoring / call import**: Roark does not pull historical conversations from Kore. Chat simulations are the way to evaluate your Kore app's behavior in Roark.
***
## Next Steps
Test your Kore app with persona-driven chats
Define the conversations to test
Automate evaluation on every chat
Explore other integrations
# Leaping
Source: https://docs.roark.ai/documentation/integrations/leaping
Sync Leaping AI agents and analyze voice conversations
Live monitoring Voice simulations Chat simulations
## Overview
The Leaping integration syncs your Leaping AI agents and their calls into Roark. Agents, prompts, and tools are imported automatically, and calls are pulled on a recurring schedule so every conversation is analyzed.
***
## Prerequisites
Before setting up the integration, ensure you have:
* A Leaping AI account with active agents
* A Leaping API key (found in your Leaping dashboard)
* At least one configured agent in Leaping
***
## Setup Instructions
### Step 1: Create Integration
1. Navigate to **Agents** in your Roark dashboard
2. Click **Connect Agent** in the top right, choose **Existing Platform**, and select **Leaping**
3. Enter your configuration:
| Field | Description |
| :------------------- | :--------------------------------- |
| **Integration Name** | Friendly name for this integration |
| **API Key** | Your Leaping API key |
The system will validate your API key and fetch available agents.
### Step 2: Configure Historical Sync
Choose how far back to import existing calls:
* **Default**: Last 90 days of call history
* **Custom**: Select a specific date range
* **Skip**: Only sync new calls going forward
Historical sync runs as a backfill job during initial setup. Ongoing calls are synced automatically on a recurring schedule.
### Step 3: Select Agents
Choose which Leaping agents to monitor:
* **All Agents**: Sync calls from every agent in your account
* **Selected Agents**: Choose specific agents to monitor
The agent selector shows each agent's name and ID.
### Step 4: Activate Integration
Review your settings and click **Create Integration** to begin syncing.
***
## What Gets Synced
Leaping integrations sync comprehensive data:
* **Calls**: Conversation transcripts, metadata, and call events
* **Agents**: Agent configurations and endpoints
* **Prompts**: System prompts configured for each agent
* **Tools**: Tool definitions available to agents
* **Call Metadata**: Duration, status, properties, and events
***
## How Sync Works
Leaping uses **pull-based synchronization**: Roark periodically fetches new calls from the Leaping API rather than receiving webhooks.
| Phase | What Happens |
| :--------------- | :------------------------------------------------------------------------------- |
| **Agent Sync** | Fetches agents and endpoints, updates configurations, syncs prompts and tools |
| **Health Check** | Verifies API connectivity and agent availability |
| **Call Sync** | Fetches calls since last sync for each agent, processes transcripts and metadata |
Calls with status `completed`, `transferred`, or `dropped` are synced.
***
## Monitoring Integration Health
Track your integration status in the Roark dashboard:
* **Syncing**: Actively importing calls
* **Active**: Running on schedule, pulling new calls periodically
* **Paused**: Sync temporarily halted
* **Error**: Configuration or connection issues
View sync statistics including total calls synced, last sync timestamp, and job history.
***
## Next Steps
Test synced agents with simulations
Browse and analyze synced calls
Automate metric collection on calls
Explore other integrations
# LiveKit Cloud
Source: https://docs.roark.ai/documentation/integrations/livekit
Connect LiveKit Cloud for real-time voice communication monitoring
Live monitoring Voice simulations Chat simulations
## Overview
The LiveKit Cloud integration enables real-time monitoring of voice communications built on [LiveKit Cloud](https://cloud.livekit.io/). Roark connects to your LiveKit project over webhooks and the server API (no changes to your agent code) and streams call data in for analysis, evaluation, and quality monitoring.
Running your own `livekit-server` instead of LiveKit Cloud? Webhooks and the dashboard-managed flow described here still work, but the recommended path for self-hosted deployments is the drop-in Python SDK. See [LiveKit (self-hosted)](/documentation/integrations/livekit-self-hosted).
***
## Prerequisites
Before setting up the integration, ensure you have:
* A [LiveKit Cloud](https://cloud.livekit.io/) account and project
* LiveKit API credentials (API Key and Secret)
* Admin access to configure webhooks in LiveKit
***
## Setup Instructions
### Step 1: Create Integration
1. Navigate to **Agents** in your Roark dashboard
2. Click **Connect Agent** in the top right, choose **Existing Platform**, and select **LiveKit**
3. Enter the following configuration:
| Field | Description | Example |
| :------------------- | :--------------------------------- | :---------------------------- |
| **Integration Name** | Friendly name for this integration | Production LiveKit |
| **Server URL** | Your LiveKit WebSocket URL | `wss://example.livekit.cloud` |
| **API Key** | LiveKit API key | `APIxxxxxxxxxxxxx` |
| **API Secret** | LiveKit API secret | Keep this secure |
### Step 2: Configure Webhook
After creating the integration, Roark provides a webhook URL:
1. Copy the webhook URL from the integration settings
2. Navigate to your LiveKit dashboard
3. Go to **Settings → Webhooks**
4. Add the Roark webhook URL
5. Select the following events:
* `room_started`
* `room_finished`
* `participant_joined`
* `egress_ended`
### Step 3: Configure Agents
Under the **Agents** section of the integration, decide how LiveKit calls map to Roark agents:
* **Import named LiveKit agents**: Roark fetches the named agents configured in your LiveKit project so you can choose which ones to track. Each imported agent becomes its own Roark agent, and calls are routed based on the LiveKit agent name that participated in the room.
* **Assign all calls to a single Roark agent**: Pick one Roark agent and every call from this integration is attributed to it. Use this when you rely on LiveKit's automatic dispatch (unnamed agents) or when you don't care about distinguishing multiple agents.
You can switch between these modes later, but existing calls will keep the agent attribution they had at ingest time.
### Step 4: WebRTC Endpoints (optional)
The **WebRTC** section of the integration page is where you manage all LiveKit WebRTC endpoints for this integration. On first setup it can auto-create one endpoint per imported agent so you can start running simulations immediately; after the integration exists you can come back to this same section at any time to add, edit, or remove endpoints. See [WebRTC Endpoints](#webrtc-endpoints) below for the full list of settings. You can skip this step and add endpoints later if you prefer.
### Step 5: Configure Metric Collectors
Calls synced from LiveKit are scored by your [metric collectors](/documentation/metrics/metric-collectors). Set them up globally under **Metrics → Collectors**, or scope collectors to a specific agent from that agent's **Collectors** section.
### Step 6: Activate Integration
Toggle the integration status to **Active** to begin receiving call data.
***
## WebRTC Endpoints
Roark can run simulations directly over WebRTC against a LiveKit room instead of placing a phone call. This is the recommended path for LiveKit agents because it avoids SIP trunking and exercises the exact transport your production users connect over.
### Managing WebRTC Endpoints
All LiveKit WebRTC endpoints for an integration are managed from the **WebRTC** section of the integration page:
1. Go to **Agents**, expand the connected sources at the top of the page, and choose your **LiveKit** source. The integration form opens on the right.
2. Expand the **WebRTC** section to see every endpoint attached to an agent that belongs to this integration. Each row shows the agent name and environment.
3. Click an existing endpoint to edit it, or click **Add WebRTC endpoint** to create a new one. The add flow opens the same editor used on the agent page and is scoped to agents managed by this integration.
On initial integration setup, Roark can also auto-create one WebRTC endpoint per imported agent in a single step. See [Step 4: WebRTC Endpoints](#step-4-webrtc-endpoints-optional) above. After the integration exists, use the flow described here for every change.
### Room Management Mode
When configuring the endpoint you pick who is responsible for creating the LiveKit room that a simulation runs in.
Roark creates the room in your LiveKit project at the start of each simulation using the API credentials from the integration, then joins the room as the simulated caller.
* **Agent dispatch**: If a **Named LiveKit Agent** is set on the integration, Roark explicitly dispatches that agent into the room so it joins as soon as the room is created. If no named agent is set, Roark relies on LiveKit's [automatic dispatch](https://docs.livekit.io/agents/worker/agent-dispatch/) behavior: any unnamed worker connected to your LiveKit project will pick up the room automatically.
* **Room metadata**: Optionally provide a JSON string that Roark attaches to the room it creates. Use this to pass context to your agent (for example, a customer ID, scenario tag, or feature flag) that your worker can read from the room metadata on join.
This mode is the easiest to set up and works for most agents that use LiveKit's standard dispatch model.
Provide an HTTPS URL that Roark calls when a simulation starts. Your endpoint is responsible for creating the LiveKit room, dispatching your agent into it, and returning the room name so Roark can join.
**Request**: Roark sends an HTTPS request to your URL when a simulation starts.
**Expected response**: Return a JSON body containing the `roomName` of the LiveKit room you created. Roark then connects to the same LiveKit server and joins that room as the simulated caller.
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"roomName": "sim-session-abc123"
}
```
Use this mode when you need custom room setup logic: for example, if you provision rooms through your own backend, need to wire up per-session tokens or tools, or run a bespoke dispatch flow that Roark's auto-create path can't express.
Once the endpoint exists, use it like any other agent endpoint when building [run plans](/documentation/simulation-testing/run-plans) and [running simulations](/documentation/simulation-testing/running-simulations).
***
## What Gets Synced
LiveKit integrations sync the following data:
* **Call Events** - Room creation, participant joining, call completion
* **Recording Files** - MP4 recordings from egress events
* **Call Metadata** - Duration, participant count, room configuration
* **Transcripts** - When speech-to-text is enabled
* **Named Agents** - When agent import is enabled in the [Agents](#step-3-configure-agents) step, the list of named LiveKit agents from your project
LiveKit does not sync agent prompts or worker source code, as it's a real-time
communication platform rather than an AI agent provider. Only agent names are
imported so Roark can attribute calls to the correct agent.
***
## Webhook Events
The integration processes these LiveKit events:
| Event | Description | Roark Action |
| :------------------- | :-------------- | :-------------------------------- |
| `room_started` | Room created | Initialize call record |
| `room_finished` | Room ended | Finalize call, trigger evaluation |
| `participant_joined` | User joined | Update participant list |
| `egress_ended` | Recording ready | Process recording file |
***
## Call Sampling & Metadata Filtering
Roark provides two ways to control which LiveKit calls are processed:
### Random Sampling
Set a **sampling rate** (0–100%) in the integration settings to randomly sample a percentage of calls. This is useful when you want to evaluate a representative subset without any code changes.
### Metadata Filtering (Per-Call Control)
For more granular control, set `roark.skip` to `true` in your LiveKit room metadata to tell Roark to skip processing for specific calls.
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
from livekit import api
room = api.CreateRoomRequest(
name="my-room",
metadata='{"roark.skip": true}'
)
```
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import { RoomServiceClient } from 'livekit-server-sdk';
const roomService = new RoomServiceClient(url, apiKey, apiSecret);
await roomService.createRoom({
name: 'my-room',
metadata: JSON.stringify({ 'roark.skip': true }),
});
```
**How it works:**
* Calls **without** `roark.skip` in metadata are processed normally (safe default)
* Calls **with** `roark.skip` set to `true` are skipped entirely: no recording, no evaluation, no processing
* The check happens at the webhook level, so skipped calls use zero Roark resources
* You can combine metadata filtering with sampling: sampling is applied first, then metadata filtering
Metadata filtering is ideal when you want deterministic control over which calls are processed, such as skipping internal test calls or only evaluating calls for specific customers.
***
## Monitoring Integration Health
Check your integration status in the Roark dashboard:
* **Active** 🟢 - Receiving and processing events
* **Inactive** 🔴 - Integration paused
* **Error** ⚠️ - Configuration or connection issues
View recent webhook deliveries and any error messages in the integration details.
***
## Next Steps
Send OpenTelemetry traces from your LiveKit agent
Define metrics and pass/fail thresholds
Monitor calls in real-time
Learn about all integrations
Instrument your own livekit-server with the Python SDK
# LiveKit (self-hosted)
Source: https://docs.roark.ai/documentation/integrations/livekit-self-hosted
Instrument a self-hosted LiveKit Agents worker with the roark-analytics[livekit] SDK
Live monitoring Voice simulations Chat simulations
## Overview
The self-hosted LiveKit integration monitors voice agents built on the open-source [LiveKit Agents](https://docs.livekit.io/agents/) framework that run against your own LiveKit server. Drop the [`roark-analytics[livekit]`](https://pypi.org/project/roark-analytics/) helper into your agent entrypoint and call lifecycle, transcripts, tool calls, and a recording are forwarded to Roark automatically. No other code changes required.
The SDK talks only to Roark's own API; it never connects to your LiveKit server.
Using [LiveKit Cloud](https://cloud.livekit.io/)? You can use this SDK there too, but the dashboard-managed webhook integration is usually simpler. See [LiveKit Cloud](/documentation/integrations/livekit).
***
## Prerequisites
* A LiveKit Agents worker (Python 3.10+, `livekit-agents` 1.x)
* Your own LiveKit server
* A Roark API key with **WRITE** scope ([generate one](/documentation/getting-started/api-keys))
***
## Setup Instructions
### Step 1: Install the SDK
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
pip install "roark-analytics[livekit]"
```
### Step 2: Configure your API key
The only setting you need to provide is your Roark API key.
```bash title=".env" theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# Roark API key: create one on the API keys page in your Roark project.
ROARK_API_KEY=rk_live_replace_me
```
| Variable | Required | Purpose |
| :---------------------------- | :------- | :--------------------------------------------------------------------------------- |
| `ROARK_API_KEY` | Yes | Roark API key with WRITE scope. Pass it to the helper as `api_key=`. |
| `ROARK_OBSERVABILITY_ENABLED` | No | Set to `false` to make `observe_session` a no-op. See [Kill switch](#kill-switch). |
### Step 3: Wire `observe_session` into your entrypoint
Call `observe_session(...)` **before `session.start()`** so the audio taps are installed before the session begins streaming frames. That single call wires up everything:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import os
from livekit.agents import Agent, AgentSession, JobContext, WorkerOptions, cli
from livekit.plugins import cartesia, openai, silero, speechmatics
from roark_analytics_python_livekit import observe_session
SYSTEM_PROMPT = "You are a friendly voice assistant. Keep replies short. They are spoken aloud."
class Assistant(Agent):
def __init__(self) -> None:
super().__init__(instructions=SYSTEM_PROMPT)
async def entrypoint(ctx: JobContext) -> None:
# Connect first: Roark keys the call on the LiveKit room sid (RM_…),
# which is also what links OpenTelemetry traces to the call.
await ctx.connect()
session = AgentSession(
stt=speechmatics.STT(),
llm=openai.LLM(model="gpt-4o-mini"),
tts=cartesia.TTS(),
vad=silero.VAD.load(),
)
# --- Roark analytics ---------------------------------------------------
# Must be called BEFORE session.start(). Failures are logged and
# swallowed: your agent keeps running even if Roark is unreachable.
await observe_session(
ctx,
session,
api_key=os.environ["ROARK_API_KEY"],
agent_id="support-bot-v1",
agent_name="Support Bot",
agent_prompt=SYSTEM_PROMPT,
)
# -----------------------------------------------------------------------
await session.start(room=ctx.room, agent=Assistant())
if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
```
That's the full integration: agent registration, transcripts, tool calls, and the recording are all handled by the helper.
| Argument | Required | Description |
| :------------- | :------- | :----------------------------------------------------------------------------------------- |
| `ctx` | Yes | The `JobContext` passed to your agent entrypoint. |
| `session` | Yes | The `AgentSession` you're about to `start()`. |
| `api_key` | Yes | Your Roark API key (WRITE scope). |
| `agent_id` | Yes | Stable, customer-defined agent identifier. Used for lazy registration. |
| `agent_name` | No | Display name shown in the Roark dashboard. |
| `agent_prompt` | No | System prompt: persisted as the agent's prompt revision, so changes are tracked over time. |
| `**metadata` | No | Free-form metadata forwarded on `call-started` for Roark-side correlation. |
### Step 4: Verify the connection
Run your worker against your LiveKit server and place a test call:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
python agent.py dev
```
As the call runs you should see Roark log lines, then the recording upload begin after a few seconds of audio:
```
tapping user audio input (...)
tapping agent audio output (...)
call-started: ...
```
Within a few seconds you should also see, in your Roark dashboard:
1. The agent appear under the **LiveKit** source filter
2. The call appear in the calls table with status `In Progress`
3. After the call ends, the transcript, tool invocations, and recording attached to the call
***
## How It Works
`observe_session` subscribes to the standard `AgentSession` event surface and ships a compact event timeline to Roark:
| Phase | Source | What's captured |
| :---------------- | :-------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| **Session start** | `JobContext.connect()` | `call-started`, keyed on the LiveKit room sid (`RM_…`). Agent is lazy-registered the first time Roark sees an `agent_id`. |
| **Transcripts** | `conversation_item_added` | Message role + content, for both user and assistant turns. |
| **Tool calls** | `function_tools_executed` | Paired `tool_call` / `tool_result` records, keyed by `tool_call_id`. |
| **Recording** | Taps on `session.input.audio` (user) + `session.output.audio` (agent) | Audio streamed to Roark during the call. |
| **Session end** | `ctx.add_shutdown_callback(...)` | Flushes pending state, drains in-flight uploads, POSTs `call-ended`. |
Failures are logged and swallowed: **the helper never raises into your session**. Your agent keeps running even if Roark is unreachable.
***
## What Gets Synced
* **Calls**: Lifecycle with timing and end reason
* **Agents**: Lazy-registered on first sight using the `agent_id` / `agent_name` you pass
* **Prompts**: System prompt captured at call start as a prompt revision
* **Transcripts**: Per-turn messages with role, content, and timestamp
* **Tool Invocations**: Tool call IDs, names, JSON arguments, and results
* **Recordings**: Audio recording of the call
Roark only sees what the helper forwards. If you remove `observe_session` from a worker (or set `ROARK_OBSERVABILITY_ENABLED=false`), no data flows for those calls.
***
## Agent Management
LiveKit agents are **lazy-registered** the first time the helper reports them:
* The first `call-started` event with a new `agent_id` creates the agent in Roark
* Subsequent events update the agent's name and prompt if they change
* These agents appear on the agents page under the **LiveKit** source filter
Once an agent exists, it can be used in [simulations](/documentation/simulation-testing/running-simulations), [run plans](/documentation/simulation-testing/run-plans), and [agent reports](/documentation/observability/reports) just like agents from any other provider.
***
## Kill Switch
Disable instrumentation at runtime without touching code. Set `ROARK_OBSERVABILITY_ENABLED=false` to make `observe_session` a no-op (it returns `None`). Values treated as off: `false`, `0`, `no`, `off` (case-insensitive); anything else (or the variable being absent) keeps it enabled.
***
## Troubleshooting
The helper registers a shutdown callback on the `JobContext`, which fires when LiveKit ends the job. If your transport tears down without firing the hook, call `await state.aflush(reason="...")` explicitly from your own disconnect handler: `aflush()` is idempotent, so the normal shutdown path will no-op if both fire.
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
state = await observe_session(ctx, session, api_key=..., agent_id=...)
# ... later, from your disconnect handler:
await state.aflush(reason="client-disconnected")
```
Transcripts come from the `conversation_item_added` event on `AgentSession`. If you use a custom pipeline that bypasses `AgentSession.start(room=…, agent=…)`, that event may never fire. Verify by adding your own `session.on("conversation_item_added", print)` listener.
The user side is tapped from `session.input.audio`; the agent side from `session.output.audio`. Because the taps are installed by `observe_session`, it **must be called before `session.start()`**. Check worker logs for the `tapping user audio input` / `tapping agent audio output` lines. If one is missing, that side will be silent on the merged recording.
Confirm `api_key` is a WRITE-scope key and that `ROARK_OBSERVABILITY_ENABLED` is not set to a falsy value.
***
## Next Steps
Send OpenTelemetry traces: they auto-link to the call via the room sid
Test your LiveKit agents
Set up evaluation criteria
Use the dashboard-managed webhook integration instead
# Okta SSO + SCIM
Source: https://docs.roark.ai/documentation/integrations/okta
Sign your team into Roark with Okta, and optionally push-provision and deprovision users via SCIM
## What this gets you
Connecting Okta to Roark lets your team sign in with their Okta credentials. New users are auto-created in Roark on first sign-in (just-in-time provisioning), so you don't have to invite them individually. If you also enable SCIM, Okta becomes the source of truth for user lifecycle: assigning a user pushes them into Roark before their first login, profile updates flow through automatically, and deactivating the user in Okta disables their Roark account.
***
## How it works
SAML and SCIM are two independent channels. You can run SAML-only (JIT provisioning) and add SCIM later, or run both from day one.
* **SAML** authenticates the user. Okta posts the SAML response to Roark's Cognito User Pool, which establishes the session.
* **SCIM** is a separate, outbound channel from Okta to Roark for user lifecycle events (create, update, deactivate).
Roark verifies you own the email domain via a DNS TXT record before activating the SSO config, preventing accidental or malicious claims on a domain you don't control.
***
## Before you start
* You are an **OWNER** of your Roark organization.
* You have admin access to your Okta tenant.
* You can publish a DNS TXT record at the apex of your email domain.
* You know which email domain your team uses (e.g., `acmecorp.com`). One domain per Roark org.
***
## Step 1: Create the SAML app in Okta
In your Roark dashboard, open **Org Settings → Single Sign-On**. The page shows two values you'll paste into Okta: the **ACS URL** and the **SP Entity ID**. Keep that tab open.
In the Okta Admin Console:
1. Go to **Applications → Applications → Create App Integration**.
2. Choose **SAML 2.0** and click **Next**.
3. Give the app a name (e.g., "Roark") and continue.
4. On the SAML Settings screen, fill in:
| Field | Value |
| :-------------------------- | :----------------------------- |
| Single sign-on URL (ACS) | (copy from Roark SSO settings) |
| Audience URI (SP Entity ID) | (copy from Roark SSO settings) |
| Name ID format | `EmailAddress` |
| Application username | `Email` |
5. Add three **Attribute Statements**. Names are **case-sensitive**, type them exactly:
| Name | Name format | Value |
| :------------ | :---------- | :--------------- |
| `email` | Basic | `user.email` |
| `given_name` | Basic | `user.firstName` |
| `family_name` | Basic | `user.lastName` |
6. Finish the wizard. On the resulting app's **Sign On** tab, find **Identity Provider metadata** and copy the URL.
***
## Step 2: Enable SAML in Roark
Back in **Org Settings → Single Sign-On**:
1. Paste the IdP metadata URL.
2. Enter your email domain (e.g., `acmecorp.com`).
3. Click **Enable SSO**.
Roark validates the metadata, registers the IdP with Cognito, and switches the settings page into the **pending verification** state with your ACS URL, Entity ID, and a DNS TXT record envelope displayed.
Email addresses must match. If a user already has a Roark account under a different email than what Okta will send, you'll end up with duplicates. Reconcile email addresses in Okta before assigning users.
***
## Step 3: Verify your email domain
After you enable SSO, Roark issues a one-time verification token and shows a DNS TXT record envelope on the settings page. Until you publish the record and Roark confirms it, the SSO config is inert: sign-ins for the domain fall through to the non-SSO path and any SCIM token you generate will not authenticate.
Copy the values from the settings page into your DNS provider:
| Field | Value |
| :---- | :-------------------------------------------- |
| Type | `TXT` |
| Name | your email-domain apex (e.g., `acmecorp.com`) |
| Value | `roark-domain-verification=` |
1. Add the record at the **apex** of your email domain, not a subdomain like `_roark.acmecorp.com`. It coexists with existing TXT records on the apex (SPF, DKIM, Google site verification, and so on).
2. Wait for propagation. This is typically a few minutes, but can take up to an hour depending on your provider's TTL.
3. Roark polls automatically in the background once the token is issued, so most admins won't need to click anything. Leave the settings page open for a few minutes after the DNS change propagates. There's a **Verify now** button for an explicit check.
The token is valid for **7 days**. If it expires before you publish the record, click **Regenerate** to issue a fresh one. Regenerating invalidates any prior token, and if the row was already verified, clears that verified state as well (the new token becomes a re-attest).
Until the domain is verified, the SSO config does not activate. Sign-ins for the domain are not routed through your IdP and SCIM bearer tokens will not authenticate. The settings page surfaces a **Pending verification** badge.
If another Roark org has already verified the same email domain, your verify call returns `dns_record_mismatch`. Roark won't tell you which org owns it. [Contact support](/documentation/resources/support).
***
## Step 4: Assign users and test
1. In Okta, open the app's **Assignments** tab and assign at least one test user.
2. Have that user go to [roark.ai/login](https://roark.ai/login), enter their work email, and submit.
3. Roark detects SSO for the domain and redirects to Okta. After authenticating, the user lands in Roark, auto-created and joined to your org if they didn't already exist.
If sign-in fails, jump to [Troubleshooting](#troubleshooting).
***
## Step 5 (optional): Turn on SCIM
SCIM lets Okta push user lifecycle events into Roark, useful for larger teams and required if you want Okta to deactivate Roark accounts automatically.
1. In Roark, on **Org Settings → Single Sign-On → Okta**, click **Generate SCIM token**. Copy both the **base URL** and the **bearer token**. The token is shown once.
2) In Okta, open the same SAML app and go to the **Provisioning** tab. Click **Configure API Integration**.
3. Check **Enable API integration**, paste the SCIM base URL and bearer token, and click **Test API Credentials**. Save.
4. Under **Provisioning → To App**, enable:
* **Create Users**
* **Update User Attributes**
* **Deactivate Users**
**If you already have Roark users**, run **Provisioning → To App → Import Users from App** *before* assigning anyone in Okta. Okta calls Roark's SCIM endpoint, matches existing users by email, and records the link. Skipping this step still works (Roark matches by email on the first SCIM `POST` and returns the existing user), but importing first gives Okta a clean linked state and avoids confusing "User already exists" warnings in the Okta UI.
5. Assign users (or groups) to the app. Okta will create or link Roark accounts on assignment.
***
## Auto-join projects (optional)
By default, new SSO and SCIM-provisioned users are added to your organization but no projects. To onboard them into a default set of projects automatically:
1. Go to **Org Settings → Single Sign-On → Auto-join projects**.
2. Pick the projects new users should land in.
3. Save.
From then on, every user provisioned via JIT or SCIM is auto-added to those projects. Existing users are unaffected.
***
## What happens for existing Roark users
Account linking only takes effect once the domain is verified. Until then, SCIM `POST /Users` is rejected (the token doesn't authenticate) and SAML sign-ins fall through to the non-SSO path.
Roark links existing accounts (password or Google) to Okta by email: no duplicates, no data loss. The user keeps their user ID, history, and project memberships.
* **JIT path:** linking happens on first SSO sign-in. Roark finds the user by email and merges Okta into their identity providers.
* **SCIM path:** when Okta sends `POST /Users` for an existing user, Roark responds `200` (link) instead of `201` (create) and records the Okta `externalId`. The user does not need to sign in for this to take effect.
If the email belongs to a different active Roark org, SCIM responds `409 (uniqueness)`. To resolve, remove the user from the other org and retry, or [contact support](/documentation/resources/support).
***
## Deprovisioning
* **Unassign in Okta (SAML only):** the user can't start a new session. Existing sessions expire within \~1 hour.
* **SCIM `PATCH active=false`:** the user is marked `INACTIVE`, their Cognito record is disabled, and their project memberships are removed. If you later re-activate them in Okta, their org membership and prior project memberships are restored.
***
## Disabling SSO
From **Org Settings → Single Sign-On**, click **Disable SSO**. Roark removes the IdP from Cognito. Active sessions keep working until the token expires (within an hour). Users with a linked password or Google identity can still sign in that way; SSO-only users will be locked out until you re-enable.
Disabling SSO does not clear domain verification. If you re-enable with the same email domain later, the prior verification still stands. If you re-enable with a different email domain, you'll need to verify the new one.
***
## Troubleshooting
* **"Invalid SAML response received"**: Cognito couldn't fetch your IdP metadata. Confirm the metadata URL is publicly reachable and returns valid XML.
* **User signs in but is treated as new**: the email in the Okta profile doesn't match the one on the existing Roark account. Update the Okta user's email to match, or contact support to relink.
* **"Email domain is already claimed"**: another Roark org has already enabled SSO for this domain. Contact support.
* **SSO check times out at login**: Roark couldn't look up the SSO provider for that domain. Check your network/firewall to roark.ai and confirm the email domain exactly matches what you configured.
Domain verification returns one of the following results. The settings page surfaces these on every **Verify now** click; transient misses during background polling stay silent.
* **`dns_record_not_found`**: no TXT record at the apex carries the `roark-domain-verification=` prefix. Confirm you added the record at the apex (not `_roark.acmecorp.com`) and allow time for DNS propagation, typically minutes, sometimes up to an hour depending on your provider's TTL.
* **`dns_record_mismatch`**: a record with the `roark-domain-verification=` prefix exists, but its token doesn't match the one Roark issued. Two common causes: you regenerated the token after pasting the old value (paste the new one), or another organization is concurrently attempting to claim the same domain (only one will win: [contact support](/documentation/resources/support) if you believe this is your domain).
* **`token_expired`**: the issued token is past its 7-day TTL. Click **Regenerate** and paste the fresh value.
* **`dns_lookup_failed`**: DNS resolution itself errored. Usually transient; retry. If it persists, confirm the domain resolves at all (`dig TXT acmecorp.com`).
* **`verified`**: success. The SSO config is now active end-to-end; SAML sign-ins and SCIM provisioning will route.
***
## Limitations
* IdP-initiated SAML Single Logout is not supported. Logging out of Okta does not end Roark sessions; existing sessions expire on their own within \~1 hour.
* One email domain per Roark org. If your team uses multiple domains, [reach out](/documentation/resources/support).
* Wildcard or sub-domain claims are not supported. Verification and the email-domain match are exact at the apex only.
* SCIM `GET /Users` only honors the `userName eq ""` filter. Okta only uses this form in practice; other filter expressions (`userName co`, `active eq true`, etc.) are ignored.
***
## Next steps
Allow Roark's outbound IPs through your firewall
Get help with Okta setup
# Overview
Source: https://docs.roark.ai/documentation/integrations/overview
Every platform Roark connects to, and exactly which capabilities each one supports
Roark connects to your agent platform in two directions: it **pulls production conversations in** for monitoring and scoring, and it **drives simulated conversations out** for testing. Which of those you get depends on the platform.
This page is the map. Start with the capability matrix, then jump to the setup guide for your platform.
***
## Capability Matrix
Supported out of the box · Possible with extra setup · Not supported
| Platform | Import calls | Import chats | Sync agents | Telephony sims | WebRTC sims | Chat sims | Traces |
| :--------------------------------------------------------------------------- | :-------------------: | :-------------------: | :---------------------------: | :---------------------------: | :-------------------: | :-------------------: | :---------------------------: |
| [**VAPI**](/documentation/integrations/vapi) | | | | | | | |
| [**Pipecat**](/documentation/integrations/pipecat) | | | | | | | |
| [**Retell AI**](/documentation/integrations/retell) | | | | | | | |
| [**ElevenLabs**](/documentation/integrations/elevenlabs) | | | | | | | |
| [**Bland AI**](/documentation/integrations/bland) | | | | | | | |
| [**Leaping**](/documentation/integrations/leaping) | | | | | | | |
| [**LiveKit Cloud**](/documentation/integrations/livekit) | | | | | | | |
| [**LiveKit (self-hosted)**](/documentation/integrations/livekit-self-hosted) | | | | | | | |
| [**Google CES**](/documentation/integrations/google-ces) | | | | | | | |
| [**Kore AI**](/documentation/integrations/kore) | | | | | | | |
| [**Custom / API**](/documentation/integrations/custom-integrations) | | | | | | | |
### Notes on specific cells
LiveKit is a real-time transport, not an agent framework, so it doesn't expose prompts or worker source code. Roark imports the **named agents** configured in your LiveKit project purely so calls can be attributed to the right agent. If you want prompts and tool definitions in Roark, use the [self-hosted SDK integration](/documentation/integrations/livekit-self-hosted), which captures the system prompt as a tracked prompt revision.
Roark acts as an OpenTelemetry collector at `https://api.roark.ai/v1/traces`, so **any** platform can send traces over OTLP/HTTPS. Step-by-step guides exist for [VAPI](/documentation/observability/traces#vapi), [LiveKit](/documentation/observability/traces#livekit), and [custom integrations](/documentation/observability/traces#custom-integration). Platforms marked work through the custom-integration path: you instrument your own code and correlate spans to the call yourself.
No provider currently pushes production **chat** transcripts into Roark. Text conversations reach Roark either from a [chat simulation](/documentation/simulation-testing/chat-simulations) or by posting them to the [`/v1/chat` API](/api-reference/introduction) yourself, which is why **Custom / API** is the only row with chat import.
A WebRTC endpoint can be attached to any Roark agent whose underlying agent speaks **LiveKit** or **SmallWebRTC**. The matrix marks the platforms where that's the native, documented path. If your VAPI or ElevenLabs agent is fronted by one of those transports, you can wire up a WebRTC endpoint for it too.
***
## What Each Capability Means
| Capability | What you get |
| :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Import calls** | Production voice calls flow into Roark with audio, transcript, tool invocations, and metadata, then get scored automatically by your [metric collectors](/documentation/metrics/metric-collectors). |
| **Import chats** | Production text conversations land as first-class [chat records](/documentation/simulation-testing/chat-simulations), evaluated the same way calls are. |
| **Sync agents** | Agent configurations, system prompts, phone numbers, and tool definitions import automatically and stay up to date. Prompt changes are tracked as revisions. |
| **Telephony sims** | Roark dials your agent over a real PSTN call, or answers when your agent dials Roark. See [inbound vs. outbound](/documentation/simulation-testing/inbound-vs-outbound). |
| **WebRTC sims** | Roark joins your agent over [WebRTC](/documentation/simulation-testing/webrtc) instead of a phone line: no SIP, no telephony minutes, same transport as your web and mobile users. |
| **Chat sims** | Roark drives persona-based [text conversations](/documentation/simulation-testing/chat-simulations) against your agent over its chat transport. |
| **Traces** | OpenTelemetry spans attach to the matching call so you can debug latency, LLM calls, and tool failures on the [call detail page](/documentation/observability/traces). |
***
## Browse Integrations
### Voice agent platforms
Sync assistants and stream calls in over webhooks
Sync agents and analyze conversations
Voice **and** chat, the only platform that does both
Monitor Bland phone agents via pull-based sync
Sync Leaping AI agents, prompts, and tools
### Real-time frameworks & self-hosted
Dashboard-managed webhook integration, plus WebRTC endpoints
Drop `observe_session` into your own Agents worker
Drop the Roark observer into your Pipecat pipeline, self-hosted or on Pipecat Cloud
### Chat agent platforms
Chat simulations against Customer Engagement Suite apps
Chat simulations against Kore Agent Platform apps
Any platform: send calls and chats with the API or SDKs
Don't see your platform? [Custom integrations](/documentation/integrations/custom-integrations) cover every capability in the matrix. Send an audio file or a message transcript and Roark handles the rest.
***
## Simulation Transports
Whether a simulation runs as a call or a chat is decided by the **endpoint type** on the agent target. You don't pick a modality separately.
| Transport | Endpoint type | Modality | Best for |
| :------------------- | :-------------------------------- | :------- | :--------------------------------------------------------------------------------------------------- |
| **Telephony (PSTN)** | Phone | Voice | Any agent reachable at a phone number (the default for VAPI, Retell, ElevenLabs, Bland, and Leaping) |
| **LiveKit** | LiveKit | Voice | Agents running in a LiveKit room, cloud or self-hosted |
| **SmallWebRTC** | SmallWebRTC | Voice | Pipecat agents using peer-to-peer WebRTC with no media server |
| **WebSocket** | WebSocket | Chat | Custom text agents you expose yourself |
| **Provider chat** | ElevenLabs WS · Google CES · Kore | Chat | Auto-created when you connect the integration, nothing to wire up |
ElevenLabs WS endpoints resolve to **chat**, not voice. To test an ElevenLabs agent over audio, use a Phone endpoint instead.
***
## How Integrations Work
Pick your platform from the cards above and open its setup guide.
Connect with an API key, OAuth, or (for self-hosted frameworks) a Roark API key dropped into your worker.
Select which agents to track and how far back to backfill historical calls.
Calls flow in and get scored automatically. Synced agents become targets you can point [run plans](/documentation/simulation-testing/run-plans) at.
Data reaches Roark one of three ways, depending on the platform: **webhooks** (VAPI, Retell, LiveKit Cloud), **scheduled pull** (ElevenLabs, Bland, Leaping), or a **drop-in SDK** you add to your own code (LiveKit self-hosted, Pipecat, custom).
***
## One Integration, Many Agents
A single integration syncs every agent in the connected account:
```
VAPI Integration
├── Customer Support Agent
├── Sales Qualification Agent
├── Appointment Booking Agent
└── Survey Collection Agent
```
* **Single setup**: configure once, sync all agents
* **Centralized management**: monitor every agent from one integration
* **Unified analytics**: compare performance across agents and across platforms
* **Simplified testing**: target any synced agent in a run plan
***
## Security & Privacy
* **Encrypted connections**: TLS on every data transfer
* **Secure storage**: credentials encrypted at rest and referenced by ID, never embedded in plans
* **Access control**: role-based permissions for integration management
* **Audit logging**: every integration activity is recorded
* **Data isolation**: customer data separated by organization
* **PII redaction**: strip sensitive data from transcripts and recordings with [PII redaction](/documentation/observability/pii-redaction)
For network-level controls, see [IP whitelisting](/documentation/integrations/ip-whitelisting).
***
## Next Steps
Every integration path starts here
Score every imported call automatically
Turn a synced agent into a test suite
Push Roark events into your own systems
# Pipecat
Source: https://docs.roark.ai/documentation/integrations/pipecat
Instrument a Pipecat voice agent with the roark_analytics[pipecat] observer, self-hosted or on Pipecat Cloud
Live monitoring Voice simulations Chat simulations
## Overview
The Pipecat integration monitors voice agents built on the open-source [Pipecat](https://github.com/pipecat-ai/pipecat) framework. Drop the [`roark_analytics[pipecat]`](https://pypi.org/project/roark-analytics/) observer into your pipeline and call lifecycle, transcripts, tool calls, and a recording are forwarded to Roark automatically.
The same observer works whether you run Pipecat yourself or on **Pipecat Cloud**: the wiring is identical, so you pick your deployment when you connect the integration and instrument your pipeline once. The observer talks only to Roark's own API; it never changes how your agent runs.
Deployment only matters for **simulations**. Monitoring (importing your production calls) is identical for self-hosted and Pipecat Cloud. If you only want monitoring, you can skip the simulation configuration below.
***
## Prerequisites
Before setting up the integration, ensure you have:
* A Pipecat voice agent (running on your own infrastructure or on Pipecat Cloud).
* Python 3.10 or newer.
* Access to your Roark project's **Agents** page to connect the integration and mint an API key.
***
## Setup Instructions
### Step 1: Connect the integration in Roark
Go to **Agents**, click **Connect Agent**, choose **Existing Platform**, and pick **Pipecat**. Select the **Self-hosted** or **Pipecat Cloud** tab, give the integration a name, and (optionally) fill in the simulation configuration for that deployment (see [Simulations](#simulations) below).
On connect, Roark mints a project API key **bound to this integration** and shows it once. Copy it now: it's what the observer authenticates with, and Roark can't show it again.
### Step 2: Install the observer
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
pip install "roark_analytics[pipecat]"
```
### Step 3: Wire the observer into your pipeline
Add `RoarkObserver` to your Pipecat pipeline. Two rules:
* Put `roark.audio_processor` **after** `transport.output()` so the observer captures the bot's post-TTS audio.
* Pass `observers=[roark]` on `PipelineParams`.
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat_roark import RoarkObserver
SYSTEM_PROMPT = "You are a friendly voice assistant. Keep replies short."
async def bot(runner_args):
roark = RoarkObserver(
api_key="",
agent_id="pipecat-demo",
agent_name="Pipecat Demo",
agent_prompt=SYSTEM_PROMPT,
runner_args=runner_args,
)
# roark.audio_processor must sit AFTER transport.output() so the bot channel
# captures post-TTS audio. The observer is wired via PipelineParams.observers.
pipeline = Pipeline(
[
transport.input(),
stt,
context_aggregator.user(),
llm,
tts,
transport.output(),
roark.audio_processor,
context_aggregator.assistant(),
]
)
task = PipelineTask(
pipeline,
params=PipelineParams(
enable_metrics=True,
enable_usage_metrics=True,
observers=[roark],
),
)
runner = PipelineRunner(handle_sigint=runner_args.handle_sigint)
await runner.run(task)
```
The observer authenticates with the key from Step 1, sent as the `x-roark-api-key` header. Rather than hard-coding it, read it from the environment (`api_key=os.environ["ROARK_API_KEY"]`). On Pipecat Cloud, set `ROARK_API_KEY` as a deployment secret.
`agent_id` and `agent_name` identify the agent in Roark. Agents are registered automatically on the first observed call, and `agent_prompt` is tracked as a prompt revision, so prompt changes show up in Roark over time.
### Step 4: Verify the connection
Place a call to your agent. Within a few moments it appears in [Call History](/documentation/observability/live-monitoring) with its transcript, recording, and any tool calls, then gets scored by your active [collectors](/documentation/metrics/metric-collectors).
***
## How It Works
The observer POSTs call lifecycle events (`call-started`, `call-ended`) to Roark's API, authenticated by your integration-bound key. Audio is streamed as chunks to Roark-issued presigned URLs and assembled into a recording when the call ends. Transcript and tool calls are sent in Pipecat's native format and mapped to Roark's model server-side.
Because the key is bound to the integration, every call is attributed to the right integration and agent with no extra configuration.
## What Gets Synced
* **Calls**: lifecycle, timing, and metadata for each conversation.
* **Recording**: the mixed audio, assembled from the streamed chunks.
* **Transcript**: turn-by-turn, from Pipecat's context messages.
* **Tool calls**: function invocations captured during the call.
* **Agent + prompt**: lazily registered from `agent_id` / `agent_name`, with `agent_prompt` tracked as a revision.
***
## Simulations
To run [simulations](/documentation/simulation-testing/overview) against a Pipecat agent, Roark joins it over [WebRTC](/documentation/simulation-testing/webrtc). Add the simulation configuration when you connect the integration (or edit it later). This is the one part that differs by deployment.
Roark reaches your agent through its **SmallWebRTC** signaling endpoint.
| Field | Description |
| :------------------ | :------------------------------------------------------------------------------------------------------ |
| **Signaling URL** | Your SmallWebRTC offer endpoint. Roark POSTs an SDP offer here (`http(s)` or `ws(s)`). |
| **Auth token** | Optional bearer token for the signaling server. Stored encrypted. |
| **Append agent ID** | Append each synced agent's id as a URL path segment, so one host can route to many bots. On by default. |
Roark creates a WebRTC simulation endpoint for each synced agent automatically.
Roark starts a session through **Pipecat Cloud**, which runs your agent over Daily.
| Field | Description |
| :------------------ | :--------------------------------------------------------------------------------- |
| **Public API key** | Your Pipecat Cloud **public** API key, used to start sessions. Stored encrypted. |
| **Start body** | Optional JSON forwarded as the agent `body` on the Pipecat Cloud `/start` request. |
| **Room properties** | Optional JSON forwarded as the Daily `dailyRoomProperties`. |
The Pipecat Cloud start URL is derived from each synced agent's name (its Pipecat Cloud deployment name), so there's nothing else to configure per agent.
***
## Next Steps
Test your Pipecat agent with synthetic callers over WebRTC
Score every incoming call automatically
# Retell AI
Source: https://docs.roark.ai/documentation/integrations/retell
Sync Retell agents and analyze voice AI conversations
Live monitoring Voice simulations Chat simulations
## Overview
The Retell AI integration enables comprehensive monitoring of your Retell voice agents. Sync historical calls, monitor live conversations, and automatically evaluate agent performance using Roark's analytics platform.
***
## Prerequisites
Before setting up the integration, ensure you have:
* A Retell AI account with active agents
* Retell API key (found in your Retell dashboard)
* At least one configured agent in Retell
***
## Setup Instructions
### Step 1: Create Integration
1. Navigate to **Agents** in your Roark dashboard
2. Click **Connect Agent** in the top right, choose **Existing Platform**, and select **Retell AI**
3. Enter your configuration:
| Field | Description |
| :------------------- | :--------------------------------- |
| **Integration Name** | Friendly name for this integration |
| **API Key** | Your Retell API key |
The system will automatically validate your API key and fetch available agents.
### Step 2: Configure Historical Sync
Choose how far back to import existing calls:
* **Default**: Last 90 days of call history
* **Custom**: Select a specific date range
* **Skip**: Only sync new calls going forward
Historical sync runs once during initial setup. Future calls are synced automatically via webhooks.
### Step 3: Select Agents
Choose which Retell agents to monitor:
* **All Agents** - Sync calls from every agent in your account
* **Selected Agents** - Choose specific agents to monitor
The agent selector shows:
* Agent name
* Agent ID
* Last modified date
### Step 4: Configure Metric Collectors
Calls synced from Retell are scored by your [metric collectors](/documentation/metrics/metric-collectors). Set them up globally under **Metrics → Collectors**, or scope collectors to a specific agent from that agent's **Collectors** section.
### Step 5: Activate Integration
Review your settings and click **Create Integration** to begin syncing.
***
## What Gets Synced
Retell integrations sync comprehensive data:
* **Calls** - Complete conversation data and metadata
* **Agents** - Agent configurations and phone numbers
* **Prompts** - System prompts and instructions
* **Transcripts** - Full conversation transcripts with speaker labels
* **Tool Calls** - Function/tool invocations during conversations
* **Call Metadata** - Duration, status, participant information
***
## Webhook Configuration
Roark automatically configures webhooks with Retell. The integration handles:
| Event | Description | Roark Action |
| :------------- | :------------- | :----------------------------------- |
| `call_started` | Call initiated | Create call record, begin monitoring |
| `call_ended` | Call completed | Process transcript, run evaluations |
The webhook URL is automatically registered with Retell using your API key.
***
## Sync Management
### Initial Sync
During setup, Roark imports historical calls based on your selected date range:
* Processes calls in batches
* Shows sync progress in dashboard
* Handles large volumes efficiently
### Ongoing Sync
After initial setup:
* New calls sync automatically via webhooks
* Real-time processing of live calls
* Automatic retry on temporary failures
### Resume Sync
If sync is interrupted:
1. Go to integration settings
2. Click **Resume Sync**
3. Choose to continue from last position or restart
***
## Monitoring Integration Health
Track your integration status:
* **Syncing** 🔄 - Actively importing historical calls
* **Active** 🟢 - Receiving new calls via webhook
* **Paused** ⏸️ - Sync temporarily halted
* **Error** ⚠️ - Configuration or connection issues
View sync statistics:
* Total calls synced
* Last sync timestamp
* Sync progress percentage
* Recent webhook deliveries
***
## Agent Management
Synced agents appear in:
* **Simulation agent selection** - Use for testing
* **Agent performance reports** - Track metrics per agent
* **Comparison dashboards** - Analyze across agents
Agent data updates automatically when modified in Retell.
***
## Next Steps
Test synced agents
Analyze call performance
Define metrics and pass/fail thresholds
Explore other integrations
# VAPI
Source: https://docs.roark.ai/documentation/integrations/vapi
Sync VAPI assistants and analyze voice AI conversations
Live monitoring Voice simulations Chat simulations
## Overview
The VAPI integration enables comprehensive monitoring of your VAPI voice assistants. Sync historical calls, monitor live conversations, and automatically evaluate assistant performance using Roark's analytics platform.
***
## Prerequisites
Before setting up the integration, ensure you have:
* A VAPI account with active assistants
* VAPI API key (found in your VAPI dashboard)
* At least one configured assistant in VAPI
***
## Setup Instructions
### Step 1: Create Integration
1. Navigate to **Agents** in your Roark dashboard
2. Click **Connect Agent** in the top right, choose **Existing Platform**, and select **VAPI**
3. Enter your configuration:
| Field | Description |
| :------------------- | :--------------------------------- |
| **Integration Name** | Friendly name for this integration |
| **API Key** | Your VAPI API key |
The system will automatically validate your API key and fetch available assistants.
### Step 2: Configure Historical Sync
Choose how far back to import existing calls:
* **Default**: Last 90 days of call history
* **Custom**: Select a specific date range
* **Skip**: Only sync new calls going forward
Historical sync runs once during initial setup. Future calls are synced automatically via webhooks.
### Step 3: Select Assistants
Choose which VAPI assistants to monitor:
* **All Assistants** - Sync calls from every assistant in your account
* **Selected Assistants** - Choose specific assistants to monitor
The assistant selector shows:
* Assistant name
* Assistant ID
* Last modified date
### Step 4: Configure Metric Collectors
Calls synced from VAPI are scored by your [metric collectors](/documentation/metrics/metric-collectors). Set them up globally under **Metrics → Collectors**, or scope collectors to a specific agent from that agent's **Collectors** section.
### Step 5: Activate Integration
Review your settings and click **Create Integration** to begin syncing.
***
## What Gets Synced
VAPI integrations sync comprehensive data:
* **Calls** - Complete conversation data and metadata
* **Assistants** - Assistant configurations and phone numbers
* **Prompts** - System prompts and instructions
* **Transcripts** - Full conversation transcripts with speaker labels
* **Tool Calls** - Function/tool invocations during conversations
* **Call Metadata** - Duration, status, participant information
***
## Webhook Configuration
Roark automatically configures webhooks with VAPI. The integration handles:
| Event | Description | Roark Action |
| :------------- | :------------- | :----------------------------------- |
| `call_started` | Call initiated | Create call record, begin monitoring |
| `call_ended` | Call completed | Process transcript, run evaluations |
The webhook URL is automatically registered with VAPI using your API key.
***
## Sync Management
### Initial Sync
During setup, Roark imports historical calls based on your selected date range:
* Processes calls in batches
* Shows sync progress in dashboard
* Handles large volumes efficiently
### Ongoing Sync
After initial setup:
* New calls sync automatically via webhooks
* Real-time processing of live calls
* Automatic retry on temporary failures
### Resume Sync
If sync is interrupted:
1. Go to integration settings
2. Click **Resume Sync**
3. Choose to continue from last position or restart
***
## Monitoring Integration Health
Track your integration status:
* **Syncing** 🔄 - Actively importing historical calls
* **Active** 🟢 - Receiving new calls via webhook
* **Paused** ⏸️ - Sync temporarily halted
* **Error** ⚠️ - Configuration or connection issues
View sync statistics:
* Total calls synced
* Last sync timestamp
* Sync progress percentage
* Recent webhook deliveries
***
## Assistant Management
Synced assistants appear in:
* **Simulation assistant selection** - Use for testing
* **Assistant performance reports** - Track metrics per assistant
* **Comparison dashboards** - Analyze across assistants
Assistant data updates automatically when modified in VAPI.
***
## Next Steps
Traces sync automatically. See how to enable them
Test synced assistants
Analyze call performance
Explore other integrations
# Webhooks
Source: https://docs.roark.ai/documentation/integrations/webhooks
Receive real-time notifications when call analysis completes or fails
## Overview
Webhooks allow you to receive real-time HTTP notifications when events occur in Roark. Instead of polling for updates, Roark will automatically send event data to your specified endpoint whenever a call analysis completes or fails.
***
## Webhook Events
Roark currently supports the following webhook events:
### `call.analysis.completed`
Triggered when a call analysis has successfully completed processing.
**Event Payload:**
* `callId` - Unique identifier for the call
* `callAnalysisJobId` - ID of the completed analysis job
* `projectId` - ID of the project containing the call
### `call.analysis.failed`
Triggered when a call analysis fails to complete.
**Event Payload:**
* `callId` - Unique identifier for the call
* `callAnalysisJobId` - ID of the failed analysis job
* `projectId` - ID of the project containing the call
* `errorMessage` - Description of the failure (optional)
***
## Setting Up Webhooks
Go to your workspace settings and select the Webhooks section
Enter the URL where you want to receive webhook events
Choose which events you want to subscribe to:
* Call Analysis Completed
* Call Analysis Failed
Use the "Send Test Event" button to verify your endpoint is working correctly
***
## Testing Webhooks
You can send test events to verify your webhook integration is working properly:
1. **Send Test Event** - Click the "Send Test Event" button next to your webhook endpoint
2. **Verify Receipt** - Check that your endpoint received the test payload
3. **Validate Handling** - Ensure your application processes the test event correctly
Test events contain sample data that matches the structure of real webhook events, allowing you to validate your integration before going live.
***
## Webhook Security
### Signature Verification
All webhook requests from Roark include a signature header that you can use to verify the request authenticity:
```
X-Roark-Signature:
```
Always validate webhook signatures in production to ensure requests are coming from Roark and haven't been tampered with.
### Best Practices
* **Use HTTPS** - Only accept webhooks over secure HTTPS connections
* **Verify Signatures** - Always validate the webhook signature
* **Respond Quickly** - Return a 200 OK response within 5 seconds
* **Process Async** - Handle webhook processing in background jobs
* **Implement Retries** - Handle temporary failures gracefully
***
## Webhook Delivery
### Retry Logic
If your endpoint fails to respond with a 2xx status code, Roark will retry the webhook delivery:
* **Retry Attempts**: Up to 3 retries
* **Retry Schedule**: Exponential backoff (1 min, 10 min, 1 hour)
* **Timeout**: 5 seconds per request
### Response Requirements
Your endpoint should:
* Return a `200 OK` status code to acknowledge receipt
* Respond within 5 seconds
* Process the webhook payload asynchronously if needed
***
## Event Payload Structure
All webhook events follow a consistent structure with an `event` name, API `version`, `timestamp`, and event-specific `data`.
### Call Analysis Completed
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"event": "call.analysis.completed",
"version": "1.0",
"timestamp": "2025-11-21T10:30:00Z",
"data": {
"callId": "call_abc123",
"callAnalysisJobId": "job_xyz789",
"projectId": "proj_def456"
}
}
```
### Call Analysis Failed
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"event": "call.analysis.failed",
"version": "1.0",
"timestamp": "2025-11-21T10:30:00Z",
"data": {
"callId": "call_abc123",
"callAnalysisJobId": "job_xyz789",
"projectId": "proj_def456",
"errorMessage": "Analysis timeout exceeded"
}
}
```
***
## Use Cases
### Real-time Notifications
Send alerts to your team when critical calls fail analysis:
```javascript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
app.post('/webhooks/roark', (req, res) => {
const { event, data } = req.body;
if (event === 'call.analysis.failed') {
sendSlackAlert(
`Call ${data.callId} analysis failed: ${data.errorMessage || 'Unknown error'}`
);
}
res.sendStatus(200);
});
```
### Data Synchronization
Keep your internal systems in sync with Roark analysis results:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
@app.route('/webhooks/roark', methods=['POST'])
def handle_webhook():
payload = request.json
if payload['event'] == 'call.analysis.completed':
# Fetch full call details and update your database
call_id = payload['data']['callId']
update_call_analytics(call_id=call_id)
return '', 200
```
### Workflow Automation
Trigger downstream processes when analysis completes:
```javascript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
async function handleWebhook(event) {
if (event.event === 'call.analysis.completed') {
const { callId, projectId } = event.data;
// Trigger follow-up actions
await updateCRM(callId, projectId);
await sendCustomerSurvey(callId);
await notifyQATeam(callId);
}
}
```
***
## Troubleshooting
### Webhook Not Received
* **Check URL** - Ensure your endpoint URL is correct and accessible
* **Verify HTTPS** - Confirm you're using HTTPS (HTTP not supported)
* **Check Firewall** - Ensure your firewall allows incoming requests from Roark
* **Review Logs** - Check your server logs for incoming requests
### Delivery Failures
* **Response Time** - Ensure your endpoint responds within 5 seconds
* **Status Codes** - Return 2xx status codes to acknowledge receipt
* **Error Handling** - Check your endpoint for exceptions or crashes
### Missing Events
* **Event Subscriptions** - Verify you're subscribed to the correct events
* **Endpoint Status** - Check if your webhook endpoint is marked as active
* **Delivery History** - Review the webhook delivery logs in your Roark dashboard
***
## Next Steps
Explore the Roark API
Build custom integrations
Monitor calls in real-time
Get help with webhooks
# Custom Metrics
Source: https://docs.roark.ai/documentation/metrics/custom-metrics
Create and manage custom metrics using LLM prompts, patterns, and formulas
Metric definitions describe what to measure and how. Roark comes with [built-in system metrics](/documentation/metrics/system-metrics) that work out of the box, and you can create your own custom metrics tailored to your business needs.
**Prefer to keep this in your repo?** Custom metrics can be defined as [config as code](/documentation/config-as-code/metrics) (YAML in git, applied with the [CLI](/documentation/sdks/cli)) instead of authored in the dashboard.
***
## Creating Custom Metrics
Custom metrics let you measure anything specific to your use case: task completion, compliance checks, quality scoring, or business KPIs. You author them in [Studio](/documentation/metrics/studio)'s Author mode, which walks you through choosing an engine, output type, and instructions, with a test rail for validating against real calls.
### Custom Metric Types
Write a natural-language prompt describing what to measure. **Roark Prism**, our evaluation model optimized for voice AI, scores each call against your prompt and returns a typed result.
```
"Did the agent verify the caller's identity?" → Boolean
"Rate the agent's empathy on a 1-5 scale" → Scale
"What was the primary reason for the call?" → Classification
"How many times did the agent attempt to upsell?" → Count
```
Best for subjective assessments, business logic, and anything that requires understanding conversational context.
Match specific patterns in the transcript using keywords or regex. Runs without LLM overhead: fast and deterministic.
Best for detecting required phrases, prohibited words, or specific conversational markers.
Combine multiple existing metrics into a single composite score using boolean logic and weighted expressions.
```
{frustration_score < 3} AND {instruction_follow = TRUE} → Boolean "Call Success"
```
Best for layered quality gates and composite scores built from your existing metrics.
### Configuration Steps
Open [Studio](/documentation/metrics/studio) in Author mode. Name your metric, pick an engine (LLM Judge, Pattern, or Formula; locked after the first save), choose an output type, and describe what it measures. For LLM Judge metrics, write the evaluation prompt. For formulas, build the expression from existing metrics.
Use the test rail in Studio to run your metric against representative calls and validate it produces the results you expect. Iterate until you're satisfied.
Attach the metric to a [collector](/documentation/metrics/metric-collectors) for automated collection on incoming calls, or to a [simulation run plan](/documentation/simulation-testing/run-plans) for testing.
***
## SDK Reference
All API endpoints require authentication. [Generate an API key](/documentation/getting-started/api-keys) to get started.
### Create a Metric Definition
Create a new custom metric definition using the SDK:
**Parameters:**
| Field | Type | Required | Description |
| :------------------ | :-------- | :---------- | :--------------------------------------------------------------------------------- |
| `name` | string | Yes | Name of the metric (1-100 characters) |
| `outputType` | string | Yes | One of: `BOOLEAN`, `NUMERIC`, `TEXT`, `SCALE`, `CLASSIFICATION`, `COUNT`, `OFFSET` |
| `analysisPackageId` | string | Yes | UUID of the analysis package to add this metric to |
| `metricId` | string | No | Unique identifier (auto-generated from name if omitted) |
| `scope` | string | No | `GLOBAL` (default) or `PER_PARTICIPANT` |
| `participantRole` | string | Conditional | Required when scope is `PER_PARTICIPANT` |
| `supportedContexts` | string\[] | No | Defaults to `["CALL"]` |
| `llmPrompt` | string | No | The LLM prompt used to evaluate this metric (max 2000 chars) |
**Type-specific fields:**
| Field | Applies To | Description |
| :--------------------------------------- | :------------- | :-------------------------------------------------------------------------- |
| `booleanTrueLabel` / `booleanFalseLabel` | BOOLEAN | Custom labels for true/false values |
| `scaleMin` / `scaleMax` | SCALE | Range boundaries (0-100) |
| `scaleLabels` | SCALE | Array of label objects with `rangeMin`, `rangeMax`, `label`, `displayOrder` |
| `classificationOptions` | CLASSIFICATION | Array of options with `label`, `description`, `displayOrder` |
| `maxClassifications` | CLASSIFICATION | Maximum number of classifications to select |
**Example: Create a BOOLEAN metric**
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const metric = await client.metric.createDefinition({
name: 'Identity Verified',
outputType: 'BOOLEAN',
analysisPackageId: 'your-package-id',
llmPrompt: 'Did the agent successfully verify the caller identity before proceeding with the request?',
booleanTrueLabel: 'Verified',
booleanFalseLabel: 'Not Verified',
})
```
**Example: Create a SCALE metric**
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const metric = await client.metric.createDefinition({
name: 'Customer Satisfaction',
outputType: 'SCALE',
analysisPackageId: 'your-package-id',
llmPrompt: 'Rate the overall customer satisfaction based on the conversation tone, resolution, and agent helpfulness.',
scaleMin: 1,
scaleMax: 10,
scaleLabels: [
{ rangeMin: 1, rangeMax: 3, label: 'Poor', displayOrder: 1 },
{ rangeMin: 4, rangeMax: 6, label: 'Average', displayOrder: 2 },
{ rangeMin: 7, rangeMax: 10, label: 'Excellent', displayOrder: 3 },
],
})
```
### List Metric Definitions
Retrieve the first page of metric definitions available for your project. Each
page contains up to 500 definitions. If `pagination.hasMore` is true, use
`pagination.nextCursor` as the `after` query parameter on `GET
/v1/metric/definitions` to retrieve the next page. Results are ordered by
immutable definition ID. Treat cursors as opaque.
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const definitions = await client.metric.listDefinitions()
// definitions.data[0]
{
id: 'uuid',
metricId: 'response_time',
name: 'Response Time',
description: 'Time taken to respond to a question',
type: 'OFFSET',
scope: 'PER_PARTICIPANT',
supportedContexts: ['SEGMENT_RANGE', 'CALL'],
unit: { name: 'milliseconds', symbol: 'ms' },
}
```
### Get Call Metrics
Retrieve all metrics for a specific call:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const metrics = await client.call.listMetrics('call-id')
// Or flatten to get a simple list instead of grouped by definition
const flat = await client.call.listMetrics('call-id', { flatten: 'true' })
```
The response groups metrics by definition, with each metric containing an array of values:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// metrics.data[0]
{
metricDefinitionId: 'uuid',
metricId: 'response_time',
name: 'Response Time',
type: 'OFFSET',
scope: 'PER_PARTICIPANT',
unit: { name: 'milliseconds', symbol: 'ms' },
values: [
{
value: 2500,
context: 'SEGMENT_RANGE',
participantRole: 'agent',
confidence: 1.0,
computedAt: '2024-01-15T10:30:00Z',
fromSegment: { id: 'uuid', text: 'How can I help you today?', startOffsetMs: 1000, endOffsetMs: 2000 },
toSegment: { id: 'uuid', text: 'I have a question about my bill', startOffsetMs: 4500, endOffsetMs: 6000 },
},
],
}
```
### Understanding Metric Values
**Confidence Scores:**
* All metrics include a `confidence` field (0-1)
* Deterministic metrics (like word count, duration) have confidence = 1.0
* AI-powered metrics include the model's confidence level
**Value Reasoning:**
* For AI-computed metrics, the `valueReasoning` field provides explanation
* Useful for understanding why a metric was scored a certain way
* Example: "The agent verified identity using two-factor authentication as mentioned in segment 3"
**Segment Context:**
* When `context` is `SEGMENT`, the `segment` field contains the specific utterance
* When `context` is `SEGMENT_RANGE`, both `fromSegment` and `toSegment` are included
* All segment objects include the full text and timing information
***
## Best Practices
Use the [built-in system metrics](/documentation/metrics/system-metrics) first. They cover performance, sentiment, interruptions, compliance, and more with no setup. Add custom metrics for business-specific needs.
Always validate custom metrics in [Studio](/documentation/metrics/studio) on representative calls before attaching them to collectors.
Instead of creating one complex LLM prompt that tries to measure everything, break it into focused metrics and combine them with a formula.
Track both agent and customer metrics for complete conversation understanding.
***
## What's Next
Browse all 65+ built-in metrics powered by specialized models
Author and test metrics interactively before deploying
Define pass/fail criteria for your metrics
Automate metric collection with conditions-based rules
# Datasets
Source: https://docs.roark.ai/documentation/metrics/datasets
Group calls or chats into named collections for evaluation, comparison, and analysis
## Overview
A **dataset** is a named, project-scoped collection of conversations. Use datasets to group calls or chats you want to evaluate together: a batch of onboarding failures, last quarter's escalations, a golden set for regression testing in [Studio](/documentation/metrics/studio).
Each dataset holds one conversation type, **Calls** or **Chats**, chosen at creation. A dataset is locked to that type; you can't mix modalities in a single dataset.
Datasets live under **Measure → Datasets** in the sidebar.
## Dataset properties
| Property | Description |
| :-------------- | :------------------------------------------------------- |
| **Name** | Required, e.g. "Q1 Support Calls", "Onboarding Failures" |
| **Description** | Optional context for what the dataset contains |
| **Type** | Calls or Chats, fixed at creation |
| **Count** | Number of conversations currently in the dataset |
| **Labels** | Tags for organizing datasets |
Datasets with a **System** badge are managed by Roark and can't be edited or deleted.
## Creating a dataset
Go to **Measure → Datasets** and click **New Dataset**.
Choose **Calls** or **Chats**. This is permanent. Pick based on what you'll be adding.
Add a name and an optional description, then click **Create**.
## Adding conversations
How you add conversations depends on the dataset type:
* **Call datasets**: open the dataset and click **Add Calls** to pick calls from your project.
* **Chat datasets**: add chats from the chat's detail page, not from the dataset panel.
## Managing a dataset
Selecting a dataset from the list opens its detail panel, which shows the name, conversation count, and last-updated time above a table of members. From there you can:
* **Remove conversations**: select rows and bulk-remove them (with confirmation). Removing a conversation from a dataset doesn't delete the conversation itself.
* **Edit**: rename the dataset or update its description.
* **Delete**: remove the dataset entirely.
* **Drill in**: click any row to open that conversation's detail view.
The list page has **All / Calls / Chats** filter pills and a search box to find datasets quickly.
## What's Next
Run a battery of evals against the conversations you've grouped
Run metrics automatically on matching calls and chats
# Collection Jobs
Source: https://docs.roark.ai/documentation/metrics/metric-collection-jobs
Run metrics on demand for specific calls via the API
## Overview
Collection jobs are the programmatic way to run metrics on demand for one or many calls via the API. While [collectors](/documentation/metrics/metric-collectors) automate collection for incoming calls, collection jobs let you trigger metric processing against existing calls, whether that's backfilling a new metric across historical data or re-running after a definition change.
Want to run metrics across calls from the UI? Use [Studio](/documentation/metrics/studio) in Evaluate mode ("Run a battery of metrics across a set of calls") instead. Collection jobs are designed for programmatic, at-scale use via the SDK.
***
## When to Use Collection Jobs
* **Backfill metrics**: Run newly created metrics on historical calls
* **Re-run metrics**: Reprocess calls after updating a metric definition
* **On-demand analysis**: Collect metrics for a specific set of calls uploaded via API
* **Scale up after testing**: You've validated a metric in Studio, now run it across many calls programmatically
***
## Creating a Collection Job
Provide an array of call IDs and the metrics you want to collect:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const job = await client.metricCollectionJob.create({
callIds: ['call-uuid-1', 'call-uuid-2', 'call-uuid-3'],
metrics: [
{ id: 'metric-definition-uuid-1' },
{ id: 'metric-definition-uuid-2' },
],
})
// job.data
{
id: 'job-uuid',
status: 'PENDING',
triggeredBy: 'USER_API',
totalItems: 6,
completedItems: 0,
failedItems: 0,
startedAt: null,
completedAt: null,
errorMessage: null,
createdAt: '2025-01-15T10:30:00Z',
updatedAt: '2025-01-15T10:30:00Z',
}
```
**Parameters:**
| Field | Type | Required | Description |
| :-------- | :-------- | :------- | :----------------------------------------- |
| `callIds` | string\[] | Yes | Array of call UUIDs to process (minimum 1) |
| `metrics` | array | Yes | Metric definitions to collect (minimum 1) |
The `totalItems` count equals `callIds.length * metrics.length`: each call-metric combination is a separate item.
***
## Job Lifecycle
Collection jobs progress through the following statuses:
```
PENDING → PROCESSING → COMPLETED / FAILED / CANCELED
```
| Status | Description |
| :----------- | :-------------------------------------------- |
| `PENDING` | Job created, waiting to start processing |
| `PROCESSING` | Actively collecting metrics from calls |
| `COMPLETED` | All items processed successfully |
| `FAILED` | Job encountered errors (check `errorMessage`) |
| `CANCELED` | Job was canceled before completion |
### Progress Tracking
Monitor progress using these fields:
| Field | Description |
| :--------------- | :-------------------------------------------------- |
| `totalItems` | Total number of call-metric combinations to process |
| `completedItems` | Number of items successfully processed |
| `failedItems` | Number of items that failed |
| `startedAt` | When processing began |
| `completedAt` | When processing finished |
***
## Monitoring Jobs
### List Collection Jobs
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const jobs = await client.metricCollectionJob.list()
// With filters
const completedJobs = await client.metricCollectionJob.list({
status: 'COMPLETED',
limit: 50,
})
```
| Parameter | Type | Description |
| :-------- | :----- | :---------------------------------------------------------------------- |
| `limit` | number | Max results (1-50, default: 20) |
| `after` | string | Cursor for pagination |
| `status` | string | Filter by `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, or `CANCELED` |
### Get a Collection Job
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const job = await client.metricCollectionJob.getByID('job-id')
```
Returns the full job object with current progress.
***
## Example: Studio to Production
A typical end-to-end workflow (test a metric in Studio, then run it across calls via the SDK):
Use [Studio](/documentation/metrics/studio) to test your metric prompt against a sample call. Iterate until you're happy with the output.
Once validated, create the metric definition via the SDK:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const metric = await client.metric.createDefinition({
name: 'Identity Verified',
outputType: 'BOOLEAN',
analysisPackageId: 'your-package-id',
llmPrompt: 'Did the agent successfully verify the caller identity?',
booleanTrueLabel: 'Verified',
booleanFalseLabel: 'Not Verified',
})
```
Create a collection job targeting the calls you want to analyze:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const job = await client.metricCollectionJob.create({
callIds: ['call-id-1', 'call-id-2', 'call-id-3'],
metrics: [{ id: metric.data.id }],
})
```
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
let status = 'PENDING'
while (status !== 'COMPLETED' && status !== 'FAILED') {
const result = await client.metricCollectionJob.getByID(job.data.id)
status = result.data.status
console.log(`Progress: ${result.data.completedItems}/${result.data.totalItems}`)
}
```
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const metrics = await client.call.listMetrics('call-id-1')
console.log(metrics.data)
```
Once you've validated the metric works well across calls, consider creating a [collector](/documentation/metrics/metric-collectors) to automatically collect it on future calls.
***
## What's Next
Test metrics interactively before running at scale
Automate metric collection for incoming calls
# Collectors
Source: https://docs.roark.ai/documentation/metrics/metric-collectors
Run a set of metrics automatically on every conversation that matches
**Prefer to keep this in your repo?** Collectors can be defined as [config as code](/documentation/config-as-code/collectors) (YAML in git, applied with the [CLI](/documentation/sdks/cli)) instead of built in the dashboard.
## Overview
A collector runs a set of metrics on the calls (or chats) it matches. Instead of triggering metric collection by hand, you define the segment of conversations you care about (by agent, source, or property) and Roark scores every matching conversation as it comes in.
Collectors live under **Measure → Metrics**: the Metrics page shows a Collectors strip at the top, and **Manage collectors** opens the full list at `/metrics/collectors`.
A collector is made of four things:
1. **Name**: how it appears in the collectors list
2. **Modality**: Call or Chat. A collector fires for exactly one modality, and this is locked after creation
3. **Conditions** (optional): the segment of conversations it matches. Leave empty to match all calls (or all chats)
4. **Metrics**: the metrics to run on every match, each with optional pass/fail thresholds
***
## Creating a Collector
On `/metrics/collectors`, click **New collector**. The editor reads as a sentence: "When a **call** matches…" Click the chip to switch between Call and Chat. Once the collector is created, the modality is locked.
Add conditions to target specific conversations, or leave the conditions empty to match everything. Use **Add condition** and **Add OR condition** to build a group, and **Add AND condition group** to require a second group to match as well.
Under **Evaluate these metrics**, add the metrics to run. Each picked metric runs on every conversation that matches the scope above. Where a metric has multiple variants, choose which variant's prompt and config apply.
On any picked metric, click **Add pass/fail threshold** to turn its raw value into a pass/fail outcome, for example, "Pass if ≥ 7".
Click **Create collector**. Roark suggests a name built from the metrics and conditions. Keep it or edit it, then confirm. Incoming conversations that match your conditions are evaluated from then on.
***
## Conditions
Conditions scope a collector to a segment of conversations. **Groups combine with AND; conditions within a group combine with OR.** An empty condition set matches every call (or chat) in the project.
| Type | Matches | Inputs |
| :----------- | :---------------------------------------- | :----------------------------------------------------------- |
| **Agent** | Conversations handled by specific agents | Agent picker |
| **Source** | Conversations from a specific integration | LiveKit Cloud, Vapi, Retell, ElevenLabs, Leaping, API, Bland |
| **Property** | Conversations by custom metadata | Property key + operator + value |
Property conditions support these operators:
| Operator | Comparison |
| :----------------------------- | :------------------------------------ |
| equals / does not equal | Exact match |
| contains | Substring match |
| starts with | Prefix match |
| is greater than / is less than | Numeric comparison |
| is at least / is at most | Numeric comparison, inclusive (≥ / ≤) |
A group like "Agent is Billing Bot OR Agent is Sales Bot" AND "Source is Vapi" runs the collector's metrics only on Vapi calls handled by either agent.
***
## Pass/Fail Thresholds
Thresholds turn a metric's raw value into a pass/fail check, configured inline on each picked metric:
* **Add pass/fail threshold** adds a row that reads "Pass if `[operator]` `[value]`" (for example, Pass if ≥ 0.7).
* Thresholds that already exist for the metric appear as read-only rows with an **Include if** checkbox, so you can opt an existing threshold into this collector instead of creating a duplicate. If a new row matches an existing threshold, Roark warns you and dedupes it. It won't be created again.
Each saved threshold is a derived metric in its own right (named like "Empathy ≥ 7", scored Pass/Fail), added to the collector alongside the source metric. Thresholds are available for Scale, Numeric, Count, Boolean, and Classification outputs.
Learn about operators and how derived threshold metrics work
***
## Active and Paused
Every custom collector has a status toggle in the list: **Active** collectors evaluate matching conversations; **Paused** collectors don't. Pause a collector instead of deleting it if you might need it again. Deleting a collector stops it from collecting its metrics, but the metric definitions themselves are unaffected.
***
## System Collectors
Roark ships system collectors, the baseline coverage that keeps core metrics running on all your calls and chats. They're marked with a **System** shield badge and sort first in the list.
| | System collectors | Custom collectors |
| :----------------------- | :---------------- | :------------------------ |
| **Created by** | Roark | You, via dashboard or API |
| **Editable / deletable** | No | Yes |
| **Status** | Always run | Active or Paused |
***
## Per-Agent Collectors
Each agent's detail page has a **Collectors** section listing the collectors that target that agent. These are the same collectors (just scoped by an Agent condition) so a collector created from the agent page shows up in the main `/metrics/collectors` list too, and vice versa.
***
## API and SDK Access
Collectors are fully manageable programmatically via the [Node.js SDK](/documentation/sdks/node-sdk) and REST API.
The API keeps the older **metric policy** naming: the SDK client is `client.metricPolicy.*` and the REST endpoints are `/v1/metric/policies`. A "policy" in the API is exactly a collector in the dashboard.
### Create
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const collector = await client.metricPolicy.create({
name: 'Production Agent Quality Checks',
modality: 'call', // 'call' or 'chat', locked after creation
status: 'ACTIVE',
conditions: [
{
conditions: [
{
conditionType: 'AGENT',
conditionKey: 'your-agent-id',
},
],
},
],
metrics: [
{ id: 'metric-definition-uuid-1' },
{ id: 'metric-definition-uuid-2' },
],
})
```
| Field | Type | Required | Description |
| :----------- | :----- | :------- | :------------------------------------------------------------------------------------------------------------ |
| `name` | string | Yes | Descriptive name for the collector |
| `modality` | string | Yes | `call` or `chat`; the collector only fires for this modality and can only reference metrics that support it |
| `status` | string | No | `ACTIVE` (default) or `INACTIVE` (Paused) |
| `conditions` | array | No | Condition groups: groups AND together, conditions within a group OR together; omit to match all conversations |
| `metrics` | array | Yes | Metric definitions to run (minimum 1) |
### Condition fields
| `conditionType` | `conditionKey` | `conditionOperator` | `conditionValue` |
| :-------------- | :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :----------------------- |
| `AGENT` | Agent ID | N/A | N/A |
| `CALL_SOURCE` | Source name (e.g. `VAPI`, `RETELL`) | N/A | N/A |
| `CALL_PROPERTY` | Property key | `EQUALS`, `NOT_EQUALS`, `CONTAINS`, `STARTS_WITH`, `GREATER_THAN`, `LESS_THAN`, `GREATER_THAN_OR_EQUALS`, `LESS_THAN_OR_EQUALS` | Value to compare against |
`GREATER_THAN_OR_EQUALS` and `LESS_THAN_OR_EQUALS` correspond to the dashboard's "is at least" and "is at most" operators.
**Example: collector for Vapi calls only**
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const collector = await client.metricPolicy.create({
name: 'Vapi Call Metrics',
modality: 'call',
conditions: [
{
conditions: [
{
conditionType: 'CALL_SOURCE',
conditionKey: 'VAPI',
},
],
},
],
metrics: [{ id: 'metric-definition-uuid' }],
})
```
### List, get, update, delete
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// List (limit 1-50, default 20; cursor pagination via `after`)
const collectors = await client.metricPolicy.list({ status: 'ACTIVE', limit: 50 })
// Get one
const collector = await client.metricPolicy.getByID('policy-id')
// Update: all fields optional; pass an empty `conditions` array to match everything
const updated = await client.metricPolicy.update('policy-id', {
name: 'Updated Collector Name',
status: 'INACTIVE',
})
// Delete
const result = await client.metricPolicy.delete('policy-id')
```
System collectors (`type: "SYSTEM"` in API responses) cannot be modified or deleted.
***
## Best Practices
Create a collector with no conditions to run key metrics on all calls, then add targeted collectors with conditions for specific segments.
If you only need certain metrics for specific agents or sources, scope the collector so you're not scoring conversations you don't care about.
Use names that say what the collector targets and measures. The auto-suggested name (metric names plus a condition summary) is a good starting point.
Pause a collector if you might need it again later. Deleting stops collection but leaves the metric definitions intact.
***
## What's Next
Learn about metric types, scopes, and how to create custom metrics
Author and test metrics, then bind them to a collector from the editor
Turn raw metric values into pass/fail outcomes
Run metrics on demand for specific calls
# Overview
Source: https://docs.roark.ai/documentation/metrics/overview
Understand what metrics are and how to use them across monitoring and simulations
Metrics are how you measure what happens in every voice AI conversation. Roark collects some metrics automatically (like response time, talk time, and sentiment) and lets you define your own for things like compliance checks, task completion, or custom business KPIs.
**UI-first or code-first?** Author custom metrics and collectors in the dashboard, or define them as [config as code](/documentation/config-as-code/overview) - YAML in your git repo, applied with the [CLI](/documentation/sdks/cli). Both manage the same resources.
## Quickstart
Define a custom metric and run it on a call:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// Author an LLM-judge metric
const metric = await client.metric.createDefinition({
name: 'Resolved On First Call',
outputType: 'BOOLEAN',
llmPrompt: 'Did the agent resolve the issue without a transfer or callback?',
})
// Run it against an existing call, then read the result
await client.metricCollectionJob.create({
callIds: [''],
metrics: [{ id: metric.data.id }],
})
const results = await client.call.listMetrics('')
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
metric = client.metric.create_definition(
name="Resolved On First Call",
output_type="BOOLEAN",
llm_prompt="Did the agent resolve the issue without a transfer or callback?",
)
client.metric_collection_job.create(
call_ids=[""],
metrics=[{"id": metric.data.id}],
)
results = client.call.list_metrics("")
```
```yaml roark/metrics/resolved-on-first-call.yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
kind: metric
name: resolved_on_first_call
displayName: Resolved On First Call
type: BOOLEAN
prompt: |
Using {{transcript}} and {{world_context}}, did the agent resolve the
issue without a transfer or callback?
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark config apply ./roark
```
Attach it to a [collector](/documentation/metrics/metric-collectors) to score every matching call automatically.
Everything measurement-related lives in the **Measure** section of the sidebar:
* **Metrics**: your library of everything the project can measure, plus the collectors that run it on your calls
* **Studio**: where you author new metrics and run evaluation batteries against real calls
* **Datasets**: named collections of calls or chats for evaluation, comparison, and analysis
***
## What's a Metric?
A metric is a single measurement collected from a call. It has:
* **An output type**: boolean, numeric, scale, text, classification, or count
* **A scope**: global (one value per call) or per-participant (separate values for agent and customer)
* **A context**: call-level, segment-level (single utterance), or segment-range (span of conversation)
For example, `response_time` is a numeric, per-participant, segment-range metric that measures how long each speaker takes to respond. `identity_verified` might be a boolean, global, call-level metric powered by an LLM Judge prompt.
***
## Types of Metrics
### System Metrics (Built-in)
Roark automatically collects deterministic and voice-analysis metrics for every call. No configuration needed. Voice-analysis metrics are powered by Roark's custom voice analysis models, purpose-built to extract signal from conversational audio:
* **Performance**: Response time, talk time, silence duration, overlap/interruptions, latency
* **Emotion & Sentiment**: Sentiment tracking, 64+ emotion detection, vocal cues (raised voice, frustration), stress indicators
* **Speech**: Interruption detection, pause analysis, repetition detection
* **Compliance**: Disclosure completeness, prohibited language, PII handling, prompt injection resistance
* **Call Quality**: Speech quality scoring (DNSMOS), accent detection, voicemail handling
See the full list of 65+ system metrics in the [System Metrics Reference](/documentation/metrics/system-metrics). System metrics are read-only in Studio until you fork them with **Customize for your team**, which creates an org-scoped variant you can edit.
### Custom Metrics
Define your own metrics in [Studio](/documentation/metrics/studio) by picking one of three engines:
| Engine | What it does | Example |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| **LLM Judge** | Scores each call against a natural-language prompt, evaluated by **Roark Prism**, our purpose-built evaluation model for voice AI conversations | "Did the agent verify the caller's identity?" → Boolean |
| **Pattern** | Composes triggers, outcomes, and time windows to detect when something happens in a call | Fire when the agent mentions a refund within 30s of a complaint |
| **Formula** | Computes a value from other metrics into a composite score | Weighted quality score across empathy, resolution, and latency |
LLM Judge metrics return typed results:
* "Did the agent verify the caller's identity?" → **Boolean**
* "Rate the agent's empathy on a scale of 1-10" → **Scale**
* "What was the primary reason for the call?" → **Classification**
* "How many times did the agent attempt to upsell?" → **Count**
Custom metrics are created in Studio's Author mode (**New metric** on the Metrics page) or via the [SDK](/documentation/metrics/custom-metrics#create-a-metric-definition). See [Custom Metrics](/documentation/metrics/custom-metrics) for the full authoring guide.
***
## The Metrics Page
The Metrics page (**Measure → Metrics**) is home base for measurement:
* **Collectors strip**: each collector runs a set of metrics on the calls its segment matches. Compact cards show each collector's name, segment, and metrics, with a **Manage collectors** link and a **New collector** tile.
* **Library**: everything this project can measure, grouped by package. Filter with the **All / System / Custom** pills or search; clicking a metric opens it in Studio for editing and testing.
* **New metric**: jumps straight into Studio's Author mode.
***
## How It All Fits Together
Here's the typical sequence from defining a metric to seeing results:
Use a built-in [system metric](/documentation/metrics/system-metrics), or [create your own custom metric](/documentation/metrics/custom-metrics) with an LLM Judge prompt, a Pattern, or a Formula.
Author mode's test rail lets you [run your metric against real calls](/documentation/metrics/studio): add test calls and hit **Run all** to validate it produces the results you expect. Iterate on the prompt until you're satisfied. To compare many metrics across many calls at once, use Evaluate mode's **Run battery**.
Set [pass/fail criteria](/documentation/metrics/thresholds) on your metric, for example, `Customer Satisfaction >= 7` or `Response Time < 1000ms`. Thresholds turn raw values into actionable outcomes.
Choose how and when your metric runs:
* **[Collectors](/documentation/metrics/metric-collectors)**: Automate collection on incoming calls (monitoring). Add conditions to target specific agents, sources, or call properties; leave them empty to match every call.
* **[Simulation plans](/documentation/simulation-testing/run-plans)**: Attach metrics with thresholds to simulation runs to validate agent behavior before deployment.
* **[Collection Jobs](/documentation/metrics/metric-collection-jobs)**: Run metrics on demand against existing calls via the SDK, useful for backfilling or re-processing.
View metric values per call in [Call History](/documentation/observability/live-monitoring), aggregate them in [Reports](/documentation/observability/reports), and organize everything in [Dashboards](/documentation/observability/dashboards). Group the calls you care about into [Datasets](/documentation/metrics/datasets) for evaluation and comparison.
***
## Quick Start Examples
The REST API and SDKs keep the older name for collectors: the SDK client is `client.metricPolicy.*` and the endpoints live under `/v1/metric/policies`. In the UI, these are **Collectors**.
System metrics like `response_time` are already collected for every call. To set a quality bar, create a collector with a threshold:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const collector = await client.metricPolicy.create({
name: 'Agent Response Time SLA',
status: 'ACTIVE',
conditions: [
{
conditions: [
{
conditionType: 'AGENT',
conditionKey: 'your-agent-id',
},
],
},
],
metrics: [
{
id: 'response-time-metric-id',
threshold: {
operator: 'LESS_THAN',
value: 1000,
aggregationMode: 'P95',
participantRole: 'AGENT',
},
},
],
})
```
Calls where the agent's P95 response time exceeds 1 second are automatically flagged as failures.
Define a business-specific metric and add it to a collector. Roark Prism evaluates each call against your prompt:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// 1. Create the metric definition
const metric = await client.metric.createDefinition({
name: 'Appointment Booked',
outputType: 'BOOLEAN',
analysisPackageId: 'your-package-id',
llmPrompt: 'Did the agent successfully book an appointment for the caller?',
booleanTrueLabel: 'Booked',
booleanFalseLabel: 'Not Booked',
})
// 2. Add it to a collector so it runs on every call
const collector = await client.metricPolicy.create({
name: 'Appointment Booking Check',
status: 'ACTIVE',
metrics: [{ id: metric.data.id }],
})
```
Test your prompt in [Studio](/documentation/metrics/studio) first to validate it produces the results you expect.
Attach metrics with thresholds to a simulation plan to validate agent behavior before deployment. Run the simulation, and each call is scored against your pass/fail criteria:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// Run metrics against simulation calls after they complete
const job = await client.metricCollectionJob.create({
callIds: ['sim-call-1', 'sim-call-2', 'sim-call-3'],
metrics: [
{ id: 'task-completion-metric-id' },
{ id: 'compliance-check-metric-id' },
],
})
// Check results
const result = await client.metricCollectionJob.getByID(job.data.id)
console.log(`${result.data.completedItems}/${result.data.totalItems} processed`)
```
Or configure metrics with [thresholds](/documentation/metrics/thresholds) directly in a [simulation plan](/documentation/simulation-testing/run-plans) from the dashboard.
***
## Sections
Browse all 65+ built-in metrics powered by specialized models
Create custom metrics with LLM Judge prompts, patterns, and formulas
Author metrics and run evaluation batteries against real calls
Run sets of metrics on the calls each collector's segment matches
Define pass/fail criteria for your metrics
Group calls or chats for evaluation, comparison, and analysis
Run metrics on demand for existing calls via the SDK
# Studio
Source: https://docs.roark.ai/documentation/metrics/studio
Author, test, and evaluate metrics in one workbench before they run on live traffic
Studio is the metric workbench: build metrics, test them against real calls, and run evaluation batteries, all before anything touches production traffic. Find it in the sidebar under **Measure → Studio**.
Studio replaces the Playground. If you're looking for the old Playground docs, everything it did (and more) now lives here.
Studio has three modes:
| Mode | What it's for |
| ------------ | ----------------------------------------------------------------------------------- |
| **Hub** | The landing page: start authoring or evaluating, and revisit recent evaluation runs |
| **Author** | Define or edit a single metric, with a test-call rail and version history |
| **Evaluate** | Run a battery of metrics across a set of calls and compare results side-by-side |
***
## The hub
Opening Studio lands you on the hub ("Build metrics. Run evaluations.") with two start cards:
* **Define a new metric** opens Author mode with a blank metric.
* **Run a battery of evals** opens Evaluate mode.
Below the cards, **Recent evaluation runs** lists your past batteries (e.g. "3 metrics × 5 calls") with a status chip: In progress, All passing, "N failing", Canceled, or Failed. Reopen any run to see its full result matrix, or fork it into a new evaluation.
***
## Author mode
Author mode is the metric editor. You reach it from the hub, from the **New metric** button on the Metrics page, or by clicking any metric in the [Library](/documentation/metrics/overview).
The editor walks you through four numbered sections:
Pick how the metric is computed:
| Engine | How it works |
| ------------- | ----------------------------------------------------------------------------- |
| **LLM Judge** | Scores each call against your natural-language prompt |
| **Pattern** | Composes triggers, outcomes, and time windows. Fires when the pattern matches |
| **Formula** | Math over other metrics |
The engine is **locked after the first save**. To measure the same thing with a different engine, create a new metric.
Choose the result type. Some types need extra configuration:
| Output | Returns | Extra config |
| ------------------ | ---------------------- | ------------------------ |
| **Boolean** | Yes / No | True and False labels |
| **Scale** | A score in a range | Min/Max plus band labels |
| **Classification** | One of your categories | Category list |
| **Numeric** | A number | A unit |
| **Count** | An occurrence count | A unit |
| **Text** | Free-form text | None |
The third section adapts to your engine. For **LLM Judge**, write instructions tuned to the output type: for a Boolean metric, the yes/no question the model should answer; for a Scale, what each band means. For **Pattern**, define the logic: when should this metric fire? For **Formula**, define the computation over other metrics.
Assign the metric to a group (analysis package). Groups organize the Library on the Metrics page, so pick one your team will look for it under.
### Test against real calls
The right-hand rail is your test bench. Use **Add test calls** to pick the calls Studio should evaluate the metric against, then run them individually or hit **Run all**. Each run streams through the evaluation pipeline live, so you can watch the result land and tighten your instructions between runs.
Iterate here until the metric returns what you expect on calls you already know the answer for, before you apply it to live traffic.
### Version history
Every save publishes a new version. The **History** tab in the rail lists them all: view any past version, or restore one, which publishes a new version from the old one (you never lose the intermediate history).
### System metrics and variants
[System metrics](/documentation/metrics/system-metrics) open read-only in Author mode. To change one, click **Customize for your team**. This forks an org-scoped variant you can freely edit and save. The Roark-managed original stays intact, and your instructions go in an "Additional instructions" field layered on top of the system prompt.
### Editing a metric that's already live
If the metric is bound to collectors, a notice appears above the form: **"In use by N collectors: saving publishes a new version every collector will pick up."**
Saving a metric republishes it everywhere it runs. Every collector using the metric picks up the new version for calls scored from that point on. Test your changes in the rail before saving.
***
## Evaluate mode
Evaluate mode runs a battery of metrics across a set of calls and compares results side-by-side. Use it to validate a metric at scale, compare candidate metrics against each other, or spot-check quality across a sample of production calls.
Use **Add metrics** to choose one or more metrics from your Library. You can attach a pass/fail threshold inline per metric (e.g. "Pass if ≥ 0.7") to turn raw scores into verdicts. See [Thresholds](/documentation/metrics/thresholds).
Use **Add calls** to select the conversations to evaluate.
Hit **Run battery**. Results stream in as a comparison **matrix** (metrics × calls) or grouped **by call**. Switch views to suit the question you're asking. You can cancel a run in progress.
Finished batteries appear under **Recent evaluation runs** on the hub, so you can reopen the matrix later or fork the run into a new evaluation.
Prefer to run evaluations programmatically? [Metric collection jobs](/documentation/metrics/metric-collection-jobs) are the API equivalent: run a set of metrics across a set of calls via the SDK or REST.
***
## Apply a metric to live calls
Testing in Studio scores only the calls you pick. To score matching calls automatically as they come in, bind the metric to a collector. The Author mode footer (**"Apply this metric to live calls"**) has an **Add collector** button that opens the collector editor with your metric prefilled and the modality locked to what the metric supports.
Once bound, the footer shows **"Running on N collectors"** instead. See [Collectors](/documentation/metrics/metric-collectors) for how conditions and scoping work.
The REST API and SDKs keep the older name for collectors: endpoints live under `/v1/metric/policies` and the SDK client is `client.metricPolicy.*`.
***
## Next steps
Deep-dive on LLM Judge, Pattern, and Formula metric types
Run your metrics automatically on matching live calls
Turn scores into pass/fail verdicts
Run evaluations at scale via the API
# System Metrics Reference
Source: https://docs.roark.ai/documentation/metrics/system-metrics
Complete reference for all built-in system metrics powered by specialized models
Roark ships with a comprehensive set of **system metrics** that are automatically available in every project. These metrics are powered by purpose-built, specialized models (not generic LLMs) designed to extract precise signal from conversational audio and transcripts.
System metrics require no configuration. Add them to an [analysis package](/documentation/metrics/custom-metrics), attach a [metric collector](/documentation/metrics/metric-collectors), and start collecting data immediately.
All system metrics listed below are powered by **specialized models** purpose-built for voice AI analysis. This means they are faster, more consistent, and more cost-effective than general-purpose LLM evaluation.
***
## Metric Types Overview
Roark supports four ways to define metrics. System metrics use the first type, and you can create your own using any of the four:
| Type | How it works | Use case |
| :------------------------------ | :---------------------------------------------------------------------------- | :----------------------------------------------------------------- |
| **System (Specialized Models)** | Purpose-built models analyze audio and transcript signals | Performance, interruptions, sentiment, compliance, call quality |
| **LLM as Judge** | An LLM evaluates the conversation against a natural-language prompt you write | Custom business logic, subjective quality checks, task completion |
| **Pattern** | Regex or keyword matching against transcript text | Detecting specific phrases, prohibited words, required disclosures |
| **Formula** | Combine existing metrics using boolean logic and weighted expressions | Composite scores, pass/fail rules based on multiple metrics |
### LLM as Judge
Define a metric with a natural-language prompt. **Roark Prism**, our evaluation model optimized for voice AI, scores each call against your prompt and returns a typed result (boolean, scale, classification, count, etc.).
```
"Did the agent verify the caller's identity before proceeding?" → Boolean
"Rate the agent's empathy on a 1-5 scale" → Scale
"What was the primary call reason?" → Classification
```
Create LLM as Judge metrics in the [dashboard](/documentation/metrics/custom-metrics) or via the [SDK](/documentation/metrics/custom-metrics#create-a-metric-definition).
### Pattern Detection
Match specific patterns in the transcript using keywords or regex. Useful for detecting required phrases, prohibited language, or specific conversational markers without LLM overhead.
### Formula Metrics
Combine multiple metrics into a single composite score using boolean logic and weighted expressions. For example, define a "Call Success" metric that requires `frustration_score < 3 AND instruction_follow = TRUE`.
Formula metrics let you build layered quality gates from your existing metrics without writing any code. Learn more about creating metrics in [Custom Metrics](/documentation/metrics/custom-metrics).
***
## System Metrics Reference
All system metrics below are collected automatically when included in an analysis package. Each metric shows its output type, scope, and the specialized model that powers it.
**Scope legend:**
* **Global**: one value per call
* **Per-participant**: separate values for agent and customer
***
### Core Analysis
Timing and interaction metrics extracted from audio diarization and transcript alignment.
Powered by **Roark Vibe**, our core voice analysis model.
| Metric | Description | Output | Scope |
| :--------------------- | :------------------------------------------------------------------------------------------------------------ | :---------------- | :-------------- |
| `call_duration` | Total duration of the call | Numeric (seconds) | Global |
| `response_time` | Time between speaking turns | Numeric (seconds) | Per-participant |
| `time_to_first_word` | Time from call start to first spoken word | Numeric (seconds) | Per-participant |
| `silence_duration` | Duration of each silence period | Numeric (seconds) | Per-participant |
| `turn_duration` | Duration of each speaking turn | Numeric (seconds) | Per-participant |
| `word_count` | Number of words spoken | Count | Per-participant |
| `talk_to_listen_ratio` | Ratio of time a participant spends talking vs total call duration | Numeric | Per-participant |
| `speaking_rate` | Words spoken per minute by a participant | Numeric (wpm) | Per-participant |
| `turn_count` | Number of speaking turns by a participant | Count | Per-participant |
| `latency_spike_count` | Number of response gaps exceeding 3 seconds | Count | Per-participant |
| `longest_pause` | Longest gap between consecutive segments in the call | Numeric (seconds) | Global |
| `agent_responsive` | Whether the agent held up its end of the conversation rather than going silent or erroring. Simulations only. | Boolean | Global |
| `agent_spoke` | Whether the agent spoke at all during the call. Simulations only. | Boolean | Global |
***
### Sentiment & Emotion
Emotion and sentiment analysis from vocal features and acoustic signals.
Powered by **Hume Expression Measurement**, a specialized vocal emotion model.
| Metric | Description | Output | Scope |
| :----------------- | :----------------------------------------------------------- | :------------- | :-------------- |
| `sentiment_score` | Sentiment rating on a 1–9 scale (1 = negative, 9 = positive) | Scale (1-9) | Per-participant |
| `emotion_label` | Detected emotion label from 64+ emotions | Classification | Per-participant |
| `dominant_emotion` | Most frequent emotion across the call | Classification | Per-participant |
| `vocal_cue_label` | Detected vocal cue or expression label | Classification | Per-participant |
***
### Interruptions
Detailed interruption and overlap analysis from speaker diarization.
Powered by **Roark Interruptions**, a specialized overlap detection model.
| Metric | Description | Output | Scope |
| :----------------------------------- | :----------------------------------------------------------------------------- | :---------------- | :-------------- |
| `interruption` | Whether overlapping speech occurred on a segment | Boolean | Per-participant |
| `interruption_duration` | Duration of overlapping speech | Numeric (seconds) | Per-participant |
| `interruption_count` | Total number of interruptions | Count | Global |
| `first_interruption_time` | Time into call of first interruption | Offset (seconds) | Global |
| `overtalk_ratio` | Ratio of overlapping speech duration to total call duration | Numeric | Global |
| `agent_interruption_count` | Number of times the agent interrupted the customer | Count | Global |
| `incorrect_agent_interruption_count` | Number of agent interruptions classified as inappropriate | Count | Global |
| `incorrect_interruption_rate` | Proportion of agent interruptions that were inappropriate | Scale (0-1) | Global |
| `customer_barge_in_count` | Number of times the customer attempted to interrupt the agent | Count | Global |
| `failed_barge_in` | Whether a customer interruption attempt failed because the agent did not yield | Boolean | Per-participant |
| `failed_barge_in_count` | Number of customer interruption attempts where the agent did not yield | Count | Global |
| `failed_barge_in_rate` | Proportion of customer interruption attempts that failed | Scale (0-1) | Global |
| `interruption_appropriateness` | Whether an agent interruption was appropriate based on conversational context | Boolean | Per-participant |
| `pre_interruption_speaker_duration` | How long the interrupted speaker had been talking before being interrupted | Numeric (seconds) | Per-participant |
| `agent_cutoff` | Whether the agent started speaking while the customer was mid-sentence | Boolean | Per-participant |
| `agent_cutoff_count` | Number of times the agent cut off the customer mid-sentence | Count | Global |
***
### Quality
Experience quality scoring from conversational signals.
Powered by **Roark Quality Analysis** and **Roark Prism**, specialized models for quality assessment.
| Metric | Description | Output | Scope |
| :---------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :-------------- |
| `frustration_score` | Customer frustration level (1 = none, 5 = severe) | Scale (1-5) | Per-participant |
| `user_effort_score` | How much effort the customer exerted to accomplish their goal (1 = effortless, 5 = very difficult) | Scale (1-5) | Per-participant |
| `call_outcome` | Overall outcome: Resolved, Unresolved, Escalated, Dropped, or Follow-up Required | Classification | Global |
| `instruction_follow` | How well the agent followed its given instructions (1 = not followed, 5 = fully followed) | Scale (1-5) | Global |
| `redundant_question_count` | Questions where the agent asked for information already provided | Count | Global |
| `missed_response_count` | Moments where a participant should have responded but did not | Count | Per-participant |
| `comprehension_failure` | Whether the agent misunderstood what the customer said | Boolean | Per-participant |
| `comprehension_failure_count` | Number of times the agent misunderstood the customer | Count | Global |
| `customer_reception` | How well the customer received the interaction overall, folded from frustration and effort signals (1 = very poor, 5 = excellent) | Scale (1-5) | Per-participant |
| `conversation_flow_score` | Naturalness of the conversational exchange: turn-taking, pacing, responsiveness, absence of awkward silences (1 = poor, 5 = natural) | Scale (1-5) | Global |
| `scenario_adherence` | Flow Adherence: how closely the simulated customer followed its scripted flow. Simulations only. | Scale (1-5) | Global |
| `agent_expectations` | Whether the agent met every expectation authored on the customer flow, graded one verdict per expectation and rolled up all-or-nothing. Simulations only. | Boolean | Global |
***
### Repetition Detection
Conversational loop and repetition analysis.
Powered by **Roark Prism**, our evaluation model optimized for voice AI.
| Metric | Description | Output | Scope |
| :------------------- | :----------------------------------------------- | :------ | :-------------- |
| `repetition_density` | Ratio of repeated turns to total turns (0–1) | Numeric | Per-participant |
| `loop_count` | Number of distinct conversational loops detected | Count | Per-participant |
***
### Tool Invocations
Analysis of function/tool calling behavior during conversations.
Powered by **Roark Vibe** and **Roark Prism**.
| Metric | Description | Output | Scope |
| :----------------------------------- | :------------------------------------------------------------------- | :------ | :----- |
| `tool_invocation_count` | Total number of tool/function calls made during the conversation | Count | Global |
| `tool_invocation_correct` | Whether the agent invoked the correct tools at the appropriate times | Boolean | Global |
| `tool_invocation_order_correct` | Whether tools were called in the correct logical sequence | Boolean | Global |
| `tool_invocation_parameters_correct` | Whether correct parameters were passed to each tool invocation | Boolean | Global |
| `tool_invocation_result_correct` | Whether the agent correctly interpreted and used tool results | Boolean | Global |
***
### Compliance
Regulatory and safety evaluation metrics for AI agent conversations.
Powered by **Roark Prism**, customizable with your own compliance requirements.
| Metric | Description | Output | Scope |
| :--------------------------------------- | :--------------------------------------------------------------------------------------------------------- | :---------- | :----- |
| `compliance_disclosure_completeness` | Whether all required disclosures were delivered (recording notice, AI identity, licensing) | Scale (1-5) | Global |
| `compliance_prohibited_language` | Whether the agent used prohibited language (unauthorized guarantees, medical/legal advice, discrimination) | Boolean | Global |
| `compliance_pii_handling` | How properly the agent handled personally identifiable information | Scale (1-5) | Global |
| `compliance_consent_collection` | Whether required consent was obtained before data collection or recording | Boolean | Global |
| `compliance_escalation_adherence` | Whether the agent properly escalated to a human when required | Boolean | Global |
| `compliance_scope_adherence` | Whether the agent stayed within its defined scope of topics | Scale (1-5) | Global |
| `compliance_prompt_injection_resistance` | Whether the agent resisted attempts to override its instructions or jailbreak | Boolean | Global |
| `compliance_identity_consistency` | Whether the agent maintained its assigned identity and disclosed its AI nature | Boolean | Global |
| `compliance_hallucination_boundary` | Whether the agent avoided fabricating information and deferred when unsure | Scale (1-5) | Global |
***
### Voicemail Detection
Voicemail detection and handling quality assessment.
Powered by **Roark Prism**.
| Metric | Description | Output | Scope |
| :----------------------------- | :---------------------------------------------------------------------------- | :---------- | :----- |
| `voicemail_detected` | Whether the call reached a voicemail system rather than a live person | Boolean | Global |
| `voicemail_agent_left_message` | Whether the agent left a voicemail message | Boolean | Global |
| `voicemail_handling_score` | Quality of voicemail handling (beep detection, message clarity, completeness) | Scale (1-5) | Global |
***
### Call Screening
Detects when a screener answers an outbound call instead of the person being called: a receptionist, an assistant, a household gatekeeper, or an automated screen.
Powered by **Roark Prism**.
| Metric | Description | Output | Scope |
| :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------- | :----- |
| `call_screening_encountered` | Whether the call was answered by someone screening it rather than the person being called | Boolean | Global |
| `call_screening_handling_score` | How well the agent handled being screened: recognising the gatekeeper, answering their questions directly, staying concise (1 = ran its script at the screener, 5 = handled cleanly) | Scale (1-5) | Global |
***
### Accent Detection
English accent identification from audio signals.
Powered by **Roark Accent ID**, a specialized accent classification model supporting 16 English accent variants.
| Metric | Description | Output | Scope |
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------- | :-------------- |
| `accent` | Detected English accent (US, British, Australian, Canadian, Indian, Irish, Scottish, Welsh, African, New Zealand, Hong Kong, Malaysian, Philippine, Singaporean, Bermudian, South Atlantic) | Classification | Per-participant |
| `accent_stability` | How stable the detected accent is across segments (1.0 = consistent, lower = varies) | Numeric (0-1) | Per-participant |
For a detailed walkthrough on using accent metrics, see the [Accent Detection recipe](/documentation/recipes/accent-detection).
***
### Call Quality (DNSMOS)
Speech quality assessment using the ITU-T P.808/P.835 Mean Opinion Score (MOS) scale.
Powered by **Roark DNSMOS**, a specialized speech quality model based on the ITU-T standard.
| Metric | Description | Output | Scope |
| :-------------------------- | :-------------------------------------------------------------------------------------------------- | :---------- | :----- |
| `speech_quality_overall` | Overall perceived speech quality (P.835 OVRL). Combines signal and background noise quality. | Scale (1-5) | Global |
| `speech_quality_signal` | Quality of the speech signal itself (P.835 SIG). Measures distortion, codec artifacts, and clarity. | Scale (1-5) | Global |
| `speech_quality_background` | Background environment quality (P.835 BAK). Higher = cleaner background. | Scale (1-5) | Global |
| `speech_quality_mos` | ITU-T P.808 Mean Opinion Score. Single overall quality rating from the audio signal. | Scale (1-5) | Global |
***
### Voice Naturalness
Acoustic naturalness of the agent's synthesized voice.
Powered by **Roark UTMOS**, a specialized naturalness model producing a mean opinion score.
| Metric | Description | Output | Scope |
| :------------------ | :---------------------------------------------------------------------------- | :---------- | :---------------------- |
| `voice_naturalness` | How human-like vs robotic the agent's voice sounds (1 = robotic, 5 = natural) | Scale (1-5) | Per-participant (agent) |
***
### Voice Human-Likeness
Perceptual human-likeness judged from the agent's own audio.
Powered by an audio-input evaluation model listening to the agent's actual speech.
| Metric | Description | Output | Scope |
| :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | :---------- | :---------------------- |
| `voice_human_likeness` | Whether the agent could pass as a human speaker: prosody, breathing, disfluencies, delivery (1 = clearly synthetic, 5 = indistinguishable from human) | Scale (1-5) | Per-participant (agent) |
***
### Call Environment
Zero-shot acoustic classification of the caller's environment.
Powered by a specialized audio classification model.
| Metric | Description | Output | Scope |
| :----------------- | :----------------------------------------------------------------------- | :------ | :------------------------- |
| `in_car_detection` | Whether the customer appears to be speaking from inside a moving vehicle | Boolean | Per-participant (customer) |
***
### Pronunciation Analysis
Per-word pronunciation checking against expected pronunciations you configure.
Powered by **Roark Phoneme**, a wav2vec2-based phoneme recognition model. Configure the word list and strictness per project.
| Metric | Description | Output | Scope |
| :---------------------------- | :--------------------------------------------------------------------------------------------------------------------- | :----------------------------------- | :---------------------- |
| `pronunciation_correctness` | Whether the agent pronounced each configured word correctly, one result per spoken occurrence | Classification (correct / incorrect) | Per-participant (agent) |
| `pronunciation_word_coverage` | Fraction of your configured words the agent actually said on the call (0 = none came up, 1 = all spoken at least once) | Numeric (0-1) | Per-participant (agent) |
***
### Property Verification
Checks the call properties you send at ingest (customer name, account number, appointment time, and so on) against what was actually said on the call, and flags values that conflict. Useful for catching stale CRM data, transcription errors, and callers who are not who the metadata says they are.
Powered by **Roark Prism**. Requires [custom properties](/documentation/observability/overview) on the call; system-generated properties are never checked.
| Metric | Description | Output | Scope |
| :----------------------------------- | :--------------------------------------------------------------------------------------- | :------ | :----- |
| `property_transcript_mismatch` | Whether any call property conflicted with what was said on the call | Boolean | Global |
| `property_transcript_mismatch_count` | How many properties conflicted with the transcript (0 = everything that came up matched) | Count | Global |
Every property is judged with a three-way verdict:
* **MATCH**: the conversation referred to this property and agrees with the value you sent, allowing for formatting differences, nicknames, partial references, and transcription noise.
* **MISMATCH**: the conversation states a genuinely different value.
* **NOT\_MENTIONED**: the subject never came up. This is the most common outcome and is not counted as a mismatch.
The full per-property breakdown is returned on the [call metrics endpoint](/api-reference/call/list-call-metrics) as a `propertyVerdicts` array on the metric value: each entry carries the property name, the expected value, the verdict, the observed value for mismatches, the judge's reasoning, and the transcript segment where the property was referred to. The same breakdown renders on the call detail page in the platform.
***
## What's Next
Create custom LLM as Judge, Pattern, and Formula metrics
Test metrics interactively against real calls
Automate metric collection with conditions-based rules
Define pass/fail criteria for your metrics
# Thresholds
Source: https://docs.roark.ai/documentation/metrics/thresholds
Define pass/fail criteria for your metrics
## Overview
Thresholds turn raw metric values into clear **pass/fail outcomes**. When you add a threshold to a metric, Roark mints a derived boolean metric that automatically evaluates whether each conversation meets your criteria.
For example, a threshold of `>= 7` on `Customer Satisfaction` produces a Pass whenever the score is 7 or above, and a Fail otherwise.
***
## How Thresholds Work
Each threshold is a **derived metric** in its own right:
1. The source metric is collected as usual (e.g., a satisfaction score of `8`)
2. The threshold compares the value against your condition (e.g., `>= 7`)
3. A boolean result is produced with the labels **Pass** and **Fail**
4. The derived metric is stored alongside the original, so both show up in reporting
The derived metric is named after its rule (`Customer Satisfaction >= 7`) and uses the `THRESHOLD` calculation type with a Boolean output. Because it's a real metric definition, the same threshold can be reused across collectors.
Thresholds are available on these metric output types: **Scale**, **Numeric**, **Count**, **Boolean**, and **Classification**.
***
## Adding a Threshold
Thresholds can be configured from several places in Roark:
* **[Collectors](/documentation/metrics/metric-collectors)**: Each picked metric in the collector editor has a **Pass/Fail Threshold(s)** block with an **Add pass/fail threshold** button
* **[Studio](/documentation/metrics/studio)**: In Evaluate mode, configure thresholds inline per metric before running a battery of evals
* **[Simulation Run Plans](/documentation/simulation-testing/run-plans)**: Set pass/fail checks for simulation testing
The configuration is the same everywhere: each threshold row reads **"Pass if \[operator] \[value]"**.
### Reusing Saved Thresholds
Because thresholds are metric definitions, ones you've already created appear as read-only rows with an **Include if** checkbox. Tick it to opt an existing threshold into the current collector. If you try to add a rule that matches a saved threshold, Roark warns "Matches an existing threshold: it won't be created again" and reuses the existing one instead of creating a duplicate.
***
## Operators
The available operators depend on the metric's output type.
### Numeric Types (Scale, Numeric, Count)
| Operator | Symbol | Example |
| :--------------------- | :----- | :------------------------ |
| Greater than | `>` | Score `>` 5 |
| Greater than or equals | `>=` | Score `>=` 7 |
| Less than | `<` | Response time `<` 3000ms |
| Less than or equals | `<=` | Response time `<=` 2000ms |
| Equals | `=` | Count `=` 0 |
| Not equals | `!=` | Count `!=` 0 |
### Categorical Types (Boolean, Classification)
| Operator | Symbol | Example |
| :--------- | :----- | :---------------------------- |
| Equals | `=` | Compliance check `=` true |
| Not equals | `!=` | Call outcome `!=` "escalated" |
***
## API-Only Options
The UI keeps threshold rows simple: an operator and a value. The REST API supports two additional options when creating thresholds programmatically.
These options are set via the API's `CreateThresholdInput` and are not exposed in the Collectors or Studio UI.
### Aggregation Modes
When a metric produces multiple values per call (e.g., a per-segment metric that fires on every turn), the `aggregationMode` field determines how those values are combined before the comparison is applied.
| Mode | Description | Example |
| :---------- | :-------------------------------------------------------------------------- | :-------------------------------------- |
| **Each** | Compare every value individually. Fails if any single value fails (default) | Each response time `<` 5000ms |
| **Average** | Average all values, then apply the threshold | Average sentiment `>=` 6 |
| **Min** | Use the minimum value | Min confidence `>=` 0.8 |
| **Max** | Use the maximum value | Max response time `<` 10000ms |
| **Median** | Use the median value | Median score `>=` 7 |
| **Sum** | Sum all values, then apply the threshold | Total talk time `<=` 300000ms |
| **P95** | Use the 95th percentile value | P95 response time `<` 5000ms |
| **P99** | Use the 99th percentile value | P99 latency `<` 8000ms |
| **Count** | Count how many values match, and fire when `countThreshold` is met | No more than 2 segments below threshold |
### Participant Role Filtering
For metrics with `PER_PARTICIPANT` scope, the `sourceParticipantRole` field narrows the threshold to a specific speaker: `AGENT`, `CUSTOMER`, `SIMULATED_CUSTOMER`, or `BACKGROUND_SPEAKER`. This is useful when a metric like sentiment is tracked for both speakers but you only want to set a threshold on the agent's performance.
***
## Examples
**Source metric:** Customer Satisfaction (Scale 1-10)
**Operator:** Greater than or equals
**Value:** 7
**Result:** Pass if satisfaction is 7 or above, Fail otherwise. The derived metric is named `Customer Satisfaction >= 7`.
**Source metric:** Identity Verified (Boolean)
**Operator:** Equals
**Value:** true
**Result:** Pass if the agent verified the caller's identity
**Source metric:** Response Time (Numeric, per-segment)
**Operator:** Less than
**Value:** 1000 (milliseconds)
**Aggregation (API-only):** P95
**Participant Role (API-only):** Agent
**Result:** Pass if 95th percentile agent response time is under 1 second
**Source metric:** Tone Appropriate (Boolean, per-segment)
**Operator:** Equals
**Value:** true
**Aggregation (API-only):** Count (`countThreshold`: 2)
**Result:** Pass if no more than 2 segments have an inappropriate tone
***
## What's Next
Add thresholds to the metrics a collector runs
Test thresholds in Evaluate mode before deploying
Set pass/fail criteria for simulation testing
Learn about metric types, scopes, and output formats
# Dashboards
Source: https://docs.roark.ai/documentation/observability/dashboards
Organize multiple reports into a single view for comprehensive monitoring
## Overview
Dashboards allow you to organize and view multiple reports in a single place, giving you a comprehensive overview of your voice AI performance. Create separate dashboards for different teams, use cases, or monitoring needs.
***
## Creating Dashboards
* **Multiple Dashboards**: Create separate dashboards for different use cases (e.g., Production Monitoring, QA Review, Executive Summary)
* **Add Reports**: Select which saved [reports](/documentation/observability/reports) to include in each dashboard
* **Reorder**: Drag and drop reports to organize your dashboard layout
***
## Property Overrides
Filter an entire dashboard by specific call properties without creating new reports:
**Common Use Cases:**
* View all reports filtered to a specific business name or customer ID
* Analyze performance for a particular agent or integration
* Compare metrics across different property values
* Quickly switch between filtered views without leaving the dashboard
When you override properties at the dashboard level, all reports on that dashboard automatically update to reflect the filter, making it easy to drill down into specific segments of your data.
***
## Managing Dashboards
* **Default Dashboard**: Set which dashboard loads by default
* **Quick Access**: Switch between dashboards from the navigation
* **Update Reports**: Add, remove, or reorder reports as your needs change
***
## What's Next
Create the reports that power your dashboards
Monitor calls in real-time
# Live Monitoring
Source: https://docs.roark.ai/documentation/observability/live-monitoring
Browse, search, and analyze every voice AI call
## Overview
Call History is your central hub for browsing, searching, and analyzing every call that flows through Roark. Each call is automatically transcribed and analyzed the moment it arrives.
***
## Getting Calls Into Roark
### Via Integration
Connect a voice platform ([Vapi](/documentation/integrations/vapi), [Retell](/documentation/integrations/retell), or [LiveKit](/documentation/integrations/livekit)) and calls sync automatically. See [Integrations](/documentation/integrations/overview) for setup guides.
### Via API
Upload a call recording using the [Node.js](/documentation/sdks/node-sdk) or [Python](/documentation/sdks/python-sdk) SDK:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const call = await client.call.create({
recordingUrl: 'https://example.com/recording.mp3',
startedAt: '2024-01-15T10:00:00Z',
interfaceType: 'PHONE',
callDirection: 'INBOUND',
agent: { name: 'Support Agent' },
customer: { phoneNumberE164: '+15551234567' },
properties: { department: 'sales' },
})
```
Once created, the call appears in Call History and is automatically analyzed.
***
## What You See Per Call
Each call opens into a detailed view with the following tabs:
* **Overview**: AI-generated summary, key stats (duration, direction, status, end status), participants, and labels
* **Transcript**: Speaker-diarized conversation with timestamps, tool call annotations, and sentiment overlay
* **Metrics**: All collected metric values organized by package (call-level, segment-level, turn-level)
* **Properties**: Custom metadata passed with the call, filterable and searchable
* **Tools**: All tool/function invocations with inputs, outputs, and error details
You can turn real calls into [customer flows](/documentation/simulation-testing/customer-flows). When creating a flow, choose **From your calls** and Roark drafts the flow from the conversations you pick.
***
## Filtering and Search
Find exactly what you need:
* **Full-text transcript search** across all calls
* **Filters**: duration, end status, call type (live vs simulation), agent, labels, analysis status, and custom properties
* **Saved views** for quick access to frequently used filter combinations
***
## Deep Linking with URL Parameters
Share filtered views or link directly to relevant calls from external systems using URL query parameters. Custom properties can be filtered using dot notation:
```
/org/{org-slug}/{project-slug}/calls?props.{property_name}={operator}{value}
```
### Supported Operators
| Operator | Description | Example |
| -------------- | --------------------- | ----------------------------------- |
| `=` | Equals (default) | `props.status=completed` |
| `!=` | Not equals | `props.status=!=failed` |
| `>` | Greater than | `props.count=>10` |
| `>=` | Greater than or equal | `props.count=>=10` |
| `<` | Less than | `props.count=<5` |
| `<=` | Less than or equal | `props.count=<=5` |
| `contains:` | Contains | `props.customer_name=contains:John` |
| `starts_with:` | Starts with | `props.region=starts_with:us-` |
### Examples
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# Filter by exact match
/calls?props.simulation_run_id=abc-123
# Filter by contains
/calls?props.customer_name=contains:John
# Filter by not equals
/calls?props.status=!=failed
# Filter by numeric comparison
/calls?props.attempt_count=>3
# Combine multiple filters
/calls?props.simulation_run_id=abc-123&props.region=starts_with:us-
```
### Direct Call Linking
When a filter returns only a single matching call, Roark automatically opens that call's detail view. This enables direct deep linking to specific calls using unique identifiers:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# Link directly to a specific call using a unique custom property
/calls?props.custom_call_id=abc-123
```
This is particularly useful when you store your own call identifiers and want to link from external systems directly to the call details without an intermediate list view.
### Use Cases
* **Direct call access**: Link to a specific call using a unique identifier (e.g., `custom_call_id`); the call opens automatically when only one match is found
* **External system integration**: Link from CRMs, dashboards, or internal tools directly to relevant calls
* **Bookmarks**: Save filtered views for quick access to specific call subsets
* **Team sharing**: Share pre-filtered views with teammates for collaboration
***
## Collecting More Metrics
Roark automatically analyzes transcription, speech patterns, and sentiment for every call. To collect additional metrics (like custom LLM evaluations, compliance checks, or business KPIs) configure [metric collectors](/documentation/metrics/metric-collectors) or test metrics in [Studio](/documentation/metrics/studio).
Available out-of-the-box analysis includes:
* Sentiment tracking and 64+ emotion detection
* Interruption and overlap detection
* Speech pause and silence analysis
* Vocal cue detection (raised voice, frustration, etc.)
* Repetition detection
***
## What's Next
Define custom metrics for your calls
Automate metric collection
Build analytics from call data
Organize reports into views
# Overview
Source: https://docs.roark.ai/documentation/observability/overview
Monitor and analyze your voice AI conversations in real-time
Roark gives you full visibility into every voice AI conversation. Calls flow in via integrations or the API, metrics are collected automatically via policies, and you can explore everything in real-time.
## Quickstart
Send Roark a call and read what it measured:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const call = await client.call.create({
recordingUrl: 'https://example.com/recording.mp3',
startedAt: '2024-01-15T10:00:00Z',
interfaceType: 'PHONE',
callDirection: 'INBOUND',
agent: { name: 'Support Agent' },
})
const metrics = await client.call.listMetrics(call.data.id)
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
call = client.call.create(
recording_url="https://example.com/recording.mp3",
started_at="2024-01-15T10:00:00Z",
interface_type="PHONE",
call_direction="INBOUND",
agent={"name": "Support Agent"},
)
metrics = client.call.list_metrics(call.data.id)
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
CALL_ID=$(roark call create \
--recording-url "https://example.com/recording.mp3" \
--started-at "2024-01-15T10:00:00Z" \
--interface-type PHONE --call-direction INBOUND \
--agent '{"name":"Support Agent"}' | jq -r '.data.id')
roark call metric list "$CALL_ID"
```
Most teams connect a platform so production calls flow in automatically, see [Integrations](/documentation/integrations/overview).
***
## Key Components
Browse, search, and analyze every call
Define what to measure across calls
Build analytics from your metric data
OpenTelemetry tracing and backend correlation
Organize reports into views
***
## How Calls Get Into Roark
There are two ways to get calls into Roark:
### Integrations
Connect a voice platform ([Vapi](/documentation/integrations/vapi), [Retell](/documentation/integrations/retell), or [LiveKit](/documentation/integrations/livekit)) and calls sync automatically. See [Integrations](/documentation/integrations/overview) for setup guides.
### API / SDK
Upload call recordings directly via the [Node.js](/documentation/sdks/node-sdk) or [Python](/documentation/sdks/python-sdk) SDK, or the [REST API](/api-reference/introduction).
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const call = await client.call.create({
recordingUrl: 'https://example.com/recording.mp3',
startedAt: '2024-01-15T10:00:00Z',
interfaceType: 'PHONE',
callDirection: 'INBOUND',
agent: { name: 'Support Agent' },
customer: { phoneNumberE164: '+15551234567' },
properties: { department: 'sales' },
})
```
***
## What Happens When a Call Arrives
The call is transcribed and analyzed: speech patterns, sentiment, and 64+ emotions are detected automatically.
Active [metric collectors](/documentation/metrics/metric-collectors) run automatically, collecting the metrics you've configured.
The call appears in [Call History](/documentation/observability/live-monitoring) with full analysis, ready to explore.
# PII Redaction
Source: https://docs.roark.ai/documentation/observability/pii-redaction
Mask sensitive caller information in transcripts before it reaches your team or evaluators
Roark can detect and mask personally identifiable information (PII), phone numbers, social security numbers, credit cards, and more, in your call and chat transcripts. When redaction is on, the original PII never leaves the redaction boundary: viewers see masked tokens, evaluator LLMs receive masked text, and search results are sanitized.
Redaction is a per-project setting and applies to both **calls and chats** going forward. Existing conversations keep their original transcripts.
***
## How It Works
Whether the transcript came from your integration, your own upload, or Roark's transcription pipeline, redaction kicks in once the transcript is persisted.
A redaction pass identifies the entity types you've enabled (phone numbers, account numbers, addresses, names, etc.) and stores them as redaction spans alongside the transcript.
When you view a conversation, the transcript renders `[REDACTED:PHONE]`-style pills in place of the sensitive content. Evaluators that use LLMs (custom prompts, sentiment, toxicity, emotion, politeness) receive the masked transcript by default.
***
## Enabling Redaction
Go to your project's **Settings** page and find the **PII Redaction** section.
Flip the master toggle. The entity-type list expands.
Most entity types are on by default. Names and addresses are off by default: they have higher false-positive rates and customers usually opt in deliberately.
***
## Supported Entity Types
| Entity | Default | Notes |
| ------------------------------------- | ------- | ---------------------------------- |
| Social security numbers | On | |
| Credit card number / CVV / expiration | On | Each is a separate toggle |
| Bank account numbers | On | |
| Bank routing numbers | On | |
| Phone numbers | On | |
| Email addresses | On | |
| Dates of birth | On | |
| PINs | On | |
| Passwords | On | |
| Names | Off | Higher false-positive rate, opt-in |
| Addresses | Off | Higher false-positive rate, opt-in |
***
## Evaluators and Redaction
Evaluator LLM calls (custom prompts, sentiment, toxicity, emotion, politeness) automatically receive the **redacted** transcript when redaction is enabled. This keeps PII from leaving your tenant for analysis.
If your evaluators rely on the original PII to do their job (and you accept the trade-off) you can override this with the **Allow evaluators to see original PII** toggle in the same settings panel. We recommend leaving it off unless you have a specific reason.
***
## What's Stored
* The original transcript is preserved unchanged: storage is non-destructive.
* Redaction spans (entity type, segment, confidence, source) are stored as metadata so masking is consistent across every read path and reproducible if you change the policy later.
* Audio recordings are stored as-is. Audio-level redaction (beep/silence at PII timestamps) is on the roadmap.
***
## Limitations Today
* Masking is applied at the **segment** level when any PII is detected within it, so a turn containing one phone number renders as a single redaction pill, not inline word-level redaction. Word-precise inline masking is on the roadmap.
* Audio recordings are not yet redacted. If you need redacted audio (for sharing recordings outside your team), reach out. It's planned and we're prioritizing based on demand.
***
## Compliance and Boundaries
When redaction is enabled:
* The frontend renders masked tokens in transcripts and exports.
* The GraphQL API returns masked text by default for every segment-returning query (calls and chats).
* LLM evaluators receive masked text unless you explicitly opt out per project.
* OpenSearch event indexing is unaffected (event properties are structured, not transcript-derived).
If you have specific compliance requirements (HIPAA BAA, PCI scope, destructive retention of original transcripts), [reach out to support](/documentation/resources/support). We can scope what's needed for your environment.
# Reports
Source: https://docs.roark.ai/documentation/observability/reports
Analytics dashboards and insights for voice AI performance
## Overview
Reports allow you to chain multiple metrics together to create comprehensive analytics dashboards. Track trends, compare performance, and drill down into specific calls - all from a single view.
## What You Can Report On
### Events
Track occurrences of specific events in your system:
* `evaluation_failed` - When calls fail quality checks
* `task_completed` - Successful objective completion
* `escalation_triggered` - Customer requested human agent
* `tool_call_failed` - Function execution errors
* Custom events defined in your system
### Metrics
Analyze performance indicators over time:
* `response_time` - Agent response latency
* `sentiment_score` - Customer satisfaction trends
* `interruption_count` - Conversation flow issues
* `call_duration` - Efficiency metrics
* Any custom metrics you've defined
**Aggregation Options:**
* **Count** - Total number of occurrences
* **Average** - Mean value across calls
* **Sum** - Total combined value
* **Minimum** - Lowest recorded value
* **Maximum** - Highest recorded value
* **Median** - Middle value (50th percentile)
* **P90** - 90th percentile value
* **P95** - 95th percentile value
## Baseline Configuration
Set context for your data with flexible baseline options:
View data as a percentage against all your calls:
```
Failed evaluations: 12% of all calls
High frustration: 8% of all calls
```
Use another metric or event as your comparison:
```
Failed evaluations: 45% of high-frustration calls
Tool failures: 23% of escalated calls
```
Compare against the sum of another metric:
```
Agent response time: 32% of total call duration
Silence time: 18% of conversation time
```
## Visualization Options
### Line Graph
Track trends over time with detailed line charts:
Perfect for:
* Identifying patterns and trends
* Comparing multiple metrics
* Spotting anomalies
### Number Chart
Display key metrics as single values or comparisons:
Ideal for:
* Executive dashboards
* Quick status checks
* KPI monitoring
## Time Controls
Customize your view with flexible time options:
### Time Period
* Last 24 hours
* Last 7 days
* Last 30 days
* Last 90 days
* Custom date range
### Interval Grouping
* **Hour**: For real-time monitoring
* **Day**: For daily trends
* **Week**: For weekly patterns
* **Month**: For long-term analysis
* **Year**: For annual comparisons
## Chain Multiple Metrics
Create powerful insights by combining metrics:
Compare response\_time with sentiment\_score to find relationships
Stack multiple metrics to see the complete picture
Combine events with metrics for context (e.g., response\_time when evaluation\_failed)
Track how one metric affects another over time
## Drill-Down to Calls
Every data point connects to the actual conversations:
1. **Click any data point** on your report
2. **View matching calls** that contributed to that metric
3. **Navigate directly** to the call screen from [Live Monitoring](/documentation/observability/live-monitoring)
4. **Analyze the conversation** with full transcript and analysis
## Use Cases
### Performance Tracking
Monitor agent efficiency and quality:
* Average response times by agent
* Task completion rates over time
* Customer satisfaction trends
### Issue Detection
Identify problems before they escalate:
* Spike in failed evaluations
* Increase in customer frustration
* Pattern of tool failures
### Business Intelligence
Track metrics that matter to your business:
* Conversion rates
* Average handle time
* Cost per successful interaction
### Compliance Monitoring
Ensure adherence to standards:
* Script compliance percentage
* Required disclosure rates
* Quality score distributions
***
## What's Next
Organize multiple reports into a single view
Monitor calls in real-time
# Traces
Source: https://docs.roark.ai/documentation/observability/traces
Send OpenTelemetry traces to Roark for full visibility into your Voice AI agent
## Overview
Roark supports **OpenTelemetry (OTel) tracing**. Send your OTel trace data to Roark to see what happens under the hood of your Voice AI agent, debug latency, inspect tool usage and external API calls, and correlate call execution with your backend traces.
### Features
* **LLM traces per call**: View LLM spans, tool calls, and model invocations directly on the call detail page. Each call’s **Tracing** tab shows the full trace tree for that conversation, so you can quickly pinpoint where latency or failures occurred.
* **Central trace explorer**: See all traces in one place. Filter by time range, custom tags, or search by span name and attributes. Use this to spot patterns across calls, compare runs, and troubleshoot recurring issues.
***
***
## Endpoint and Protocol
| Requirement | Details |
| ------------------- | ------------------------------------------------------------------------------------- |
| **Protocol** | OTLP over HTTPS only |
| **Endpoint** | Send traces to Roark's OTel endpoint (see examples below) |
| **OTel traces URL** | `https://api.roark.ai/v1/traces` |
| **Authorization** | **Required.** Send `Authorization: Bearer YOUR_ROARK_API_KEY` in the request headers. |
| **Role** | Roark acts as an OTel Collector |
All trace ingestion requests require authentication. [Generate an API
key](/documentation/getting-started/api-keys) in your Roark dashboard and use
it in the `Authorization: Bearer YOUR_ROARK_API_KEY` header.
***
## Setup Guide
Generate an API key in your Roark dashboard. Trace ingestion requires authentication via the `Authorization: Bearer YOUR_ROARK_API_KEY` header. [Create an API key →](/documentation/getting-started/api-keys)
Pick the platform you're using and follow the corresponding setup section below:
* [LiveKit](#livekit): instrument your LiveKit agent with OpenTelemetry
* [VAPI](#vapi): traces sync automatically when calls are ingested
* [Custom integration](#custom-integration): any other platform via OTLP HTTP
***
## LiveKit
**Instrument your LiveKit agent** with OpenTelemetry and export traces to Roark. Configure your tracer with the `livekit.room.id` resource attribute. This is how Roark links your LiveKit room to its traces and shows them on the call detail page.
See the [LiveKit integration guide](/documentation/integrations/livekit) for the full webhook setup.
Install the required packages:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http livekit-agents
```
Example: a simple [LiveKit Agent](https://docs.livekit.io/agents/) entrypoint with OTel exporting to Roark:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import os
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from livekit.agents import AgentServer, JobContext, cli
from livekit.agents.telemetry import set_tracer_provider
def setup_roark_tracer(ctx: JobContext):
"""Configure OTel to export traces to Roark with livekit.room.id for call correlation."""
resource = Resource.create({
"livekit.room.id": ctx.job.room.sid, # Required: Roark uses this to link traces to the call
"roark.skip": False, # Optional: set to True to filter out these traces
})
provider = TracerProvider(resource=resource)
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(
endpoint="https://api.roark.ai/v1/traces",
headers={"Authorization": f"Bearer {os.environ['ROARK_API_KEY']}"},
)
)
)
set_tracer_provider(provider)
# Ensure all pending traces are exported when the agent shuts down
async def flush_traces():
provider.force_flush()
ctx.add_shutdown_callback(flush_traces)
server = AgentServer()
@server.rtc_session(agent_name="my-agent")
async def entrypoint(ctx: JobContext):
# Initialize tracing early, before starting the agent session
setup_roark_tracer(ctx)
# Your agent logic here
pass
if __name__ == "__main__":
cli.run_app(server)
```
**Resource attributes:**
| Attribute | Required | Description |
| :---------------- | :------- | :---------------------------------------------------------------------------------------------------------- |
| `livekit.room.id` | Yes | The LiveKit room SID (`ctx.job.room.sid`). Roark uses this to link traces to the corresponding call. |
| `roark.skip` | No | Set to `true` to tell Roark to filter out traces for this room. Useful for skipping test or internal calls. |
If you're also using the [LiveKit webhook integration](/documentation/integrations/livekit), you can set `roark.skip` in both room metadata and OTel resource attributes to skip both call processing and trace ingestion.
***
## VAPI
**Traces sync automatically.** Whenever a call from a selected agent is synced to Roark, its OpenTelemetry traces are synced too. No extra OTLP exporter or instrumentation is needed on your side.
See the [VAPI integration guide](/documentation/integrations/vapi) for step-by-step setup.
Make sure **Public Logs** are enabled in your Vapi dashboard. Roark requires public log access to ingest trace data from Vapi calls.
***
## Custom Integration
For any other platform, use the OTLP HTTP trace exporter and point it to Roark's OTel endpoint. Include the **required** `Authorization: Bearer YOUR_ROARK_API_KEY` header and attach the required resource attributes.
### Correlating traces to a call or chat
If you submit calls or chats to Roark via the [Customer API](/api-reference/introduction), tag your traces with `roark.external_id` so Roark can link each conversation to its trace automatically.
1. When creating a call or chat via `POST /call` or `POST /chat`, supply the `externalId` field with a stable identifier from your own system (session ID, conversation ID, etc.). It must be unique within the project.
2. On your OpenTelemetry traces, set `roark.external_id` to the **same value**, either as a resource attribute (propagates to every span in the service) or as a span attribute on the root span.
Roark looks up the matching trace in ClickHouse after the call/chat is created and backfills the trace ID, so the conversation appears in its **Tracing** tab automatically.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
npm install @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources
```
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
const exporter = new OTLPTraceExporter({
url: "https://api.roark.ai/v1/traces",
headers: { Authorization: `Bearer ${process.env.ROARK_API_KEY}` },
});
// `roark.external_id` is the correlation key linking this trace to the
// call/chat you create with the same `externalId` via the Customer API.
const resource = new Resource({
"roark.external_id": yourSessionId,
"roark.project.tag.env": process.env.ROARK_ENV ?? "production",
});
const provider = new NodeTracerProvider({ resource });
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
go.opentelemetry.io/otel/sdk/trace
```
```go theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
package main
import (
"context"
"os"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func initTracer(sessionID string) (func(context.Context) error, error) {
exporter, err := otlptracehttp.New(context.Background(),
otlptracehttp.WithEndpoint("api.roark.ai"),
otlptracehttp.WithURLPath("/v1/traces"),
otlptracehttp.WithHeaders(map[string]string{
"Authorization": "Bearer " + os.Getenv("ROARK_API_KEY"),
}),
)
if err != nil {
return nil, err
}
// `roark.external_id` is the correlation key linking this trace to the
// call/chat you create with the same `externalId` via the Customer API.
res, err := resource.New(context.Background(),
resource.WithAttributes(
semconv.ServiceName("my-voice-agent"),
attribute.String("roark.external_id", sessionID),
attribute.String("roark.project.tag.env", "production"),
),
)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tp)
return tp.Shutdown, nil
}
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
import os
exporter = OTLPSpanExporter(
endpoint="https://api.roark.ai/v1/traces",
headers={"Authorization": f"Bearer {os.getenv('ROARK_API_KEY')}"},
)
# `roark.external_id` is the correlation key linking this trace to the
# call/chat you create with the same `externalId` via the Customer API.
resource = Resource(attributes={
"roark.external_id": your_session_id,
"roark.project.tag.env": os.getenv("ROARK_ENV", "production"),
})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
```
**Resource attributes:**
| Attribute | Required | Description |
| :------------------------ | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `roark.external_id` | Recommended | A stable identifier from your own system (e.g. session/conversation ID). Must match the `externalId` field you submit on the corresponding call or chat via the Customer API, and must be unique within a project. |
| `roark.project.tag.{key}` | No | Custom project tags for filtering and grouping in the trace explorer. |
***
Once your integration is set up, you're ready to send traces to Roark. You'll be able to view them on the Traces page and directly within each call's detail page.
***
## What's Next
View and debug active calls
Define and analyze call metrics
Build observability views
Explore Roark API endpoints
***
**Related:** [LiveKit integration](/documentation/integrations/livekit) · [VAPI integration](/documentation/integrations/vapi) · [Integrations overview](/documentation/integrations/overview)
# Accent Detection & TTS Drift Monitoring
Source: https://docs.roark.ai/documentation/recipes/accent-detection
Detect accents and flag when your agent's TTS voice drifts mid-call
## Overview
Accent Detection identifies which English accent each participant speaks with across every segment of a call. This is useful for:
* **TTS consistency monitoring**: Verify your agent's text-to-speech voice maintains the expected accent throughout the call
* **Accent drift detection**: Flag calls where the agent's accent shifted mid-conversation
* **Regional analysis**: Understand the accent distribution of your callers
The model classifies audio into 16 English accent variants: American, British, Australian, Canadian, Indian, Irish, Scottish, Welsh, African, New Zealand, Hong Kong, Malaysian, Philippine, Singaporean, Bermudian, and South Atlantic.
***
## Prerequisites
### 1. Enable the Accent Detection Package
Navigate to **Settings > Analysis Packages** and enable **Accent Detection**.
Accent detection requires both **audio conversion** and **diarization** artifacts. These are produced automatically when the package is enabled.
### 2. Understand the Metrics
The package includes two metrics:
| Metric | Type | What it measures |
| :------------------- | :------------- | :------------------------------------------------------------------------------------------------ |
| **Accent** | Classification | Detected accent per segment and dominant accent at call level, with full probability distribution |
| **Accent Stability** | Numeric (0–1) | How consistent the detected accent is across segments. 1.0 = same accent throughout |
***
## Recipe: Detect Agent TTS Accent Drift
This recipe sets up automatic monitoring to flag any call where your agent's TTS accent drifts from its expected voice.
### Step 1: Create a Collector
1. Go to **Metrics → Collectors**
2. Create a new collector or edit an existing one
3. Add the **Accent Stability** metric from the Accent Detection package
4. Configure a threshold:
* **Operator:** `>=`
* **Value:** `0.7`
* **Participant Role:** Agent
This evaluates every call and flags any where the agent's accent stability drops below 70%, meaning the detected accent changed for more than 30% of the agent's speaking time.
Start with a threshold of `0.7` and adjust based on your results. Some variation is normal. The model may oscillate between similar accents (e.g. American vs Canadian) on short segments. A threshold of `0.5` would only flag significant accent changes.
### Step 2: Review Results on the Call Detail Page
When a call is processed, open it and check the **Metrics tab**:
**Call-level accent card**: Shows the dominant accent per participant with a probability distribution. For example, if the agent spoke with an American accent for 70% of the call and British for 30%, you'll see both with their percentages.
**Segment-level probability chart**: Shows a stacked area chart of accent probabilities over time. This lets you see exactly *where* in the call the accent shifted. Use the participant filter to focus on the Agent.
### Step 3: Set Up Alerts
Once you've configured the threshold:
* **Calls that pass**: The agent maintained a consistent accent throughout
* **Calls that fail**: The agent's accent drifted beyond your tolerance, appearing as a failed threshold on the Overview tab
You can use [webhooks](/documentation/integrations/webhooks) to get notified when metric collection completes, then check the threshold results programmatically.
***
## How Accent Scores Work
### Per-Segment Scores
Each segment shows the accent probabilities after normalization. The model outputs raw probabilities across all 16 accents (softmax), but we filter out accents below the baseline (1/16 = 6.25%) and renormalize so the remaining scores sum to 100%.
For example, if the model outputs `American: 10%, British: 8%, Canadian: 7%` with everything else below 6.25%, the normalized scores become `American: 40%, British: 32%, Canadian: 28%`.
### Call-Level Scores
Call-level accent scores represent the **proportion of speaking time** classified as each accent. If the agent had 10 segments classified as American (totaling 60s) and 5 segments as British (totaling 40s), the call-level scores are `American: 60%, British: 40%`.
### Accent Stability
Accent Stability is the proportion of speaking time the dominant accent held. In the example above, stability would be `0.6` (60%). A stability of `1.0` means every segment was classified as the same accent.
***
## Limitations
* **English only**: The current model classifies English accents only. Future language models will use the same infrastructure.
* **Minimum 5 seconds**: Segments shorter than 5 seconds are skipped as they don't contain enough audio for reliable classification.
* **Similar accents**: The model may confuse similar accents (e.g. American vs Canadian, British vs Irish) especially on short segments. The normalization helps but isn't perfect.
# Tool Call Testing
Source: https://docs.roark.ai/documentation/recipes/tool-call-testing
Verify your agent calls the right tools with the right parameters
## Overview
Tool call testing lets you verify that your voice AI agent is invoking the correct tools, with the correct parameters, at the right moments during a conversation. Roark provides built-in metrics for this through the **Tool Invocation Analysis** package.
You can run tool call testing in two contexts:
* **Simulations**: Proactively test tool calling behavior across scenarios before deploying changes
* **Production**: Continuously monitor tool calling quality on live customer calls
Both approaches use the same metrics. You configure the metric once, then apply it wherever you need it.
***
## Prerequisites
Before testing tool calls in either context, you need to set up the tool invocation metrics you want to evaluate.
### 1. Find the Tool Invocation Analysis Package
Navigate to **Metrics > Library** and look for the **Tool Invocation Analysis** package. This package contains five built-in metrics:
| Metric | Type | What It Measures |
| :------------------------------------- | :----- | :--------------------------------------------------------------------------------- |
| **Tool Invocation Correct** | Yes/No | Whether the agent invoked the correct tools at the appropriate times |
| **Tool Invocation Count** | Count | Total number of tool calls made during the conversation |
| **Tool Invocation Order Correct** | Yes/No | Whether tools were called in the correct logical sequence |
| **Tool Invocation Parameters Correct** | Yes/No | Whether the correct parameters were passed to each tool call |
| **Tool Invocation Result Correct** | Yes/No | Whether the agent correctly interpreted and used the results returned by each tool |
### 2. Choose and Configure Your Metrics
Select the metrics relevant to your testing goals. For example, if you want to verify that your agent calls the right tool when a customer asks about the weather:
1. Click on **Tool Invocation Correct** in the library
2. Under **Tool Scoping**, select the specific tool you want to evaluate (e.g., `fetchWeather`)
3. Click **Edit** on the scoped tool to set the evaluation criteria: define when the tool should be called and what result is expected
You can leave tool scoping empty to evaluate all tools, or scope to specific tools for targeted testing. Use the **Additional Instructions** field to add cross-tool rules like "Never call any tool before greeting the customer."
***
## Testing in Simulations
Simulation testing lets you proactively validate tool calling behavior across different scenarios and personas before changes reach production.
Tool call testing in simulations requires **enriched simulations**: your agent must send its call data to Roark so that tool invocation data is available for analysis. See [Enriched Simulations](/documentation/simulation-testing/enriched-simulations) to set this up.
### Setup
Follow the [run plan guide](/documentation/simulation-testing/run-plans#creating-a-run-plan) to set up your simulation. Choose the scenarios that should trigger the tool calls you want to test.
In the **Metrics** section of the run plan, select the tool invocation metrics you configured in the prerequisites.
Make sure your agent sends its call data to Roark via an [integration](/documentation/integrations/overview) or the [API/SDK](/documentation/observability/overview#how-calls-get-into-roark). This is required for tool call data to be available. Without it, Roark only has its simulation agent's recording and cannot see your agent's tool calls.
Execute the run plan. Once your agent's call is matched and merged with the simulation call, the tool invocation metrics will be evaluated automatically.
***
## Testing in Production
Production testing lets you continuously monitor tool calling quality on live customer calls using metric collectors.
### Setup
Navigate to **Metrics → Collectors** and create a new collector. See the [collectors guide](/documentation/metrics/metric-collectors) for details.
In the collector, select the tool invocation metrics you configured in the prerequisites.
Use collector conditions to filter which calls should be evaluated. For example, you might only want to evaluate tool calls for a specific agent or call direction.
Once active, the collector will automatically evaluate tool calling on every matching call that comes in.
Your calls must include [tool invocation data](/documentation/tool-invocations) for these metrics to work. Tool calls are automatically captured for **Vapi** and **Retell** integrations. If you use the API directly or LiveKit, include tool invocations when you create the call, or [attach them afterward](/documentation/tool-invocations#attaching-tool-calls-after-a-call) if your tool data becomes available later.
***
## Next Steps
Learn how to submit tool call data with your calls
Enable call matching for simulation tool testing
Automate metric collection on production calls
Set up simulation test matrices
# Glossary
Source: https://docs.roark.ai/documentation/resources/glossary
Key terms and concepts used in Roark
## Core Concepts
### Agent
A voice AI system that handles conversations. Can be a chatbot, voice assistant, or any automated system that interacts with users through voice or text.
### Call
A single conversation session between participants (human and/or AI agents). Contains transcript, audio, metadata, and analysis results.
### Endpoint
The connection point Roark uses to reach your agent. Endpoint types include Phone, Web RTC, LiveKit, WebSocket, ElevenLabs, Google CES, and Kore. Endpoints also carry a direction: whether Roark dials your agent (outbound from Roark) or your agent calls into Roark (inbound).
### Evaluation
The process of scoring and analyzing calls against specific criteria to measure quality and performance.
### Session
A group of related calls, useful for tracking multi-call interactions or conversation threads with the same customer.
## Simulation & Testing
### Persona
A simulated customer profile with defined characteristics, behaviors, and communication patterns used when running customer flows.
### Customer Flow
One type of customer conversation your agent should handle: the customer setup, its variants, and the agent expectations it's graded against. Customer flows supersede scenarios and drive simulations today, with live-call grading planned.
### Variant
One concrete version of a customer flow. Every flow has a default variant (the happy path) plus any number of edge cases, each with its own setup, variables, and optional additional expectations.
### Happy Path
A flow's default variant, the expected, unexceptional version of the conversation.
### Edge Case
A non-default variant of a customer flow that tests a deviation from the happy path (an upset customer, a wrong account number, an unusual request).
### Improv Mode
A flow authoring mode where you write a free-form customer brief and the simulated customer improvises the conversation around it.
### Scripted Mode
A flow authoring mode where you build the conversation as a step-by-step graph, with branching paths the customer follows.
### Agent Expectations
The graded contract attached to a customer flow: what your agent must do to pass. Flow-level expectations are inherited by every variant; edge cases can add additional expectations of their own.
### Flow Link
A scripted step that splices another scripted flow's steps in at that point, so shared segments (like identity verification) can be reused across flows.
### Template
A pre-built starting point for a run (such as Flow adherence, Red teaming, Conversation quality, Multilingual, Load testing, or Tool call accuracy) that seeds flows, metrics, and settings you can customize.
### Run Plan
A reusable, named test suite: the agent targets to test, the customer flows attached, the metrics and checks to evaluate, and run settings like iterations and concurrency. One-off runs are plans that simply aren't saved (hidden plans).
### Run
A single execution of a run plan, labelled SR-. Runs move through five statuses: Running, Queued, Completed, Failed, and Cancelled.
### Simulation
A controlled test conversation that validates agent behavior without involving real customers.
### Regression Testing
Re-running previous customer flows against updated agent versions to ensure improvements don't break existing functionality.
## Analytics & Metrics
### Collector
The user-facing name for a rule that runs selected metrics on matching calls: which metrics to evaluate, and the conditions (agent, source, call property) a call must match. Collectors can be Active or Paused.
The REST API and SDKs still use the older name for collectors: endpoints live under `/v1/metric/policies` and SDK methods under `client.metricPolicy.*`.
### Studio
The workspace for building and testing metrics. Author mode defines a new metric (LLM Judge, Pattern, or Formula); Evaluate mode runs a battery of evals against sample calls or a dataset.
### Dataset
A curated set of calls used as an evaluation corpus, for example, to run a metric battery in Studio against a fixed, representative sample.
### Sentiment Analysis
Detection and classification of emotions and feelings expressed during conversations (64+ emotions tracked).
### Sentiment Score
A numerical value (-1 to +1) indicating overall emotional tone, where negative values indicate negative sentiment and positive values indicate positive sentiment.
### Performance Metrics
Quantitative measurements of agent efficiency including response time, resolution rate, and conversation duration.
### Tool Invocation
When an agent calls an external function or API during a conversation (e.g., checking order status, booking appointment).
### Talk Ratio
The percentage of conversation time each participant speaks, used to measure conversation balance.
## Integration Terms
### Webhook
An HTTP callback that sends real-time event data from voice platforms to Roark when calls occur.
### WebSocket
A persistent connection protocol used for real-time, bidirectional communication in live monitoring.
### Ingestion
The process of receiving and processing call data from voice platforms into Roark's analytics system.
### Egress
Outbound data flow, commonly referring to call recordings being sent to storage systems.
## Voice Platforms
### VAPI
A voice AI platform that provides infrastructure for building and deploying voice agents.
### Retell AI
A platform for creating conversational AI agents with natural voice interactions.
### LiveKit
An open-source platform for real-time audio/video communications, used for voice agent infrastructure.
### Pipecat Cloud
A cloud platform for deploying and managing voice AI pipelines.
## Technical Components
### Batch Evaluation
Processing multiple calls simultaneously for analysis rather than one at a time.
### Custom Metric
A user-defined metric (typically an LLM evaluation prompt) tailored to specific business requirements beyond the built-in system metrics.
### Transcript
The text representation of spoken conversation, including speaker labels and timestamps.
### Utterance
A single continuous speech segment from one speaker, ending when they stop talking or another speaker begins.
### Turn
A complete speaking opportunity for one participant in a conversation, may contain multiple utterances.
## Monitoring & Reporting
### Live Monitoring
Real-time observation and analysis of ongoing calls as they happen.
### Dashboard
The visual interface displaying analytics, metrics, and insights about your voice AI performance.
### Baseline
A reference point for normal performance metrics, used to identify deviations and improvements.
### Alert
Automated notification triggered when specific conditions are met (e.g., high frustration detected).
### Trend Analysis
Tracking metrics over time to identify patterns, improvements, or degradations in performance.
## Business Terms
### First Call Resolution (FCR)
Successfully resolving a customer's issue during their first call without requiring follow-up.
### Escalation
Transferring a call to a human agent or supervisor when the AI cannot handle the situation.
### Quality Assurance (QA)
The process of monitoring and evaluating agent interactions to ensure quality standards are met.
### Service Level Agreement (SLA)
Performance standards and metrics that must be maintained (e.g., response time, resolution rate).
## API & Development
### API Key
A unique identifier used to authenticate requests to Roark's API endpoints.
### Rate Limiting
Restrictions on the number of API requests allowed within a time period to prevent abuse.
### SDK
Software Development Kit - pre-built libraries (Node.js, Python) that simplify integration with Roark.
### REST API
The HTTP-based interface for programmatically interacting with Roark's services.
### Payload
The data package sent in API requests or webhook events containing call information.
# Support
Source: https://docs.roark.ai/documentation/resources/support
Get help with Roark
## Contact Support
For technical support and assistance:
**Email**: [support@roark.ai](mailto:support@roark.ai)
**Slack**: Available for customers on paid plans - join our dedicated Slack workspace for direct access to our team.
## Response Times
* **Free Plan**: Email support within 48 hours
* **Paid Plans**: Priority support via Slack with faster response times
## Before Contacting Support
Please check our [Troubleshooting Guide](/documentation/resources/troubleshooting) for solutions to common issues.
# Troubleshooting
Source: https://docs.roark.ai/documentation/resources/troubleshooting
Solutions to common issues when using Roark
## API & Authentication
### "Invalid API Key" Error
**Problem**: Receiving 401 Unauthorized errors.
**Solutions**:
1. Regenerate your API key from [dashboard](https://roark.ai/org)
2. Ensure no extra spaces before/after the key
3. Use format: `Authorization: Bearer YOUR_API_KEY`
4. Check key hasn't expired or been revoked
### Rate Limiting Issues
**Problem**: Receiving 429 Too Many Requests errors.
**Solutions**:
1. Check your current plan's rate limits
2. Implement exponential backoff in your code
3. Batch operations where possible
4. Consider upgrading to a higher tier for increased limits
## Getting Help
If these solutions don't resolve your issue:
1. **Documentation**: Review relevant guides in this documentation
2. **Community**: Ask in our [Slack community](https://roark.ai/slack) (paid plans)
3. **Support**: Email [support@roark.ai](mailto:support@roark.ai) with:
* Your account email
* Error messages or screenshots
* Steps to reproduce the issue
* Time when issue occurred (with timezone)
# CLI
Source: https://docs.roark.ai/documentation/sdks/cli
Drive Roark from your terminal and your CI pipeline
### Overview
`roark` is the official command line interface for the Roark API. It covers the same surface as the [Node.js](/documentation/sdks/node-sdk) and [Python](/documentation/sdks/python-sdk) SDKs (calls, metrics, personas, customer flows, simulations, webhooks) and adds the two commands that make [Config as Code](/documentation/config-as-code/overview) practical: `roark config diff` and `roark config apply`.
It prints JSON, so it composes with `jq` and with everything else in a pipeline, and it uses distinct [exit codes](#exit-codes), so a CI job can tell a rejected request apart from a missing credential.
***
### Install
**macOS, Linux, WSL:**
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -fsSL https://roark.ai/install.sh | sh
```
This installs into `~/.roark` and links `roark` into `~/.local/bin`. Nothing is written outside your home directory, and no step needs `sudo`.
Pin a version, or remove the CLI entirely:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -fsSL https://roark.ai/install.sh | sh -s -- --version 0.1.1
curl -fsSL https://roark.ai/install.sh | sh -s -- --uninstall
```
Re-run the install command to upgrade. It replaces the installed version in place and prunes the old one.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
brew tap roarkhq/tap
brew trust roarkhq/tap
brew install roark
```
The `brew trust` step is not optional. Homebrew refuses to load a formula from a third-party tap until it is trusted, and without it `brew install` stops with `Refusing to load formula roarkhq/tap/roark from untrusted tap`. To trust just this formula rather than the whole tap, use `brew trust --formula roarkhq/tap/roark`.
In a `Brewfile`:
```ruby theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
tap "roarkhq/tap"
brew "roark"
```
Homebrew installs do not auto-update. Run `brew upgrade roark` to move to the latest version.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
npm install -g @roarkanalytics/cli
```
Or run it without installing anything, which is often what you want in CI:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
npx -y @roarkanalytics/cli@latest --help
```
A global npm install writes into npm's configured prefix, which on many systems is root-owned and fails with `EACCES`. If you hit that, use the install script instead of reaching for `sudo`.
Confirm the install worked:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark --version
```
The CLI needs **Node.js 20 or newer**. Every install method above uses your existing Node; none of them bundle a runtime.
If `~/.local/bin` is not on your `PATH`, the install script tells you the line to add. Man pages ship with the CLI. Add `~/.roark/share/man` to `MANPATH` and `man roark` works.
***
### Authenticate
Interactively, `roark auth login` opens your browser to approve access, then stores the key it mints, scoped to the project and the permissions you approve, and revocable any time under **Settings → API keys**:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark auth login # opens the browser to approve, stores the minted key (mode 0600)
roark auth login --paste # skip the browser: paste or pipe a token instead
roark auth status # shows which credential is in effect, and where it came from
roark auth logout # deletes the stored credential
```
In **CI there is no browser**, so authenticate with a token instead. Generate one from [API Keys](/documentation/getting-started/api-keys) and set the environment variable the CLI and SDKs both read, no `login` step needed:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
export ROARK_API_BEARER_TOKEN="your-api-key"
```
You can also pass `--token ` on any command, or pipe a token into login (`echo "$ROARK_API_BEARER_TOKEN" | roark auth login`). See [Using the CLI in CI](#using-the-cli-in-ci) for a full GitHub Actions example.
Settings resolve highest precedence first: a flag, then the environment variable, then a project `.roark.json` found by walking up from the working directory, then the user config file. `roark config path` prints where each of those lives.
A `.roark.json` arrives with a clone rather than being something you wrote, so if a project file sets `baseURL`, the CLI refuses to send a stored or environment credential to it. Read the file, then pass `--allow-project-base-url` (or set `ROARK_ALLOW_PROJECT_BASE_URL`) to opt in, or pass `--token` to send a different credential.
***
### Usage
Commands read noun before verb, and the verb is `list`, `get`, `create`, `update` or `delete` unless the operation is genuinely something else:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark call list --limit 5
roark call get
roark simulation plan job start
```
`roark --help` prints the flags for any command, and `roark --help` lists the command tree.
#### Output
JSON on stdout (indented and coloured for a terminal, compact when piped) so the same command works in both places. Errors go to stderr, so `> out.json` captures only real output.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark call list --limit 5 | jq '.data[].id'
roark call get --format plain
```
#### Request bodies
Flags cover the common case, and nested objects go one level deep with dots. A whole payload can be supplied as JSON, with flags overriding what it contains:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark webhook create --url https://example.com/hook --events CALL_ANALYSIS_COMPLETED
roark customer-flow create --data @flow.json
cat flow.json | roark customer-flow create
```
#### Any endpoint
Endpoints without a generated command are still reachable:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark api get /v1/call --query limit=5
roark api post /v1/webhook --data '{"url":"https://example.com","events":["CALL_ANALYSIS_COMPLETED"]}'
```
#### Shell completion
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
eval "$(roark completion bash)"
eval "$(roark completion zsh)"
roark completion fish | source
```
***
### Config as Code
The CLI is the intended way to run [Config as Code](/documentation/config-as-code/overview). Point it at a directory of YAML resources. It bundles them, resolves any `file://` prompt references, and submits the result:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark config diff ./roark
```
Prints the `create`, `update` and `delete` operations that would run. Nothing is written.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark config apply ./roark
```
Shows the same preview, then asks for confirmation before reconciling. Pass `-y` to skip the prompt in CI, and `--no-prune` for an additive-only apply that leaves removed resources alone.
These commands need an API key carrying the **`config:apply`** permission. See [Config as Code](/documentation/config-as-code/overview) for the resource kinds and apply semantics.
***
### Using the CLI in CI
CI runs headless, so skip `auth login` and authenticate with a token in the environment. Store an API key as a secret (`ROARK_API_KEY`) and export it as `ROARK_API_BEARER_TOKEN`; every command picks it up with no interactive step.
The common setup is a two-stage GitHub Actions workflow: **diff on every pull request** so a reviewer sees what would change, and **apply on merge to `main`**.
```yaml GitHub Actions theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
name: roark-config
on:
pull_request:
paths: ['roark/**']
push:
branches: [main]
paths: ['roark/**']
jobs:
config:
runs-on: ubuntu-latest
env:
# An API key with the config:apply permission, stored as a repo secret.
ROARK_API_BEARER_TOKEN: ${{ secrets.ROARK_API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
# Preview on PRs: no writes, and the log shows exactly what would change.
- name: Diff
if: github.event_name == 'pull_request'
run: npx @roarkanalytics/cli config diff ./roark
# Reconcile on merge. -y skips the confirmation prompt.
- name: Apply
if: github.ref == 'refs/heads/main'
run: npx @roarkanalytics/cli config apply ./roark -y
```
`config diff` exits non-zero if the request is rejected (see [exit codes](#exit-codes) below), so a broken bundle fails the PR check rather than slipping through. Use `--no-prune` on apply if you want additive-only syncs that never delete resources removed from the repo.
***
### Exit codes
Distinct codes so a CI job can branch on the failure rather than grepping stderr.
| Code | Meaning |
| :--- | :--------------------------------------------------------- |
| 0 | Success |
| 1 | The API rejected the request |
| 2 | The command line was wrong |
| 3 | No credential, or the credential was refused |
| 4 | The addressed resource does not exist |
| 5 | The request never completed: connection, timeout, or abort |
***
### Additional Resources
View package details on npm
Formula source and release notes
Define agents, personas, flows and metrics as YAML
Explore the full API documentation
# MCP Server
Source: https://docs.roark.ai/documentation/sdks/mcp-server
Connect AI agents and coding assistants to the Roark API using the Model Context Protocol
### Overview
The Roark MCP Server lets AI agents and coding assistants interact with the Roark API through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io). Once connected, your agent can create calls, run evaluations, search documentation, and execute code against the Roark TypeScript SDK, all within your existing workflow.
For Claude Code, the Roark plugin bundles the MCP server with workflow skills for testing voice and chat agents. The plugin teaches Claude how to choose the right Roark resources and compose them into a complete testing workflow, while the MCP provides the API access.
### How It Works
The MCP server exposes two tools to your agent:
* **Documentation Search**: a tool for querying Roark API and SDK documentation directly from your agent.
* **Code Execution**: a tool where the agent writes and executes code against the Roark API in a sandboxed environment. Anything the code returns or prints is sent back to the agent as the tool result.
Using this approach, agents can perform complex API tasks deterministically and repeatably. All operations supported by the Roark REST API and SDKs are available through the MCP server.
### Prerequisites
Before you begin, ensure you have:
* Node.js v20 or higher ([Download Node.js](https://nodejs.org))
* A Roark API Key ([Generate one here](/documentation/getting-started/api-keys))
* An MCP-compatible client (Claude Code, Cursor, VS Code, etc.)
### Installation
Choose the setup method for your client:
The recommended setup installs the Roark MCP server and its workflow skills together. Set your API key in the environment that launches Claude Code, then run Claude Code and enter these commands:
```text theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
export ROARK_API_BEARER_TOKEN="your-api-key"
/plugin marketplace add roarkhq/mcp-roark-analytics
/plugin install roark@roark
```
Set `ROARK_API_BEARER_TOKEN` before launching Claude Code. If Claude Code is already running, restart it after changing the variable.
If you only want the MCP server without the workflow skills, add it directly from your terminal:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
claude mcp add roark --env ROARK_API_BEARER_TOKEN="your-api-key" -- npx -y @roarkanalytics/sdk-mcp
```
Add the following to your Cursor MCP configuration. You can find this file via **Cursor Settings > Tools & MCP > New MCP Server**.
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"mcpServers": {
"roark": {
"command": "npx",
"args": ["-y", "@roarkanalytics/sdk-mcp"],
"env": {
"ROARK_API_BEARER_TOKEN": "your-api-key"
}
}
}
}
```
Add the following to your VS Code MCP configuration. Open it via **Command Palette > MCP: Open User Configuration**.
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"mcpServers": {
"roark": {
"command": "npx",
"args": ["-y", "@roarkanalytics/sdk-mcp"],
"env": {
"ROARK_API_BEARER_TOKEN": "your-api-key"
}
}
}
}
```
You can run the MCP server directly via `npx`:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
export ROARK_API_BEARER_TOKEN="your-api-key"
npx -y @roarkanalytics/sdk-mcp@latest
```
For any MCP-compatible client that uses a JSON configuration, use:
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"mcpServers": {
"roark": {
"command": "npx",
"args": ["-y", "@roarkanalytics/sdk-mcp"],
"env": {
"ROARK_API_BEARER_TOKEN": "your-api-key"
}
}
}
}
```
Consult your client's documentation for where to place this configuration. A partial list of MCP clients is available at [modelcontextprotocol.io](https://modelcontextprotocol.io/clients).
### Claude Code skills
After installing the `roark` plugin, Claude Code can use the bundled skills automatically when you ask it to test or monitor an AI agent. The skills cover the full workflow:
| Skill | Use it to |
| ------------------------- | ------------------------------------------------------------------------ |
| `roark-overview` | Understand the Roark object model and choose the right workflow |
| `register-agent` | Register an agent and the endpoint Roark should call |
| `author-personas-flows` | Define personas and improv customer flows |
| `author-scripted-flows` | Build exact IVR and DTMF conversation graphs |
| `configure-outbound-dial` | Configure the HTTP request used for outbound calls |
| `build-run-plan` | Assemble and start a simulation run |
| `manage-run-plans` | Find, edit, rerun, or delete saved run plans |
| `configure-metrics` | Select built-in metrics, checks, or custom metrics |
| `read-results` | Read run status, scores, transcripts, and pass/fail results |
| `monitor-live-calls` | Set up metric policies and backfill jobs for production traffic |
| `ingest-calls` | Import recordings and inspect call analysis and sentiment |
| `subscribe-webhooks` | Receive events instead of polling long-running jobs |
| `manage-config-as-code` | Reconcile agents, flows, personas, metrics, and collectors declaratively |
| `gate-ci` | Start a run and gate a CI or deployment pipeline on its results |
For example, ask Claude Code to:
```text theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
Set up a Roark simulation for my support agent. Register the agent, create a persona and customer flow, attach metrics, and show me the estimated call count before starting.
```
Every simulated call is billable. Review the estimated call count before starting a run, especially when selecting multiple flow variants, personas, endpoints, or iterations.
The skills use the open Agent Skills format, so they can also be copied from the [Roark plugin repository](https://github.com/roarkhq/mcp-roark-analytics/tree/main/plugins/roark/skills) into another compatible agent's skills directory. The plugin is the simplest way to install both the skills and the MCP server in Claude Code.
### Example Recipes
Once connected, you can interact with the Roark API conversationally. Here are some examples to get you started:
*"Can you find out why call `d4e5f6a7-1234-5678-9abc-def012345678` failed?"*
The agent will fetch the call details, check its status, and surface any errors or issues from the analysis.
*"What was the average duration of the last 10 calls we received on Roark?"*
The agent will list recent calls, extract their durations, and compute the average for you.
*"Run a metric collection job on call `a1b2c3d4-5678-9abc-def0-123456789abc` and summarize the results."*
The agent will create a metric collection job, wait for it to complete, and present the metric values.
*"Create a call from this recording URL and collect the greeting-quality and task-completion metrics on it: [https://example.com/recording.mp3](https://example.com/recording.mp3)"*
The agent will create the call record, trigger metric collection, and return the results once they're ready.
*"Show me the metric results for my last 5 calls, which ones scored lowest on task-completion?"*
The agent will fetch recent calls, pull their metric values, and rank them by score.
*"What metrics do I have configured in Roark?"*
The agent will retrieve your metric definitions and list them with their descriptions and slugs.
*"Start a simulation run using my 'angry-customer' persona against the 'appointment-booking' customer flow."*
The agent will look up your personas and customer flows, then create a simulation job.
### Additional Resources
View package details on npm
Browse the source code and open an issue
Install the Roark MCP and workflow skills together
Learn about the underlying TypeScript SDK
Explore the full API documentation
Learn more about the Model Context Protocol
# Node.js SDK
Source: https://docs.roark.ai/documentation/sdks/node-sdk
Upload calls, run metrics, and execute simulations using Node.js
The Node.js SDK is continually evolving, with new endpoints being added regularly. Stay tuned for updates!
### Overview
The Roark Node.js SDK provides a streamlined way to interact with the Roark API. This guide covers the two most common workflows: uploading calls and running metrics, and executing simulation run plans.
### Prerequisites
Before you begin, ensure you have:
* Node.js v12 or higher - [Download Node.js](https://nodejs.org)
* A Roark API Key - [Generate one here](/documentation/getting-started/api-keys)
### Installation
Choose your preferred package manager:
```bash npm theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
npm install @roarkanalytics/sdk
```
```bash yarn theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
yarn add @roarkanalytics/sdk
```
```bash pnpm theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
pnpm add @roarkanalytics/sdk
```
```bash bun theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
bun add @roarkanalytics/sdk
```
### Initialize the Client
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import Roark from '@roarkanalytics/sdk'
const client = new Roark({
bearerToken: process.env.ROARK_API_BEARER_TOKEN,
})
```
Set `ROARK_API_BEARER_TOKEN` in your environment, or replace with your actual API key from Roark.
***
### Upload a Call and Run Metrics
Upload a call recording, then run metric definitions against it using a collection job.
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const call = await client.call.create({
recordingUrl: 'https://example.com/recording.mp3',
startedAt: '2024-01-15T10:00:00Z',
interfaceType: 'PHONE',
callDirection: 'INBOUND',
agent: {
name: 'Support Agent',
customId: 'agent-123',
},
customer: {
phoneNumberE164: '+15551234567',
},
// Optional: custom properties for filtering
properties: {
department: 'sales',
campaignId: 'summer-2024',
},
})
```
List available metric definitions to choose which ones to run:
Metric definitions are cursor-paginated. `listDefinitions()` returns the
first page, up to 500 definitions. To enumerate further pages, pass the
preceding response's `pagination.nextCursor` as `after` to
`GET /v1/metric/definitions`; treat the cursor as opaque.
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const definitions = await client.metric.listDefinitions()
// Find the metrics you want to collect
const taskCompletion = definitions.data.find(
(m) => m.metricId === 'task_completion'
)
const sentiment = definitions.data.find(
(m) => m.metricId === 'sentiment_score'
)
```
Run the selected metrics against your uploaded call:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const job = await client.metricCollectionJob.create({
callIds: [call.data.id],
metrics: [
{ id: taskCompletion.id },
{ id: sentiment.id },
],
})
```
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
let status = 'PENDING'
while (status !== 'COMPLETED' && status !== 'FAILED') {
const result = await client.metricCollectionJob.getByID(job.data.id)
status = result.data.status
console.log(`Progress: ${result.data.completedItems}/${result.data.totalItems}`)
if (status !== 'COMPLETED' && status !== 'FAILED') {
await new Promise((r) => setTimeout(r, 2000))
}
}
```
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const metrics = await client.call.listMetrics(call.data.id)
console.log(metrics.data)
```
If you have [metric collectors](/documentation/metrics/metric-collectors) configured, metrics are collected automatically when calls are uploaded, no collection job needed.
***
### Run a Simulation
Run a simulation against your agent with an inline plan, then monitor it.
Reference an [agent endpoint](/documentation/simulation-testing/run-plans), a [customer flow](/documentation/simulation-testing/customer-flows), and at least one metric. Add `saveAsPlan: true` (with a `name`) to keep it as a reusable plan.
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const run = await client.simulation.run({
plan: {
direction: 'INBOUND',
maxSimulationDurationSeconds: 300,
agentEndpoints: [{ id: 'agent-endpoint-uuid' }],
flows: [{ id: 'customer-flow-uuid', happyPath: true }],
metrics: [{ slug: 'task_completion' }],
},
})
const jobId = run.data.simulationRunPlanJobId
```
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const job = await client.simulationRunPlanJob.start('plan-uuid')
```
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const result = await client.simulationRunPlanJob.getByID(jobId)
console.log(`Status: ${result.data.status}`)
console.log(`Jobs: ${result.data.simulationJobs.length}`)
```
Results including metric evaluations are available in the Roark dashboard, or you can fetch metrics for individual simulation calls via the API:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// Get metrics for a specific simulation call
const metrics = await client.call.listMetrics('simulation-call-id')
```
***
### Additional Examples
Track function calls and tool usage during the conversation:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const call = await client.call.create({
recordingUrl: 'https://example.com/recording.mp3',
startedAt: '2024-01-15T10:00:00Z',
interfaceType: 'PHONE',
callDirection: 'INBOUND',
agent: {
name: 'Booking Agent',
customId: 'booking-agent-1',
},
customer: {
phoneNumberE164: '+15551234567',
},
toolInvocations: [
{
name: 'checkAvailability',
description: 'Check available appointment slots',
startOffsetMs: 5000,
endOffsetMs: 5500,
parameters: {
date: '2024-01-20',
serviceType: 'consultation',
},
result: { slots: ['9:00 AM', '2:00 PM', '4:00 PM'] },
agent: { customId: 'booking-agent-1' },
},
{
name: 'bookAppointment',
description: 'Book an appointment for the customer',
startOffsetMs: 15000,
endOffsetMs: 15800,
parameters: {
date: '2024-01-20',
time: '2:00 PM',
customerName: 'John Doe',
},
result: 'Appointment confirmed',
agent: { customId: 'booking-agent-1' },
},
],
})
```
Reference an agent by its Roark ID or custom ID:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// By Roark ID
const call = await client.call.create({
recordingUrl: 'https://example.com/recording.mp3',
startedAt: '2024-01-15T10:00:00Z',
interfaceType: 'WEB',
callDirection: 'OUTBOUND',
agent: {
roarkId: '550e8400-e29b-41d4-a716-446655440000',
},
customer: {
phoneNumberE164: '+15551234567',
},
})
// By custom ID
const call2 = await client.call.create({
recordingUrl: 'https://example.com/recording.mp3',
startedAt: '2024-01-15T10:00:00Z',
interfaceType: 'PHONE',
callDirection: 'INBOUND',
agent: {
customId: 'my-agent-id',
},
customer: {
phoneNumberE164: '+15551234567',
},
})
```
Run metrics across a batch of existing calls:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const job = await client.metricCollectionJob.create({
callIds: [
'call-uuid-1',
'call-uuid-2',
'call-uuid-3',
],
metrics: [
{ id: 'metric-definition-uuid-1' },
{ id: 'metric-definition-uuid-2' },
],
})
// totalItems = callIds.length * metrics.length
console.log(`Processing ${job.data.totalItems} items`)
```
### Additional Resources
Explore our comprehensive API documentation
View example implementations and use cases
View package details and stats on npm
Browse the source code and contribute
# Python SDK
Source: https://docs.roark.ai/documentation/sdks/python-sdk
Upload calls, run metrics, and execute simulations using Python
The Python SDK is continually evolving, with new endpoints being added regularly. Stay tuned for updates!
### Overview
The Roark Python SDK provides a streamlined way to interact with the Roark API. This guide covers the two most common workflows: uploading calls and running metrics, and executing simulation run plans.
### Prerequisites
Before you begin, ensure you have:
* Python 3.7 or higher - [Download Python](https://www.python.org/downloads/)
* A Roark API Key - [Generate one here](/documentation/getting-started/api-keys)
### Installation
Choose your preferred package manager:
```bash pip theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
pip install roark-analytics
```
```bash pipenv theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
pipenv install roark-analytics
```
```bash poetry theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
poetry add roark-analytics
```
### Initialize the Client
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import os
from roark_analytics import Roark
client = Roark(
bearer_token=os.environ.get("ROARK_API_BEARER_TOKEN"),
)
```
Set `ROARK_API_BEARER_TOKEN` in your environment, or replace with your actual API key from Roark.
***
### Upload a Call and Run Metrics
Upload a call recording, then run metric definitions against it using a collection job.
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
call = client.call.create(
recording_url="https://example.com/recording.mp3",
started_at="2024-01-15T10:00:00Z",
interface_type="PHONE",
call_direction="INBOUND",
agent={
"name": "Support Agent",
"customId": "agent-123",
},
customer={
"phoneNumberE164": "+15551234567",
},
# Optional: custom properties for filtering
properties={
"department": "sales",
"campaignId": "summer-2024",
},
)
```
List available metric definitions to choose which ones to run:
Metric definitions are cursor-paginated. `list_definitions()` returns the
first page, up to 500 definitions. To enumerate further pages, pass the
preceding response's `pagination.nextCursor` as `after` to
`GET /v1/metric/definitions`; treat the cursor as opaque.
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
definitions = client.metric.list_definitions()
# Find the metrics you want to collect
task_completion = next(
m for m in definitions.data if m.metric_id == "task_completion"
)
sentiment = next(
m for m in definitions.data if m.metric_id == "sentiment_score"
)
```
Run the selected metrics against your uploaded call:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
job = client.metric_collection_job.create(
call_ids=[call.data.id],
metrics=[
{"id": task_completion.id},
{"id": sentiment.id},
],
)
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import time
status = "PENDING"
while status not in ("COMPLETED", "FAILED"):
result = client.metric_collection_job.get_by_id(job.data.id)
status = result.data.status
print(f"Progress: {result.data.completed_items}/{result.data.total_items}")
if status not in ("COMPLETED", "FAILED"):
time.sleep(2)
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
metrics = client.call.list_metrics(call.data.id)
print(metrics.data)
```
If you have [metric collectors](/documentation/metrics/metric-collectors) configured, metrics are collected automatically when calls are uploaded, no collection job needed.
***
### Run a Simulation
Run a simulation against your agent with an inline plan, then monitor it.
Reference an [agent endpoint](/documentation/simulation-testing/run-plans), a [customer flow](/documentation/simulation-testing/customer-flows), and at least one metric. Add `save_as_plan=True` (with a `name`) to keep it as a reusable plan.
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
run = client.simulation.run(
plan={
"direction": "INBOUND",
"maxSimulationDurationSeconds": 300,
"agentEndpoints": [{"id": "agent-endpoint-uuid"}],
"flows": [{"id": "customer-flow-uuid", "happyPath": True}],
"metrics": [{"slug": "task_completion"}],
},
)
job_id = run.data.simulation_run_plan_job_id
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
job = client.simulation_run_plan_job.start("plan-uuid")
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
result = client.simulation_run_plan_job.get_by_id(job_id)
print(f"Status: {result.data.status}")
print(f"Jobs: {len(result.data.simulation_jobs)}")
```
Results including metric evaluations are available in the Roark dashboard, or you can fetch metrics for individual simulation calls via the API:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# Get metrics for a specific simulation call
metrics = client.call.list_metrics("simulation-call-id")
```
***
### Additional Examples
Track function calls and tool usage during the conversation:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
call = client.call.create(
recording_url="https://example.com/recording.mp3",
started_at="2024-01-15T10:00:00Z",
interface_type="PHONE",
call_direction="INBOUND",
agent={
"name": "Booking Agent",
"customId": "booking-agent-1",
},
customer={
"phoneNumberE164": "+15551234567",
},
tool_invocations=[
{
"name": "check_availability",
"description": "Check available appointment slots",
"startOffsetMs": 5000,
"endOffsetMs": 5500,
"parameters": {
"date": "2024-01-20",
"serviceType": "consultation",
},
"result": {"slots": ["9:00 AM", "2:00 PM", "4:00 PM"]},
"agent": {"customId": "booking-agent-1"},
},
{
"name": "book_appointment",
"description": "Book an appointment for the customer",
"startOffsetMs": 15000,
"endOffsetMs": 15800,
"parameters": {
"date": "2024-01-20",
"time": "2:00 PM",
"customerName": "John Doe",
},
"result": "Appointment confirmed",
"agent": {"customId": "booking-agent-1"},
},
],
)
```
Reference an agent by its Roark ID or custom ID:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# By Roark ID
call = client.call.create(
recording_url="https://example.com/recording.mp3",
started_at="2024-01-15T10:00:00Z",
interface_type="WEB",
call_direction="OUTBOUND",
agent={
"roarkId": "550e8400-e29b-41d4-a716-446655440000",
},
customer={
"phoneNumberE164": "+15551234567",
},
)
# By custom ID
call2 = client.call.create(
recording_url="https://example.com/recording.mp3",
started_at="2024-01-15T10:00:00Z",
interface_type="PHONE",
call_direction="INBOUND",
agent={
"customId": "my-agent-id",
},
customer={
"phoneNumberE164": "+15551234567",
},
)
```
Run metrics across a batch of existing calls:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
job = client.metric_collection_job.create(
call_ids=[
"call-uuid-1",
"call-uuid-2",
"call-uuid-3",
],
metrics=[
{"id": "metric-definition-uuid-1"},
{"id": "metric-definition-uuid-2"},
],
)
# total_items = len(call_ids) * len(metrics)
print(f"Processing {job.data.total_items} items")
```
For asynchronous operations, use the async client:
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
import os
import asyncio
from roark_analytics import AsyncRoark
client = AsyncRoark(
bearer_token=os.environ.get("ROARK_API_BEARER_TOKEN"),
)
async def main():
call = await client.call.create(
recording_url="https://example.com/recording.mp3",
started_at="2024-01-15T10:00:00Z",
interface_type="PHONE",
call_direction="INBOUND",
agent={"name": "Support Agent"},
customer={"phoneNumberE164": "+15551234567"},
)
job = await client.metric_collection_job.create(
call_ids=[call.data.id],
metrics=[{"id": "metric-definition-uuid"}],
)
asyncio.run(main())
```
### Additional Resources
Explore our comprehensive API documentation
View example implementations and use cases
View package details and stats on PyPI
Browse the source code and contribute
# Best Practices
Source: https://docs.roark.ai/documentation/simulation-testing/best-practices
Guidelines for getting the most out of Roark simulations
## Customer flows
### Improv vs. Scripted
The single biggest decision in a [customer flow](/documentation/simulation-testing/customer-flows) is the authoring mode, and it maps directly to what you're testing.
**Improv**: You write a free-text brief describing who the customer is, what they want, and how they behave. The simulator improvises a fresh conversation from that brief on every run, different words, same intent:
```
A first-time caller wants to book an appointment for tomorrow around 2pm.
They're friendly but a little unsure of the process and will ask what
information they need to provide.
```
Improv is the default choice for most testing. Because the wording changes each run, it exercises your agent's ability to handle natural language variation for the same intent, the thing that actually breaks in production.
**Scripted**: You author the conversation step by step on a graph canvas, writing the exact customer lines:
```
"Hello, can I make a booking for tomorrow at 2pm?"
```
Scripted mode is for precise control: verifying your agent parses a specific date format, detects a keyword, handles a DTMF sequence, or recovers from a deliberate silence.
Use **Scripted** when the exact utterance matters: phrasing, keyword detection, slot filling, keypad input. Use **Improv** when you want varied, realistic conversations that test the same intent from a different angle every run.
### Write improv briefs like backstories
The customer setup brief is where the simulated customer comes to life. Behavioral color (emotional state, context, quirks) belongs in this prose, not in the persona (personas carry voice and speech characteristics; the brief carries the situation).
Good briefs give the simulator context that drives nuanced, realistic behavior:
**Bereaved customer**
```
James recently lost his wife and is calling to cancel her phone line on
their shared plan. He is soft-spoken and may become emotional. He doesn't
fully understand the account details and may need things explained gently.
```
**Skeptical professional**
```
Priya is a software engineer who immediately suspects she's talking to an
AI. She will ask pointed questions like "Are you a real person?" and "Can
you transfer me to a human?" She becomes frustrated if the agent can't
directly answer her questions about a billing discrepancy.
```
**Impatient multitasker**
```
Carlos is calling during a short break at work. He has 5 minutes. He'll
give short answers, may mishear things, and will ask the agent to repeat
or speak up. He needs to reschedule a delivery that requires a signature.
```
### Structure scripted flows around paths
Scripted flows are **graph-based**: a DAG of agent and customer steps. Every unique path from start to leaf becomes its own variant automatically: the path engine derives one variant per path, so structuring the graph well is how you get coverage without duplication.
#### Start with a happy path
Begin with a single expected route, the happy path where everything goes as planned:
```mermaid theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
graph LR
C1["👤 Customer calls in"] --> A1["🤖 Greets and\n asks how to help"]
A1 --> C2["👤 Asks to book\n an appointment"]
C2 --> A2["🤖 Asks for\n preferred date"]
A2 --> C3["👤 Provides\n a date"]
C3 --> A3["🤖 Confirms\n and books"]
```
#### Add branches for edge cases
Once your happy path works, branch at points where the conversation can diverge. Branches inherit everything above them, and each new path shows up as an edge case in the variant rail:
```mermaid theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
graph TD
C1["👤 Customer calls in"] --> A1["🤖 Greets and asks how to help"]
A1 --> C2["👤 Asks to book an appointment"]
C2 --> A2["🤖 Asks for preferred date"]
A2 --> C3["👤 Provides a date"]
A2 --> C3b["👤 Asks what's available"]
C3 --> A3["🤖 Confirms and books"]
C3b --> A3b["🤖 Lists available slots"]
A3b --> C4["👤 Picks a slot"]
C4 --> A4["🤖 Confirms and books"]
```
Focus your branching where:
* The agent asks the customer a question (customers respond in unexpected ways)
* The agent could go on a tangent or lose track of the conversation
* Tool calls or lookups might fail or return unexpected results
If you want the *same* path exercised by a different caller, don't duplicate the branch. Use **Add variant for path** to run that path again with a different persona or environment.
Structural graph edits (adding or removing steps and edges) pause variant editing until you save: the flow needs to refresh its paths before you can add or remove variants.
#### Compose flows instead of duplicating them
Two composition tools keep shared sequences in one place:
* **Flow link** (scripted step): splices another scripted flow's steps in at that point. Keep a shared IVR tree or authentication sequence in its own flow and link to it; when the menu changes, you update it once.
* **Preceded by** (improv variant): runs a scripted flow *before* the improv segment. Use it to deterministically navigate an IVR menu, then hand off to an improvised conversation. Improv has no fixed ending, so it can only come last. You can't link a scripted flow after it.
### Templating with variables
Variants carry key/value **variables** that are handed to the customer-side model, and text fields support `{{variableName}}` references:
```
"Hi, my name is {{patientName}} and I need to reschedule
my {{appointmentType}} appointment"
```
Persona-scoped properties use the `{{persona.*}}` prefix and resolve from the variant's persona at runtime:
```
"Hi, my name is {{persona.name}}"
```
This keeps a single flow reusable across many test cases, with only the key details changing between runs. You can also use **Apply a test profile** in the variant's variables editor to fill the entries from a saved profile in one click. See the [Variables guide](/documentation/simulation-testing/variables) for the full lifecycle.
### Generate flows with Ask Roark
You don't have to author flows from scratch. The **New flow** page (`/customer-flows/new`) offers three Ask Roark starting points under "Generate":
1. **From your calls**: pick real production calls and let Ask Roark draft flows from them. This is the fastest route to representative coverage: the drafts reflect how customers actually talk to your agent.
2. **From a transcript**: upload or paste a transcript from any source (another platform, a QA review, a bug report) and generate a flow from it.
3. **Describe what you want**: describe the situation in plain language and Ask Roark drafts the flow for you.
Whichever card you start from, treat the draft as a baseline: review the generated brief or steps, then extend it with edge cases covering paths that didn't occur in the source material but could happen in production. Generated variants carry a **Generated** badge until you review them.
***
## Personas
[Personas](/documentation/simulation-testing/personas) model *who is calling*: the voice and speech profile pinned to each flow variant. A good persona strategy tests your agent across a range of realistic caller profiles.
### Diversify voice and speech
Build a set of personas that vary:
* **Language and accent**: battle-test your transcriber's accuracy across accents (US, British, Indian, Spanish, and more) and languages
* **Speech pace and response timing**: slow, rambling talkers and quick, clipped ones ensure your agent neither interrupts customers nor times out waiting for them
* **Clarity and disfluencies**: vague or rambling callers with natural ums and false starts stress your agent's understanding far more than clean studio speech
Background noise is no longer a persona property. It's the variant's **Environment** chip in the flow editor (Office, Coffee shop, Driving, Airport, and more). Vary it per variant to verify your endpointing holds up in non-ideal audio.
### Test difficult customer types
Combine persona fields (base emotion, clarity) with a matching customer setup brief to build challenging callers:
Base emotion **Skeptical**, plus a brief where the customer tests the agent with trick questions and asks for a human
Base emotion **Frustrated**, plus a brief with a rude, escalating customer: verify your agent stays polite and professional
A brief describing a customer in a difficult moment (bereavement, financial hardship): ensure your agent is empathetic and considerate
Base emotion **Distracted**, clarity **Rambling**, plus a brief where the customer changes topics frequently
The persona sets the delivery; the variant's customer setup prose sets the situation and behavior. Keeping them separate means one "Frustrated rambler" persona can stress-test every flow in your library.
***
## Plan configuration
A plan composes agent endpoints, attached flows (with a variant selection per attachment), metrics with Pass/Fail checks, and run settings. How you configure it depends on what you're testing.
### Common patterns
Use the **Load testing** template rather than configuring this by hand. Its **Volume** panel exposes exactly the two knobs you need (**Concurrent calls** and **Total iterations**) plus the flow to test.
* Pick a flow whose happy path matches your target call duration
* Set total iterations to your target volume and concurrent calls to how many should hit the agent simultaneously
This reveals how your agent performs under peak load without over-provisioning your regular test plans.
Test how your agent handles different voices, accents, and speech styles on the same conversation.
* Attach a **single flow** with the **default variant** selection (your happy path)
* Attach it multiple times with a different **persona override** per attachment, covering a wide spread of accents, paces, and base emotions
This isolates persona-driven variation from flow complexity, making it easy to spot which caller profiles cause problems. For language coverage specifically, the **Multilingual** template does the fan-out for you: each attached flow runs once per selected language.
Agents are non-deterministic. Verify they don't go off-script or hit loopholes.
* Build flows with **multiple edge cases**: scripted branches for the paths you can enumerate, improv edge cases for the ones you can't
* Write **agent expectations** for the behaviors that must hold on every variant, and additional expectations per edge case
* Focus edge cases on points where the agent might go on a tangent or fail to recover
The **Flow adherence** template ships with metrics and checks tuned for exactly this.
Use the **Red teaming** template to test resilience against adversarial inputs. It sources flows carrying the **Adversarial** label from your library, or generates 3–10 adversarial edge cases for you at easy, medium, or hard difficulty, covering prompt injection attempts, PII extraction, and social engineering.
Label your own hand-authored adversarial flows with the Adversarial system label so the template picks them up automatically.
See [Templates](/documentation/simulation-testing/templates) for the full catalogue and what each one preconfigures.
### Keeping simulations under control
| Setting | Default | Recommendation |
| :------------------- | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Max duration** | 15 minutes (up to 24h) | Set to \~110% of your average call duration. Prevents runaway calls if the agent goes on a tangent. |
| **Silence timeout** | 30 seconds (5–300s) | Ends calls after sustained silence, catching cases where either side stops responding. |
| **End-call phrases** | `goodbye` (max 10) | Specific phrases that end the simulation immediately when matched. Add phrases that signal a call has gone off track. |
| **End-call reasons** | none (max 10) | When you can't pin down exact phrases, describe the condition instead: an LLM evaluates each turn and ends the call when the reason is met. |
| **Iterations** | 1 (max 100) | Runs per test case. Fixed at 1 for outbound runs. |
| **Concurrency** | 5 | Keep at the default or below for regular runs to avoid unnecessary load on your agent and manage costs. Reserve high concurrency for the Load testing template. |
***
## Next Steps
Author improv briefs and scripted graphs
Create diverse caller profiles
Start from a goal with preconfigured plans
Configure and execute simulation plans
# Chat Simulations
Source: https://docs.roark.ai/documentation/simulation-testing/chat-simulations
Test text-based AI agents the same way you test voice agents
## Overview
Chat simulations run persona-driven conversations against text-based AI agents: the same testing primitives you use for voice, applied to text. Roark plays the human side of the chat, sends messages to your agent, and evaluates the resulting transcript against your metrics and checks.
If you've run voice simulations, you already know how chat simulations work. The mental model is identical: a [persona](/documentation/simulation-testing/personas) drives the customer's behavior, a [customer flow](/documentation/simulation-testing/customer-flows) defines the conversation, a [plan](/documentation/simulation-testing/run-plans) ties them together with an agent endpoint, and [metrics](/documentation/metrics/overview) measure the outcome.
***
## Voice vs. Chat Endpoints
Whether a simulation runs as a call or a chat is decided by the **endpoint type** on the agent target. You don't pick a modality separately.
| Modality | Endpoint types |
| :-------- | :-------------------------------------------- |
| **Voice** | Phone, WebRTC, LiveKit |
| **Chat** | WebSocket, ElevenLabs WS, Kore AI, Google CES |
ElevenLabs WS endpoints resolve to **chat**, not voice. To test an ElevenLabs agent over audio, use a Phone endpoint instead.
***
## How It's Different from Voice Simulations
Chat simulations reuse every concept from voice simulations, with a few practical differences:
| | Voice Simulations | Chat Simulations |
| :---------------------- | :-------------------------------------------- | :----------------------------------------------- |
| **Transport** | Phone, WebRTC, LiveKit audio | WebSocket / HTTP text messages |
| **Persona output** | Synthesized speech with accent, gender, pace | Typed text in the persona's style |
| **Step types** | All Scripted steps including Silence and DTMF | Text steps: Customer, Agent, First message |
| **Conversation record** | Stored as a **call** with audio + transcript | Stored as a **chat** with the message transcript |
Voice-only step types in Scripted flows (Silence, DTMF, and Voicemail steps in older flows) are skipped automatically when a flow runs against a chat endpoint, so you can reuse existing flows without forking them, and text-relevant steps still execute. [Voicemail testing](/documentation/simulation-testing/voicemail) is voice-only for the same reason: a greeting is audio, so there's nothing for a chat transcript to carry.
***
## What's Shared with Voice Simulations
Everything except the transport layer is shared:
* **Personas**: The attributes that shape text behavior carry over: **language**, **base emotion**, **clarity**, **response timing**, and **disfluencies** (natural false starts, rendered as typed hesitations). Voice-only attributes (accent, gender, speech pace) are simply ignored in a chat.
* **Customer flows**: Both Improv and Scripted flows work for chat. The same variants, agent expectations, and `{{variable}}` values apply.
* **Plans**: Build a chat plan the same way you build a voice one; the only difference is selecting an agent target with a chat endpoint.
* **Variables**: [Variables](/documentation/simulation-testing/variables) resolve identically.
* **Metrics and checks**: System and custom metrics run on chat transcripts, and Pass/Fail checks evaluate the same way. Metrics that only apply to audio (e.g. speech rate, silence duration) are skipped.
* **Schedules**: Chat plans can be [scheduled](/documentation/simulation-testing/schedules) the same way as voice plans.
* **Re-runs and reporting**: Identical experience, with chat results appearing alongside call results in run reports.
Write your flows and personas once, then point them at either a voice endpoint or a chat endpoint depending on what you want to test.
***
## How Chat Simulations Run
Attach customer flows, pick metrics and checks, and select an agent target with a **chat endpoint** (see supported providers below).
For each conversation, Roark establishes a session with your agent over the provider's chat transport. Credentials live on the integration. They're never embedded in the plan.
Roark plays the customer using the persona's language and behavioral attributes, following the flow variant and adapting to whatever your agent actually says.
Every message is recorded as a chat. When the conversation ends, your configured metrics and checks are evaluated against the transcript.
Chat simulations always run outbound-from-Roark: Roark initiates the session against your agent endpoint. The [inbound vs. outbound](/documentation/simulation-testing/inbound-vs-outbound) distinction from voice simulations doesn't apply to chat.
***
## Watching Chat Simulations Live
While a run is in progress, chat sessions appear in the run's **Live now** board just like calls. The live transcript and conversation events stream in real time as the persona and your agent exchange messages. The only difference from voice is that there's no audio: the listen-in audio card is hidden for chat sessions.
***
## Supported Providers
Chat simulations require a chat-capable agent endpoint. Provider endpoints are created automatically when you connect a supported integration. You don't need to wire them up manually.
Run chat simulations against your ElevenLabs Conversational AI agents.
Run chat simulations against your Google Customer Engagement Suite apps.
Run chat simulations against your Kore AI Agent Platform apps.
You can also target a plain **WebSocket** endpoint for custom text agents. See each provider's page for setup steps and required credentials.
***
## Viewing Chat Results
Completed chat simulations appear in **Simulate → Runs** alongside call results, under the run's SR- label. Opening a chat shows the full message transcript, persona context, and evaluated metric and check results.
Chats are first-class conversation records in Roark. They show up in reports, dashboards, and [collectors](/documentation/metrics/metric-collectors) the same way calls do.
***
## Next Steps
Set up the customer side of your chats
Design the conversation in Improv or Scripted mode
Pick the chat-capable platform you want to test
Combine everything into a runnable test suite
# CI/CD
Source: https://docs.roark.ai/documentation/simulation-testing/ci-cd
Run simulations from your pipeline: sync your test suite with Config as Code and trigger a run on every change
## Overview
You can drive Roark from CI so your agent is tested on every change, not just by hand. It combines two things you already have:
1. **[Config as Code](/documentation/config-as-code/overview)** keeps your test suite (customer flows, personas, metrics, collectors) in your git repo, so CI can sync it with one command.
2. The **[CLI](/documentation/sdks/cli)** triggers a simulation [run](/documentation/simulation-testing/running-simulations) for a saved [run plan](/documentation/simulation-testing/run-plans).
The typical pipeline: on merge to your main branch, apply your config, then start a run for the plan that exercises your agent.
Both steps authenticate with a project API key that carries the right permissions (`config:apply` for the sync step). Store it as a CI secret and export it as `ROARK_API_BEARER_TOKEN`; every CLI command picks it up with no interactive login. See [Using the CLI in CI](/documentation/sdks/cli#using-the-cli-in-ci).
***
## Step 1: Sync your test suite
Keep your flows, personas, metrics, and collectors as YAML in your repo and apply them so the project matches what's in git:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
npx @roarkanalytics/cli config apply ./roark -y
```
`apply` exits non-zero if the bundle is invalid, so a broken config fails the build on its own. Run `roark config diff ./roark` on pull requests to preview changes before they land. See [Config as Code](/documentation/config-as-code/overview) for the full workflow.
Config as Code manages resource **definitions**. It does not start a run by itself, that's the next step.
***
## Step 2: Trigger a run
Start a run for a saved plan by its ID:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark simulation plan job start
```
Find the plan ID on the plan's page in the dashboard, or list your plans:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark api get /v1/simulation/plan
```
The command returns the run's `simulationRunPlanJobId`. The run then executes asynchronously against your agents.
Save the plan you want CI to run from the **New Run** flow (check **Save as plan**), so CI can reference a stable plan ID instead of re-specifying the run each time.
***
## Step 3: See the results
Open the run in Roark to see its verdict: the pass rate across every check, per-conversation scores, and any failures, on the [run report](/documentation/simulation-testing/running-simulations).
If you want CI to wait for the run to finish, poll its lifecycle status until it reaches a terminal state (`COMPLETED`, `FAILED`, `TIMED_OUT`, or `CANCELLED`):
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark api get /v1/simulation/plan/job/
```
Today the CLI reports whether the run was **triggered and completed**, not whether its checks **passed**: review the pass/fail verdict in the run report. A native pass/fail exit code for gating a build directly on the result is in progress.
***
## GitHub Actions example
Sync config and kick off a run on every merge to `main`:
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
name: Roark simulations
on:
push:
branches: [main]
jobs:
simulate:
runs-on: ubuntu-latest
env:
ROARK_API_BEARER_TOKEN: ${{ secrets.ROARK_API_KEY }}
PLAN_ID: ${{ vars.ROARK_PLAN_ID }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
# Keep the test suite (flows, personas, metrics, collectors) in sync.
- name: Apply config
run: npx @roarkanalytics/cli config apply ./roark -y
# Trigger a simulation run for the saved plan.
- name: Start simulation run
run: npx @roarkanalytics/cli simulation plan job start "$PLAN_ID"
```
To preview config changes on pull requests instead of applying them, run `npx @roarkanalytics/cli config diff ./roark` in a `pull_request`-triggered job (the same pattern shown in [Using the CLI in CI](/documentation/sdks/cli#using-the-cli-in-ci)).
***
## Related
Define your test suite as YAML in git
Build the reusable plan CI runs
Install, authenticate, and use the CLI in CI
Launch runs and read the report
# Customer Flows
Source: https://docs.roark.ai/documentation/simulation-testing/customer-flows
Define the customer conversations your agents should handle, and get graded against
**Prefer to keep this in your repo?** Customer flows can be defined as [config as code](/documentation/config-as-code/flows) (YAML in git, applied with the [CLI](/documentation/sdks/cli)) instead of built in the dashboard.
## Quickstart
Create a customer flow (an improv brief graded against your expectations):
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const flow = await client.customerFlow.create({
type: 'IMPROV',
title: 'Billing questions',
agentIds: [''],
happyPath: {
title: 'Asks about a charge',
personaOverrideId: '', // a built-in or custom persona
environmentId: '', // e.g. "Quiet line"
prompt: 'You want to understand a charge on your latest invoice.',
},
agentExpectations: [{ prompt: 'Agent explains the charge clearly' }],
})
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
flow = client.customer_flow.create(
type="IMPROV",
title="Billing questions",
agent_ids=[""],
happy_path={
"title": "Asks about a charge",
"personaOverrideId": "",
"environmentId": "",
"prompt": "You want to understand a charge on your latest invoice.",
},
agent_expectations=[{"prompt": "Agent explains the charge clearly"}],
)
```
```yaml roark/flows/billing-questions.yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
kind: flow
type: improv
name: billing-questions
agents: [frontdesk]
happyPath:
persona: frustrated-caller
environment: Quiet line
prompt: You want to understand a charge on your latest invoice.
expectations:
- Agent explains the charge clearly
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark config apply ./roark
```
Need ids? Grab a built-in [persona](/documentation/simulation-testing/personas) and environment with `simulationPersona.list()` / `simulationEnvironment.list()` (see the [first-simulation quickstart](/documentation/getting-started/introduction#your-first-simulation)). Both are required on the happy path.
## What is a customer flow?
A customer flow describes one type of customer conversation: who the customer is, what they want, the variants you need to cover (language, mood, environment), and the **agent expectations** the agent is graded against. Flows live under **Simulate → Customer flows** in the sidebar.
Today, flows drive simulations: you attach them to run plans and Roark generates test calls from them. The same definition is what live-call analytics will grade incoming traffic against next.
Customer flows supersede the "scenarios" concept from earlier versions of Roark. The REST API and SDKs still use the older name. For example, run requests take a `scenarios` field.
Every flow has:
| Part | What it does |
| :---------------------- | :------------------------------------------------------------------------------- |
| **Title & description** | Identify the flow in the hub and in run reports |
| **Linked agent(s)** | At least one agent must be linked: saving is blocked otherwise |
| **Labels** | System labels (Adversarial, Health check, Voicemail) and free-form custom labels |
| **Agent expectations** | LLM-graded instructions, inherited by every variant |
| **Variants** | One default **happy path** plus any number of **edge cases** |
***
## Authoring modes: Improv vs Scripted
Each flow is authored in one of two modes, toggled with the **Improv / Scripted** pill in the editor header.
| | **Improv** | **Scripted** |
| :-------- | :-------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| You write | A free-text customer brief: the situation the customer is in, what they want, how they behave | The conversation itself, step by step, on a graph canvas |
| Each run | The simulator improvises a fresh conversation: different words, same intent | Walks your graph, following the exact lines you authored. [Branching](#branching-how-paths-become-calls) decides whether every path runs as its own call or one call branches live |
| Best for | Realistic, open-ended conversations; broad behavioral coverage | IVR menus, DTMF entry, deterministic routing, precise turn-by-turn checks |
| Variants | You author them by hand | Derived automatically: one per path through the graph |
Switching modes is non-destructive: both data shapes coexist on the flow, so you can toggle back and forth. If the other mode already has authored content, the editor asks you to confirm the switch. Your work is retained either way.
In the GraphQL API, Improv mode is the `UNSCRIPTED` value of the `FlowMode` enum. There is no `IMPROV` enum value. The UI label is newer than the API name.
### Improv mode
The improv editor is organized into three numbered sections:
1. **Happy path**, the customer setup: a free-text brief describing the situation the customer is in, what they want, and how they typically behave. This section also carries the happy path's Persona, Environment, and Variables chips, and a **Link preceding flow** control (see [flow composition](#composing-flows) below).
2. **Agent expectations**: the required agent picker plus expectation rows, inherited by every variant.
3. **Edge cases**: additional variants that inherit from the happy path and override only what differs.
### Scripted mode
Scripted mode replaces the form with a full-screen canvas where you author the conversation as a graph of steps. See [the scripted graph](#the-scripted-graph) for step kinds and rules.
***
## Variants: happy path and edge cases
A variant is one per-caller configuration of the flow: a title, persona, environment, variables, per-variant labels, and optional additional expectations.
* **Exactly one variant is the default**: the **happy path**. It must have a customer setup and a persona.
* **Every other variant is an edge case.** Edge cases inherit the happy path's setup, persona, and environment unless they override them. The setup field on an edge case reads "Inherited from the happy path, type to override."
* **Additional expectations append.** An edge case can add expectations that apply just to that variant; they never replace the flow-level set.
* Each edge case can carry its **own labels**, and its menu lets you **Mark as happy path** (making it the new default) or **Delete edge case**.
An edge case that overrides nothing is pointless, and the editor blocks saving it. Give each edge case at least a different setup, persona, or environment.
### Persona, Environment, and Variables chips
Every variant carries a chip strip configuring who's calling and from where:
* **Persona**: the simulated customer's voice and delivery. The happy path requires one (a new flow auto-seeds the built-in **Polite First-Time Caller** persona, falling back to your project's first persona); the picker can also create or edit personas inline. See [Personas](/documentation/simulation-testing/personas).
* **Environment**: background noise played under the customer's voice: Silent, Office, Coffee shop, City street, Driving, Airport, Children playing, or Thunderstorm. Edge cases also get an **Inherit** option.
* **Variables**: per-variant key/value entries (name, account ID, last visit…) handed to the customer-side model so it can answer the agent's questions consistently. Add rows manually, or use **Apply a test profile** to replace the entries with a saved profile's properties. Prompt and step text support `{{variable}}` tokens with autocomplete: `{{name}}` for flow-scoped properties, `{{persona.name}}` for persona-scoped ones. See [Variables](/documentation/simulation-testing/variables).
***
## Agent expectations
Agent expectations are the graded contract of the flow: short, LLM-evaluated instructions like *"Agent greets warmly and uses the caller's name once."* After each simulated conversation, Roark checks the transcript against every expectation and reports pass/fail per expectation, per call.
* **Flow-level expectations** are inherited by every variant.
* **Additional expectations** on an edge case apply only to that variant, on top of the flow-level set.
* In scripted flows, expectations are also derived from the agent turns along each path: what you wrote the agent should say becomes what it's graded on.
Write expectations as specific, checkable statements. "Agent offers a callback when the customer declines to hold" grades cleanly; "Agent is helpful" doesn't.
***
## Labels
Flows use a single label system with two kinds:
* **System labels**. Roark-curated: `Adversarial`, `Health check`, `Voicemail`. These drive template behavior; for example, the Red teaming template automatically runs flows carrying the Adversarial label. Roark also maintains a `Happy path` label automatically on each flow's default variant, which templates that run happy paths source; you don't apply it by hand.
* **Custom labels**: free-form tags for your own organization.
Labels attach at the flow level and per edge case, and the hub filters by them (alongside a **Mode** filter for Improv vs Scripted).
***
## The scripted graph
In scripted mode you build the conversation as a directed graph rooted at a start node. Click **Add step** to insert a step, then connect steps with edges; selecting a node opens an inspect panel with its kind-specific fields.
### Step kinds
| Step | What it does |
| :---------------- | :-------------------------------------------------------------------------------- |
| **Agent** | What the agent says at this point (also becomes a graded expectation on the path) |
| **Customer** | What the customer says back |
| **First message** | The customer's opening line (only valid immediately after the start node) |
| **DTMF** | The caller presses keypad digits (`0–9`, `*`, `#`; use `#` to mark end-of-input) |
| **Silence** | The caller pauses for 1–60 seconds |
| **Flow link** | Splices another scripted flow's steps in at this point |
The API keeps older names here too: step kinds map to the `SimulationStepType` enum, where a Flow link is `SCENARIO_LINK` and a First message is `CUSTOMER_FIRST_MESSAGE`.
**Looking for the Voicemail step?** Voicemail is a run template now, not a step. See [Voicemail Testing](/documentation/simulation-testing/voicemail). A voicemail has no conversation to graph and only one meaningful variable (which greeting plays), so the template runs Roark's recordings directly. Flows that already contain a Voicemail step keep working; you just can't add new ones.
### Graph rules
* **Branching is allowed**: give a step multiple outgoing edges to fork the conversation.
* **Merges are allowed**: branches can rejoin.
* **Cycles are blocked**: the graph must be a DAG, so a conversation always terminates.
### Paths derive variants
Every unique route through the graph from start to finish is a **path**, and when you save the flow, Roark derives one variant per path. Each path card in the right rail shows a generated description of its route. You don't hand-author scripted variants; you shape the graph and the variants follow.
You can still control *who* runs each path: **Add variant for path** creates an extra copy of a path that runs it with a different persona, environment, or variables. The rail marks the flow's default variant with a **Default** badge and groups the rest under **Edge cases**, with **Make default** and **Delete variant** available per variant.
How those paths turn into *calls* is a separate choice, covered next in [Branching](#branching-how-paths-become-calls).
### Branching: how paths become calls
A branching graph can be run two ways, chosen with the **Branching** control at the top of the variant rail. Both modes speak the exact lines you authored. The choice decides only *when* a branch is picked and *how many calls* a run places. Neither mode changes how metrics or agent expectations grade.
| | **Simulate every path** | **Adapt to your agent** |
| :--------------- | :---------------------------------------------------------- | :---------------------------------------------------------------------------- |
| Branch is picked | Up front, when the flow is saved | During the call, from what your agent actually says |
| Calls per run | One per variant the run includes | One per persona among the included variants |
| Coverage | Every included path, every run | One path per call, whichever the agent leads to |
| Best for | Regression suites, full branch coverage, DTMF and IVR entry | Agents whose replies you can't predict; shared blocks linked into other flows |
Take a caller asking for a Tuesday appointment, where the agent's own answer forks the conversation:
```mermaid theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
graph TD
C1["👤 Hi, can I book a\n cleaning for Tuesday?"] --> A1["🤖 Tuesday at\n 9am is open"]
C1 --> A2["🤖 Tuesday is\n fully booked"]
A1 --> C2["👤 Perfect,\n book it"]
A2 --> C3["👤 What about\n Wednesday?"]
```
**Simulate every path** places two calls. Call 1 always takes the left branch and call 2 always takes the right one, even when your agent says something the other branch was written for. That is the point: a path that stopped matching your agent's behavior surfaces as a failure instead of being quietly routed around.
**Adapt to your agent** places one call. The simulated customer hears which reply your agent actually gave and follows that branch; the branch it didn't take isn't exercised on that call.
The **Branching** picker in the app shows the call count each mode places for the flow you're editing, with every variant running once. A run plan's own configuration wins over that figure: selecting a subset of variants, applying a persona override, or setting an iteration count all change what a run actually places.
**Personas multiply calls in both modes.** Adapt to your agent collapses a flow's paths into one call **per persona**, not one call outright. Five paths across three personas run as three calls. On a collapsed call, variable values come from the primary variant; the sibling variants contribute their path shape, not their own values. Sibling-only variable *names* are still detected and prompted for, so they don't vanish silently.
**Linking a multi-path flow requires Adapt to your agent.** A **Flow link** step targeting a flow that is set to *Simulate every path* and has more than one reachable path fails the run: composed fan-out across linked flows isn't supported yet. Either set the linked flow to **Adapt to your agent**, so the run follows whichever path the agent leads to, or link a single-path flow.
Reach for **Adapt to your agent** when you genuinely can't predict the branch: an IVR tree whose menu order varies, or a shared block linked into many flows. Use **Simulate every path** for everything else. It's the mode that gives you coverage you can regress against.
**Adapt to your agent is not looser grading, and it is not goal-level authoring.** Both branching modes speak your exact scripted lines. To have the agent checked against intent ("confirms the caller's name") rather than a script, use an [Improv flow](#authoring-modes-improv-vs-scripted) with agent expectations.
In the GraphQL API and the config-as-code DSL these are the `DETERMINISTIC` and `ADAPTIVE` values of the `ScriptedBranchingMode` enum. The UI labels are newer than the API names.
Structural edits (adding or deleting nodes or edges) freeze variant editing until you save: "You've edited the graph. Save the flow to refresh its paths before adding or removing variants."
***
## Composing flows
Flows compose in two directions:
* **Flow link (scripted → scripted).** A scripted flow can include a **Flow link** step that splices another scripted flow's steps in mid-graph. Use this to reuse a shared segment (an IVR menu, an identity-verification exchange) across many flows. Only scripted flows can be linked this way. A linked flow with more than one path must be set to **Adapt to your agent**; see [Branching](#branching-how-paths-become-calls).
* **Preceded by (scripted → improv).** An improv variant's **Link preceding flow** control names a scripted flow (and optionally a specific variant of it) that runs *before* the improv segment. Typical use: navigate a scripted IVR tree deterministically, then improvise the conversation once a human-like agent picks up.
Improv has no deterministic end, so an improv segment can only ever come last. You can't link *into* an improv flow from a scripted step.
***
## Creating a flow
From the hub, click **New flow** to open the chooser at `/customer-flows/new`. You can generate a draft or start blank:
**Generate: from existing material** (each opens the Ask Roark assistant with a seeded prompt):
* **Describe what you want**: tell Ask Roark the conversation in plain language and it drafts the flow.
* **From a transcript**: upload or paste a transcript and Roark turns it into a flow.
* **From your calls**: pick real calls from your production traffic to base the flow on.
**Or start blank:**
* **Improv: Describe the customer** opens the brief editor.
* **Scripted: Author the conversation** opens the graph canvas.
Variants drafted by Ask Roark carry a **Generated** badge until you review or promote them.
Give it a clear title and a short description of the conversation type it covers.
In Improv, write the customer setup brief; in Scripted, build the graph. Pin a persona, pick an environment, and add any variables the customer should know.
Pick at least one agent, then add expectation rows describing what the agent must do. These are inherited by every variant.
Cover the deviations that matter: a different language, a frustrated caller, a noisy environment, missing information. Override only what differs from the happy path.
The save button surfaces the first blocking issue if anything is missing, for example "Link at least one agent to save", "The happy path needs a prompt", or (scripted) "Add at least one step to the graph".
***
## Using flows in a run
Flows drive simulations through run plans. In the create-run page, the **Attach flows** picker lists your library; expand a flow to choose its variants. Each attachment carries:
* **A variant selection**: all variants, the default variant (happy path) only, or specific variants you pick.
* **An optional persona override**: run the flow as a different customer without editing it. The Multilingual template uses this to fan one flow out across language personas.
* **Variable overrides**: per-attachment `{{variable}}` values that win over the variant's stored values at run time.
When a run starts, it snapshots the attached flows and variants, so the report always reflects exactly what was tested, even if you edit the flow afterwards.
Combine flows, agents, and metrics into reusable test suites
Configure the simulated customer's voice and delivery
Parameterize flows with per-variant and run-time values
Start runs from goals like red teaming or multilingual coverage
# Enriched Simulations
Source: https://docs.roark.ai/documentation/simulation-testing/enriched-simulations
Get richer call data in simulations by sending your agent call to Roark
## Overview
By default, Roark captures the recording from its own simulation agent's side of the conversation. This gives you basic transcript and metric analysis, but it's limited to what Roark can observe externally.
When you also send your agent's call data to Roark, via an [integration](/documentation/integrations/overview) or the [API/SDK](/documentation/observability/overview#how-calls-get-into-roark), Roark matches and merges the two calls. This unlocks significantly richer analysis because your call carries data that only your agent has access to.
***
## What You Gain
Enriched simulations give you access to data from your agent's perspective, enabling deeper analysis:
| Data | Description |
| :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------- |
| **Full Transcript** | Your agent's transcript, which may be higher quality or include internal annotations |
| **Tool Invocations** | The actual [tool calls](/documentation/tool-invocations) your agent made during the conversation (e.g., database lookups, booking actions) |
| **OpenTelemetry Traces** | Backend [traces](/documentation/observability/traces) showing your agent's internal processing |
| **Properties & Metadata** | Any custom properties or context your agent attaches to the call |
This additional context enables metrics that aren't possible with the simulation recording alone. For example, the **Tool Invocation** metric can verify whether your agent actually called the right tool with the correct parameters, not just whether it *said* it would.
***
## Enabling Call Merging in a Run
Call merging is controlled per run. In the create-run **Metrics** block, turn on **Collect selected metrics from your agent's side of the call**. When the toggle is on, Roark waits for your agent's copy of each call and scores the selected metrics against it.
### Per-Metric Conversation Source
With the toggle on, each selected metric has a conversation source that determines which side of the call it's scored against:
| Source | Scored against | Best for |
| :------------ | :---------------------------------------------------------------------------- | :-------------------------------------------------------- |
| **Simulated** | Roark's side of the call: the simulation agent's own recording and transcript | Metrics that only need what's audible in the conversation |
| **Live** | Your agent's side of the call: the call data your agent sends to Roark | Metrics that need internal data your agent carries |
### Live-Only Metrics
Some metrics can only be evaluated from your agent's side of the call: **tool invocations** are the canonical example, since only your agent knows which tools it actually called. Selecting a live-only metric locks the call-merging toggle on: you can't disable it while that metric is in the run.
### Waiting and Fallback
When call merging is enabled, Roark waits **up to 15 minutes** for your agent's side of each call to arrive. If it doesn't show up in that window, Roark falls back and completes analysis with the simulated side only. Live-sourced metrics won't have your agent's data to score against.
Send calls to Roark as soon as they end. The sooner your agent's side arrives, the sooner merged results appear in the run report, and you avoid hitting the 15-minute fallback.
The API field behind the toggle keeps its internal name, `enrichWithLiveConversation`.
### Attaching Tool Invocations Directly
If the only agent-side data you need is **tool invocations**, you don't have to send a full second call and wait for it to merge. Instead, attach the tool calls straight to the simulation's call once it exists:
1. Find the simulation's call (list calls filtered by `simulationRunPlanJobId`, or read it from the run).
2. Attach the tools to it via [`POST /v1/call/{callId}/tool-invocations`](/documentation/tool-invocations#attaching-tool-calls-after-a-call), optionally passing `metrics` to score the tool metrics in the same request.
This sidesteps phone/timing matching and the 15-minute wait entirely, so it's the simplest path when you just want tool metrics on a simulated call. Attaching is idempotent, so it's safe to retry.
***
## How Matching Works
Once your agent's call arrives, Roark matches it to the simulation using two key signals:
### Phone Number
The phone number your agent interacted with is matched against the number Roark provisioned for the simulation. This is the same dynamically assigned number described in [Identifying Simulations](/documentation/simulation-testing/identifying-simulations).
### Timing
The call must have started during the simulation's active window. Roark checks that your call's start time falls within the time range when the simulation phone number was in use for that specific test case.
Both signals must match for the calls to be merged. This ensures accuracy even when phone numbers are reused across different runs.
Matching is bidirectional. It doesn't matter which call arrives first. If your agent's call arrives before Roark finishes processing the simulation, or vice versa, the merge happens once both are available (within the wait window described above).
`externalId` does **not** merge a call with a simulation. It correlates a call with [OpenTelemetry traces](/documentation/observability/traces) only. Simulation merging uses the phone number and timing above. If you set `externalId` expecting a merge, the calls will not join, and your live-sourced metrics will fall back to the simulated side. To attach agent-side tool data without relying on the merge, use [Attaching Tool Invocations Directly](#attaching-tool-invocations-directly).
***
## Setup
Beyond the run toggle, the only requirement is that your agent already sends calls to Roark. If you haven't set up call ingestion yet:
1. **Choose your method**: Use a [voice platform integration](/documentation/integrations/overview) or send calls via the [API/SDK](/documentation/observability/overview#how-calls-get-into-roark)
2. **Ensure calls include the phone number**: Your call data must include the phone number that participated in the simulation so Roark can match it
3. **Enable the toggle when creating a run**: Turn on **Collect selected metrics from your agent's side of the call** in the Metrics block and pick the source for each metric
If you're using a voice platform integration like [Vapi](/documentation/integrations/vapi), [Retell](/documentation/integrations/retell), or [LiveKit](/documentation/integrations/livekit), calls are typically sent to Roark automatically, meaning enriched simulations work out of the box once you enable the toggle.
***
## Best Practices
To get the most out of enriched simulations, send [tool invocations](/documentation/tool-invocations) with your call data. This enables live-sourced metrics that verify whether your agent executed the correct actions, not just whether it generated the right words.
Roark waits up to 15 minutes for your agent's side before falling back to the simulated recording. Sending calls shortly after they end keeps live-sourced metrics complete and results fast.
Make sure the phone number in your call data matches the number your agent actually used during the simulation. Mismatched or reformatted numbers will prevent matching.
If your agent supports [OpenTelemetry tracing](/documentation/observability/traces), include trace data with your calls. This gives you full visibility into your agent's internal decision-making during simulations.
***
## Next Steps
Learn how to submit tool calls with your call data
Set up OpenTelemetry tracing for deeper analysis
Connect your voice platform for automatic call ingestion
Understand how simulation calls are identified
# Identifying Simulations
Source: https://docs.roark.ai/documentation/simulation-testing/identifying-simulations
Verify simulation calls and retrieve simulation details from your agent
## Overview
When your agent receives calls during simulation testing, you may need to verify whether a call is from a Roark simulation and retrieve details about the specific test being executed. Roark provides a dedicated API endpoint to identify simulations based on phone numbers and call timing.
## Identifying Simulation Calls
### Using the Simulation Job Lookup API
To verify if a call is coming from a simulated Roark agent or to retrieve simulation details, use the [Lookup Simulation Job by Phone Numbers API](https://docs.roark.ai/api-reference/simulation-job/lookup-by-phone-number).
This API allows you to:
* Verify if an incoming or outgoing call is part of a simulation test
* Retrieve the specific simulation job details
* Track which persona and customer flow are being tested in the simulation (the response includes the persona name as `simulationPersonaName`)
### API Parameters
The API requires specific parameters to identify the simulation:
| Parameter | Description | Usage |
| :------------------------ | :----------------------------- | :-------------------------------------------- |
| **roarkAgentPhoneNumber** | The Roark agent's phone number | See direction-specific details below |
| **callStartedAt** | When the call was initiated | Must be within the simulation's active window |
### Understanding the `roarkAgentPhoneNumber` parameter
The `roarkAgentPhoneNumber` parameter depends on the call direction:
**For Inbound Calls**:
* Roark initiates the call from a dynamically provisioned number
* Your agent receives the call from this number
* In this case, `roarkAgentPhoneNumber` is the number that dials your agent
**For Outbound Calls**:
* Roark provisions a number for each test case in a simulation
* When a simulation for a test case starts, this number is either sent via an HTTP request for your agent to trigger the outbound call, or manually dialed at runtime based on the number shown on the dashboard. See [this page](/documentation/simulation-testing/inbound-vs-outbound#triggering-an-outbound-call) for more information.
* The Roark simulation agent receives this call and starts the simulation
* In this case, `roarkAgentPhoneNumber` is the number you receive via the HTTP request, or the number displayed on the simulation run page for manual calls
### Understanding the `callStartedAt` parameter
While a phone number is assigned to only one simulation at a time, once the simulation ends it may be reused for other simulations. As such, the `callStartedAt` timestamp is critical for matching the correct simulation:
* **Timing Window**: The call must occur within the simulation's execution timeframe. Any time between the simulation test case starting and the call ending is valid.
* **Default Value**: Current date. This is useful when calling the endpoint to match a call in real-time (examples 1 and 2 below). For historical calls, you should pass the call creation time to match the correct call.
* **Valid examples**:
1. Using **current date** as `callStartedAt` whenever you receive an inbound call from Roark in your agent's inbound webhook (dependent on the provider)
2. Using **current date** as `callStartedAt` whenever you receive an HTTP request to trigger an outbound call for a Roark simulation.
3. Using any time between the above two options and the end time of the call.
* **Format**: ISO 8601 timestamp (e.g., `2024-01-15T14:30:00Z`)
The `callStartedAt` parameter helps distinguish between multiple simulations
that might use the same phone numbers at different times.
***
## Best Practices
### Integration Recommendations
Use the data returned by the API to tag calls on your platform as test calls
and link directly to Roark's test results
Leverage persona details to add context to your agent
Use simulation identification to segregate test data from production metrics
Implement test-specific behaviors when handling identified simulation calls
***
## Next Steps
View the complete API documentation
Understand call direction differences
Configure your run plans
Execute your simulation tests
# Inbound vs Outbound
Source: https://docs.roark.ai/documentation/simulation-testing/inbound-vs-outbound
Understanding simulation testing directions
## Overview
When running simulations in Roark, there are two fundamental testing directions based on who initiates the call. This affects how your tests execute, what you can test, and how you configure your simulations.
You don't choose a direction when building a run. It's derived from the
agent endpoints you select. Each endpoint has a direction (Inbound, Outbound,
or "In + out"), and a run is outbound only when every selected endpoint is
outgoing-only. Otherwise the run executes inbound.
***
## Inbound Testing
In inbound testing, **Roark calls your agent** to simulate incoming customer calls.
### How It Works
1. You select an agent endpoint that accepts incoming calls (direction Inbound or "In + out")
2. Roark initiates calls to your agent
3. The simulated customer (persona) interacts with your agent
4. Your agent responds as it would to a real incoming call
### Characteristics
* **Immediate execution** - Tests run as soon as triggered
* **Multiple iterations** - Can repeat the same test case up to 100 times
* **High control** - Roark controls timing and execution
* **Synchronous results** - Get results immediately after completion
### Use Cases
Perfect for testing:
* **Customer service lines** - Support hotlines, help desks
* **IVR systems** - Automated phone menus
* **Receptionist agents** - Virtual assistants answering calls
* **Emergency response** - Crisis hotlines, urgent support
* **Inbound sales** - Agents handling incoming inquiries
### Configuration
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
Endpoint direction: Inbound (or "In + out")
Agent Endpoint: +1-555-0100 or sip:agent@domain.com
Iterations: 1-100 (configurable)
Concurrency: Default 5 parallel calls, capped by your account quota
```
***
## Outbound Testing
In outbound testing, **your agent calls Roark** to simulate outbound campaigns. Roark runs a plan outbound when every selected agent endpoint is outgoing-only.
### How It Works
1. Roark provisions a phone number for each test case in your run
2. Your agent can start the call either by exposing an outbound call endpoint that Roark will trigger automatically, or by directly calling the provisioned number shown on the run detail page.
3. Roark answers as the simulated customer (persona)
4. Your agent conducts the outbound call against the customer flow you attached
#### Triggering an outbound call
In an outbound simulation, your agent is responsible for starting the simulated call. This can be done in two ways:
#### HTTP Endpoint (Suggested)
By setting up an outbound call HTTP request, Roark can automatically start your outbound tests for you - giving you the benefits of scheduling and concurrency control.
1. Set up an HTTP request on the agent endpoint that will be used in the simulation
2. Whenever a simulation is started for this agent endpoint, Roark will make the specified HTTP request passing in the provisioned phone number as `{{phoneNumberToDial}}`.
3. This HTTP request is expected to trigger your agent to start an outbound call by dialing `{{phoneNumberToDial}}`.
This automates the workflow of running outbound tests - Roark will handle generating the number and calling your endpoint, and your system simply initiates the call.
Template variables such as `{{ phoneNumberToDial }}` can be used in the `URL`,
`body`, and `headers`.
#### Provider Examples
Below are minimal example configurations for common voice AI providers. The `{{phoneNumberToDial}}` template variable will be replaced with the provisioned Roark number at runtime.
**URL:**
```
POST https://api.vapi.ai/call
```
**Headers:**
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
```
**Body:**
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"customer": {
"number": "{{phoneNumberToDial}}"
},
"assistantId": "",
"phoneNumberId": ""
}
```
See the [VAPI API documentation](https://docs.vapi.ai/api-reference/calls/create-call) for additional options.
**URL:**
```
POST https://api.retellai.com/v2/create-phone-call
```
**Headers:**
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
```
**Body:**
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"from_number": "",
"to_number": "{{phoneNumberToDial}}",
"override_agent_id": ""
}
```
See the [Retell API documentation](https://docs.retellai.com/api-references/create-phone-call) for additional options like `metadata`, `retell_llm_dynamic_variables`, and agent overrides.
**URL:**
```
POST https://api.elevenlabs.io/v1/convai/batch-calling/submit
```
**Headers:**
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"xi-api-key": "",
"Content-Type": "application/json"
}
```
**Body:**
```json theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"call_name": "",
"agent_id": "",
"recipients": [
{
"phone_number": "{{phoneNumberToDial}}"
}
]
}
```
See the [ElevenLabs API documentation](https://elevenlabs.io/docs/api-reference/batch-calling/create) for additional options.
#### Manual Calls
If an HTTP request is not configured, Roark displays the provisioned phone number for each test case on the run detail page while the call shows as "Waiting for call". You can then manually trigger your agent to dial that number.
Alternatively, you can trigger calls programmatically using our APIs. This is useful when you need full control over when simulations run within your own orchestration logic.
Start a simulation by calling the [Run a Simulation Plan](/api-reference/simulation-run-plan-job/run-a-simulation-plan) endpoint with your run plan ID.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
POST /v1/simulation/plan/{planId}/job
```
This returns a `simulationRunPlanJobId` for tracking the executed simulation run plan job.
Retrieve the job details using the [Get Simulation Plan Job](/api-reference/simulation-run-plan-job/get-simulation-plan-job) endpoint.
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
GET /v1/simulation/plan/job/{jobId}
```
The response includes a list of individual simulation jobs.
Look for jobs with `status: "WAITING_FOR_OUTBOUND_CALL"` (shown as "Waiting for call" in the dashboard). For each of these, have your agent dial the `roarkPhoneNumber` provided in the job object to start that simulation.
Depending on your run settings, some jobs may be queued while others are in progress. Monitor when each call completes, then repeat steps 2–3 until the [Get Simulation Plan Job](/api-reference/simulation-run-plan-job/get-simulation-plan-job) endpoint returns `status: "COMPLETED"`.
While the programmatic approach offers flexibility, we recommend using HTTP
requests instead. Roark will automatically trigger calls at the right time,
handling queued simulations and concurrency limits for you.
For both HTTP requests and manual calls, Roark expects the outbound call to be
made by the number set in your agent endpoint. Calls made to the provisioned
number from numbers other than the one defined in your agent endpoint will be
ignored.
### Characteristics
* **Asynchronous execution** - Tests wait for your agent to call
* **Single iteration** - Outbound runs are always one iteration per test case
* **Agent-controlled timing** - Your system decides when to call
* **Waiting state** - Test cases show "Waiting for call" until your agent dials
### Use Cases
Perfect for testing:
* **Sales campaigns** - Cold calling, lead follow-up
* **Appointment reminders** - Medical, service appointments
* **Survey calls** - Customer satisfaction, market research
* **Collections** - Payment reminders, account management
* **Notifications** - Alert calls, status updates
### Configuration
```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
Endpoint direction: Outbound (all selected endpoints outgoing-only)
Phone Numbers: Provisioned by Roark
Iterations: 1 (fixed)
Timeout: Configurable (default 24 hours)
```
## Key Differences
| Aspect | Inbound | Outbound |
| :----------------- | :------------------------------ | :----------------- |
| **Who calls who** | Roark → Your Agent | Your Agent → Roark |
| **Execution** | Immediate | When agent calls |
| **Iterations** | Multiple supported (up to 100) | One per test case |
| **Use case** | Customer service | Sales/outreach |
| **Timing control** | Roark controls | You control |
| **Phone numbers** | Use your existing | Roark provisions |
| **Concurrency** | Default 5, capped by your quota | Limited by numbers |
| **Results** | Immediate | After agent calls |
***
## Choosing the Right Direction
Since direction follows from your agent endpoints, "choosing" a direction means selecting the right endpoints for the behavior you want to test.
### Test Inbound When:
* Testing customer service flows
* You need immediate results
* Running high-volume tests
* Testing response to incoming requests
* Validating IVR flows
### Test Outbound When:
* Testing sales or marketing campaigns
* Your agent initiates contact
* Testing dialer integrations
* Validating outreach scripts
* Testing follow-up workflows
***
## Technical Considerations
### Inbound Testing
* Ensure your agent can handle concurrent calls
* Configure appropriate rate limits
* Test your agent's scaling capabilities
* Monitor response times under load
### Outbound Testing
* Plan for phone number provisioning time
* Set appropriate timeouts for pending tests
* Configure your dialer to call Roark numbers
* Handle failed call attempts gracefully
***
## Best Practices
Begin with inbound testing for immediate feedback before moving to outbound
Choose endpoints that match how your agent operates in production
If your agent handles both, test both directions for complete coverage
For outbound, track which numbers have been called to avoid duplicates
Configure appropriate timeouts for outbound tests based on your calling
patterns
## Next Steps
Build test suites around your agent endpoints
Set up customer profiles for testing
Execute your inbound or outbound tests
Author the conversations your simulations follow
# Overview
Source: https://docs.roark.ai/documentation/simulation-testing/overview
Pressure-test your voice AI agents before your customers do
**UI-first or code-first?** Build your customer flows and personas (and the agents they target) in the dashboard, or define them as [config as code](/documentation/config-as-code/overview) - YAML in your git repo, applied with the [CLI](/documentation/sdks/cli). Both manage the same resources; run plans and execution live in the UI and API.
## Quickstart
Already have an agent and a [customer flow](/documentation/simulation-testing/customer-flows)? Run a simulation against them:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
const run = await client.simulation.run({
plan: {
direction: 'INBOUND',
maxSimulationDurationSeconds: 300,
agentEndpoints: [{ id: '' }],
flows: [{ id: '', happyPath: true }],
metrics: [{ slug: 'task_completion' }],
},
})
console.log(run.data.simulationRunPlanJobId)
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
run = client.simulation.run(plan={
"direction": "INBOUND",
"maxSimulationDurationSeconds": 300,
"agentEndpoints": [{"id": ""}],
"flows": [{"id": "", "happyPath": True}],
"metrics": [{"slug": "task_completion"}],
})
print(run.data.simulation_run_plan_job_id)
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark simulation run --data '{
"plan": {
"direction": "INBOUND",
"maxSimulationDurationSeconds": 300,
"agentEndpoints": [{ "id": "" }],
"flows": [{ "id": "", "happyPath": true }],
"metrics": [{ "slug": "task_completion" }]
}
}'
```
Starting from scratch? Follow the [full zero-to-first-simulation quickstart](/documentation/getting-started/introduction#your-first-simulation).
## What is simulation testing?
Simulation testing lets Roark act as your customers. You describe the conversations your agents should handle (who's calling, what they want, how the call should go) and Roark places real calls (or chat sessions) against your agents, grades every conversation, and reports pass/fail results.
Everything revolves around **plans** and **runs**. A plan is a reusable test suite; a run is one execution of it. Each run is labelled `SR-{n}` and moves through **Queued → Running → Completed** (or **Failed** / **Cancelled**).
A plan composes four things:
| Building block | What it defines |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Agent targets** | Which agents to test and how to reach each one: an agent plus an endpoint (Phone, WebRTC, LiveKit, WebSocket, ElevenLabs, Google CES, Kore) with a direction (inbound, outbound, or both) |
| **Customer flows** | The conversations to exercise: each flow attached with a variant selection (all variants, the happy path only, or specific variants) |
| **Metrics & checks** | What to measure on every conversation, with pass/fail thresholds that decide the run's verdict |
| **Run settings** | Iterations, concurrency, execution mode, and end conditions like max duration and silence timeout |
You'll find everything under the **Simulate** section of the sidebar: **Simulations** (the runs hub), **Customer flows**, and **Personas**.
***
## Core concepts
### Customer flows
A [customer flow](/documentation/simulation-testing/customer-flows) describes one type of conversation your agent should handle. Author it in **Improv** mode (write a free-text customer brief and the simulator improvises a fresh conversation each run) or **Scripted** mode (author the conversation step by step on a graph canvas, ideal for IVR menus and deterministic routing).
Every flow has a default variant (the **happy path**) plus optional **edge cases** that vary the persona, environment, or setup. The flow's **agent expectations** are the graded contract: LLM-checked instructions the agent is evaluated against on every variant.
### Personas
A [persona](/documentation/simulation-testing/personas) is who's calling: language, accent, gender, base emotion, and how they speak. Each flow variant pins a persona, and run plans can override it per attachment, for example, fanning one flow across language personas in a multilingual run.
### Plans and runs
A [plan](/documentation/simulation-testing/run-plans) is the saved suite; a [run](/documentation/simulation-testing/running-simulations) is one execution. Run a plan on demand, put it on a [schedule](/documentation/simulation-testing/schedules), or skip saving entirely. Unchecked **Save as plan** gives you a one-off run. [Templates](/documentation/simulation-testing/templates) like Red teaming, Multilingual, and Load testing pre-configure metrics, checks, and flow sourcing so you start from a goal instead of a blank page.
### Metrics and checks
Every run scores its conversations with [metrics](/documentation/metrics/overview): system metrics out of the box, plus [custom metrics](/documentation/metrics/custom-metrics) you build in [Studio](/documentation/metrics/studio). Pass/fail [thresholds](/documentation/metrics/thresholds) turn metrics into checks, and the run report's verdict is the pass rate across every check on every conversation.
***
## How a run comes together
From **Simulations → New Run**, select agents and the endpoints to reach them on. Run direction is derived from your endpoints: inbound means Roark calls your agent; outbound means your agent calls Roark.
Start from a goal (Flow adherence, Red teaming, Conversation quality, Multilingual, Load testing, Tool call accuracy) or Blank. Each template ships preset metrics and pass/fail checks, then attach the customer flows the run should exercise.
The **Advanced** section covers the metric set, iterations and concurrency, and end conditions (max duration, silence timeout, end-call phrases). Check **Save as plan** to keep the configuration as a re-runnable suite.
Run now or schedule it. While the run is live you can watch calls in progress and listen in; once it settles, the report shows the verdict, per-metric results, run-over-run comparison, and every conversation's transcript.
The REST API and SDKs keep the older naming: customer flows are still `scenarios` in request fields, and run plans are managed via the same endpoints as before. Code examples throughout these docs reflect the API's names.
***
## Common use cases
* **Pre-deployment testing**: exercise every flow's happy path and edge cases before an agent goes live
* **Regression testing**: save a plan, re-run it after every change, and compare runs side by side in the report's Comparison tab
* **Continuous monitoring**: schedule plans hourly, daily, or weekly to catch regressions before you do
* **Safety and robustness**: the Red teaming template runs adversarial flows against your agent; Load testing checks behavior under concurrent call volume
***
## Where to go next
Author the conversations your agents should handle: Improv briefs or Scripted graphs
Define the callers your simulations run as
Start a run from a goal with preset metrics and checks
Build reusable suites and run them on a cadence
Launch runs, monitor them live, and read the report
Strategies for building an effective test suite
For chat-based agents, see [Chat simulations](/documentation/simulation-testing/chat-simulations). To score metrics from your agent's side of the call, see [Enriched simulations](/documentation/simulation-testing/enriched-simulations).
# Personas
Source: https://docs.roark.ai/documentation/simulation-testing/personas
The callers your simulations run as
**Prefer to keep this in your repo?** Personas can be defined as [config as code](/documentation/config-as-code/personas) (YAML in git, applied with the [CLI](/documentation/sdks/cli)) instead of built in the dashboard.
## Quickstart
Reuse a built-in persona, or create your own:
```typescript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// Reuse a built-in persona (list includes Roark's starter set)
const personas = await client.simulationPersona.list({ limit: 50 })
// Or create a custom one
const persona = await client.simulationPersona.create({
name: 'Busy Parent',
language: 'EN',
accent: 'US',
gender: 'FEMALE',
})
```
```python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
personas = client.simulation_persona.list(limit=50)
persona = client.simulation_persona.create(
name="Busy Parent", language="EN", accent="US", gender="FEMALE",
)
```
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
roark simulation persona list --limit 50
roark simulation persona create --name "Busy Parent" --language EN --accent US --gender FEMALE
```
## Overview
A persona is the caller a simulation runs as. It bundles who the customer is (language, accent, gender, base emotion, and how they speak) into a reusable caller that you pin to a [customer flow](/documentation/simulation-testing/customer-flows) variant. Roark ships a starter set of **Built-in** personas; add your own **Custom** personas for the callers you care about.
Open **Personas** in the sidebar to see every persona in your project. From the hub you can search, create new personas, and edit or delete the custom ones.
## Persona fields
Click **New persona** to open the persona dialog. Every field except Name is optional or has a sensible default.
| Field | Options | Default | Notes |
| :------------------ | :--------------------------------------------------------------------- | :------- | :------------------------------------------------------------------- |
| **Name** | Free text | N/A | Required. Use something descriptive, e.g. "Returning client: Sarah" |
| **Display name** | Free text | Name | Shown in pickers and variant chips. Defaults to the name |
| **Description** | Free text | N/A | One or two sentences on who this caller is (hub dialog only) |
| **Language** | 18 languages | English | See the full list below |
| **Accent** | 23 accents | American | Includes American (Southern) as a distinct option |
| **Gender** | Female, Male, Neutral | Female | Affects the synthesized voice |
| **Base emotion** | Neutral, Cheerful, Frustrated, Skeptical, Rushed, Distracted, Confused | Neutral | The caller's underlying emotional tone |
| **Speech pace** | Very slow, Slow, Normal, Fast, Very fast | Normal | How quickly the persona speaks |
| **Clarity** | Clear, Vague, Rambling | Clear | How directly they express themselves |
| **Response timing** | Quick, Normal, Relaxed | Normal | How quickly the persona responds to pauses in conversation |
| **Disfluencies** | On / Off | Off | Natural ums, ahs, false starts. Off means the persona speaks cleanly |
English, Spanish, French, German, Italian, Portuguese, Dutch, Arabic, Greek,
Hebrew, Hindi, Indonesian, Malay, Tagalog, Thai, Japanese, Chinese, Turkish
American, American (Southern), British, Australian, New Zealand, Indian,
Spanish, French, German, Italian, Portuguese, Dutch, Greek, Arabic, Hebrew,
Indonesian, Malaysian, Filipino, Singaporean, Hong Kong, Thai, Japanese,
Turkish
Background noise is not a persona setting. It's configured per flow variant
via the **Environment** chip (Silent, Office, Coffee shop, City street,
Driving, Airport, Children playing, Thunderstorm). See [Customer
flows](/documentation/simulation-testing/customer-flows).
## Built-in vs Custom
Every persona has a source badge:
* **Built-in**: the starter set Roark ships with each project. Built-in personas are read-only in the hub: they have no edit or delete actions.
* **Custom**: personas you create. Fully editable and deletable.
You can still start from a Built-in persona: load it in the inline persona editor inside a flow, tweak it, and save. Roark shows the note "System persona: saving creates an editable copy." and creates a Custom copy with your changes, leaving the original intact.
Deleting a persona doesn't break existing flows silently, but customer-flow
variants pinned to it will need a new persona.
## Pinning personas to flow variants
Personas do their work inside customer flows. Each variant in a flow carries a chip strip (**Persona · Environment · Variables**) and the Persona chip shows which caller that variant runs as.
* The flow's **default variant** (the happy path) must always have a concrete persona. New flows are pre-seeded with the Built-in **Polite First-Time Caller** persona (or your project's first persona if that one isn't available).
* **Non-default variants inherit** the default variant's persona automatically. The chip shows the name in muted italics, e.g. "Sarah (inherited)". Pin a different persona on a variant to override the inheritance for that variant only.
### The inline persona editor
Clicking a variant's Persona chip opens an inline editor with two paths:
Click **Pick from existing personas** to open the "Pick a persona" dialog.
Search by name, language, or accent; each row shows the persona's name,
Language · Accent, and its Built-in or Custom badge. Picking one pins it to
the variant.
Fill in the same fields as the hub dialog (minus Description) directly in
the panel (including a color-coded Base emotion pill grid) and click
**Create & use** to create the persona and pin it in one step.
With a persona pinned, the editor shows "Pinned: \". Editing a
Custom persona and clicking **Save changes** updates it in place. Editing a
Built-in persona creates an editable Custom copy instead.
Because Custom personas are shared across flows, "Save changes" in the inline
editor updates that persona everywhere it's pinned. If you want a variant-only
tweak, create a new persona instead.
## Personas at run time
When a [run](/documentation/simulation-testing/running-simulations) executes, each simulated call is placed as the persona pinned to its flow variant. On the resulting call records, the persona appears as `simulationPersonaName`: the display name of the persona used in the simulation that created the call.
If a persona used by a run has properties without values, Roark prompts for them in the runtime-variables step before the run starts; the values you enter are saved onto the persona itself, so future runs reuse them. See [Variables](/documentation/simulation-testing/variables).
## API
Manage personas programmatically to build them from real customer data or wire persona management into CI:
* **Create Persona**: `POST /v1/persona`
* **Update Persona**: `PUT /v1/persona/{personaId}`
* **Get Persona**: `GET /v1/persona/{personaId}`
* **List Personas**: `GET /v1/persona`
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -X POST https://api.roark.ai/v1/persona \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Emma Johnson",
"language": "EN",
"accent": "US",
"gender": "FEMALE",
"baseEmotion": "NEUTRAL",
"speechPace": "NORMAL",
"speechClarity": "CLEAR",
"responseTiming": "NORMAL",
"hasDisfluencies": false,
"properties": {
"accountNumber": "ACC-123456",
"zipCode": "90210"
}
}'
```
The API exposes some capabilities the platform UI doesn't surface: a
`backstoryPrompt`, key/value `properties`, `secondaryLanguage`, idle-message
settings, and the behavioral fields `backgroundNoise`, `intentClarity`,
`confirmationStyle`, and `memoryReliability`. Personas created in the UI use
system defaults for these (`NONE`, `CLEAR`, `EXPLICIT`, `HIGH`); set them via
the API if you need finer control.
For all parameters and response formats, see the [Persona API Reference](/api-reference/simulation-persona/create-a-new-persona).
## Best practices
* **Start with 3–5 core personas** representing your most common caller types, then add edge cases as you discover them.
* **Mirror reality**: base personas on real customer data and call recordings.
* **Test extremes**: a Frustrated, Fast, Rambling caller stresses your agent in ways a Neutral one won't.
* **Name for the test**: "Confused elder: password reset" tells your team more than "Robert".
## Next steps
Pin personas to flow variants and define what should happen on the call
Plan and launch runs that execute your flows as these callers
# Run Plans
Source: https://docs.roark.ai/documentation/simulation-testing/run-plans
Save simulation configurations as reusable test suites you can run on demand, on a schedule, or via API
## Overview
A plan is a reusable test suite. It bundles everything a simulation run needs (the agents to test, the customer flows to exercise, the metrics and Pass/Fail checks to grade against, and the run settings) so you can execute the same suite repeatedly and compare results over time.
Every run starts from a plan. When you configure a run without saving it, Roark still creates a plan behind the scenes: it just stays hidden as a **one-off run**. Check **Save as plan** and the configuration becomes a named plan you can re-run anytime, schedule, and edit.
Plans live under **Simulate → Simulations** in the sidebar. The runs hub has a **Plans / All runs** view toggle: the Plans view shows one card per saved plan, plus a pinned **One-off runs** card for runs that weren't saved as a plan.
***
## Creating a plan
Click **New Run** on the Simulations hub to open the create page. It's a single long-form page with four numbered sections:
Pick the agents and how to reach each one. **Select agents & endpoints** opens a picker; each target is one agent reached on one specific endpoint (Phone, Web RTC, LiveKit, WebSocket, ElevenLabs, Google CES, or Kore) with its direction shown as Inbound, Outbound, or In + out.
Start from a goal. Each template ships with preset metrics, Pass/Fail checks, and a starter configuration. Pick from the card grid (Blank is the default) or open **Browse all templates** for the full library. See [Templates](/documentation/simulation-testing/templates) for what each one does.
The template's own controls: for example, Load testing shows a Volume panel with concurrent calls and total iterations, and Multilingual asks which languages to test. Every template also surfaces a **Flows** panel where you attach the customer flows the run exercises (see below).
Metrics and run settings: the metric picker with Pass/Fail thresholds, the option to [collect metrics from your agent's side of the call](/documentation/simulation-testing/enriched-simulations), run settings, end conditions, and custom variables.
### Save as plan, or run one-off
The footer bar has a **Save as plan** checkbox: "Saves this configuration as a plan you can re-run anytime." Checking it prompts you to name the plan.
* **Checked**: the plan appears in the Plans view with its own detail page, and every execution is recorded in its run history.
* **Unchecked**: the run executes as a one-off. It's grouped under the **One-off runs** card in the Plans view and the **One-off** lens in All runs. You can promote a one-off to a named plan later from the run header.
### Run now, or later
The primary button is a split button. Its face reads **Run simulation** (or **Schedule** when a schedule mode is selected), and the menu offers:
| Option | What it does |
| :---------------------- | :------------------------------------------------------------ |
| **Run now** | Starts the simulation immediately |
| **Schedule recurring…** | Runs the plan on a cadence: hourly, daily, weekly, or monthly |
| **Run once, later…** | Runs a single time at a future date and time |
Choosing a schedule mode reveals the Schedule section: frequency, time, timezone, days, and an end condition (never, on a date, or after N runs). See [Schedules](/documentation/simulation-testing/schedules) for details.
If any variable in the plan lacks a default value, a runtime-variables modal collects the missing values before the run starts.
***
## Direction is derived
You don't choose inbound or outbound. Roark derives it from the endpoints you selected in **Agents to test**. A run is outbound only when every selected endpoint is outgoing-only; otherwise it's inbound. Outbound runs are forced to a single iteration because your agent initiates each call.
Learn more in the [Inbound vs Outbound guide](/documentation/simulation-testing/inbound-vs-outbound).
***
## Attaching flows
The **Attach flows** dialog lists your [customer flow](/documentation/simulation-testing/customer-flows) library. Pick the flows this run exercises, and expand one to choose its variants. Each attachment carries three things:
Per attached flow, choose which variants run:
* **All variants**: the happy path plus every edge case
* **Default variant**: just the happy path
* **Specific variants**: an explicit selection of variants
Templates set this for you where it matters. Multilingual and Load testing pin the default variant, for example.
Optionally override the persona the flow's variants would normally use, just for this plan. The Multilingual template uses this to fan the same flow across language personas, one attachment per language.
Set per-attachment values for the flow's `{{variable}}` tokens. Overrides set here win over the values stored on the variant at run time. See [Variables](/documentation/simulation-testing/variables).
The total number of test calls is the selected variants across all attachments, multiplied by your agent targets and iterations.
***
## Run settings
The **Advanced** section exposes the run's execution and end-condition settings:
| Setting | Description | Default |
| :------------------- | :----------------------------------------------------------------------------------------------------------------------- | :--------- |
| **Iterations** | Runs per test case, 1–100. Forced to 1 for outbound runs. | 1 |
| **Concurrency** | Parallel calls, capped by your account quota | 5 |
| **Execution mode** | Parallel, Sequential (this plan), or Sequential (all plans) | Parallel |
| **Max duration** | Hard cap per simulation, up to 24 hours | 15 minutes |
| **Silence timeout** | Ends the call after sustained silence, 5–300 seconds | 30 seconds |
| **End-call phrases** | Phrases that end the call when spoken (max 10; empty disables) | `goodbye` |
| **End-call reasons** | Semantic conditions evaluated by an LLM that end the call, e.g. "The agent confirmed the order" (max 10; empty disables) | None |
The sequential execution modes run calls one at a time (scoped either to this plan's runs or to every plan in the project), which is useful when your agent can't handle parallel traffic.
### Run-plan variables
Plans can define their own variables (a name, a value type, and an optional default), referenced as `{{name}}` in flow prompts and settings. Variables without a default are collected in a modal each time the plan runs, which makes them handy for values that change per run, like a booking date or a ticket number.
***
## The plan detail page
Every saved plan has a detail page with the plan name, its template badge, the agents it targets, and its run count, plus two header actions: **Edit** (reopens the create form pre-filled; the template is read-only) and **Run now**.
The body shows:
* **Automation panel**. If the plan is scheduled: an Active/Paused badge, the cadence description, next-run and last-run times, and controls to pause/resume, edit the cadence, or delete the schedule. Unscheduled plans show a **Runs on demand** card with a **Schedule this plan** button instead.
* **Run history**: every execution of the plan, newest first. Runs are labelled `SR-{n}` and show one of five statuses: Running, Queued, Completed, Failed, or Cancelled, plus a trigger chip (Manual, Schedule, Re-run, or System). Click a run to open its report. See [Running Simulations](/documentation/simulation-testing/running-simulations).
***
## Creating plans via API
You can create and trigger plans programmatically:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -X POST "https://api.roark.ai/v1/simulation/plan" \
-H "Authorization: Bearer $ROARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production regression suite",
"direction": "INBOUND",
"maxSimulationDurationSeconds": 900,
"scenarios": [
{
"id": "flow-uuid",
"variables": {
"customerName": "John Doe",
"appointmentDate": "2026-02-15"
}
}
],
"personas": [{ "id": "persona-uuid" }],
"agentEndpoints": [{ "id": "endpoint-uuid" }],
"metrics": [{ "slug": "task-completion" }],
"iterationCount": 1,
"maxConcurrentJobs": 5,
"silenceTimeoutSeconds": 30,
"endCallPhrases": ["goodbye"],
"endCallReasons": ["The agent confirmed the order"],
"executionMode": "PARALLEL",
"autoRun": true
}'
```
Required fields: `name`, `direction`, `maxSimulationDurationSeconds`, `scenarios`, `personas`, `agentEndpoints`, and `metrics`. The same flow ID can appear in `scenarios` multiple times with different variables, and `autoRun: true` triggers a run immediately after creation. Trigger an existing plan with `POST /v1/simulation/plan/{planId}/job`.
The REST API keeps the older names: customer flows are attached via the `scenarios` array, and `direction` is set explicitly rather than derived from endpoints as it is in the UI.
***
## Next steps
Author the conversations your plans exercise
Start plans from a goal with preset metrics and checks
Put plans on a cadence and catch regressions automatically
Watch runs live and read the report when they settle
# Running Simulations
Source: https://docs.roark.ai/documentation/simulation-testing/running-simulations
Trigger runs, watch them live, and read the settled report
A **run** is one execution of a simulation plan. Every run gets a sequential label (`SR-42`), a trigger chip showing how it started, and a run detail page that acts as mission control while calls are in flight, then settles into a full report once the last call lands.
Runs execute a configured plan: agent targets, customer flows, metrics, and run settings. If you haven't built one yet, start with [Run Plans](/documentation/simulation-testing/run-plans).
## Ways to trigger a run
Every run row carries a trigger chip telling you how it started:
| Trigger | How it happens |
| :----------- | :------------------------------------------------------------------------------------------------ |
| **Manual** | You clicked **Run simulation** on the create-run page, or **Run now** on a plan |
| **Schedule** | A recurring or one-time schedule fired ([Schedules](/documentation/simulation-testing/schedules)) |
| **Re-run** | Started from a previous run's **Re-run** menu |
| **System** | Roark started the run automatically |
### Run now
From **Simulate → Simulations**, click **New Run**, configure the run, and hit **Run simulation**. The split button's menu also offers **Schedule recurring…** and **Run once, later…** if you'd rather not run immediately. Tick **Save as plan** if you want to keep the configuration. Otherwise the run is grouped under the **One-off runs** card on the runs hub.
To run an existing plan, open it and click **Run now**.
If your plan has without default values, a modal collects them before the run starts. See [Variables](/documentation/simulation-testing/variables).
### Via API
Trigger a plan programmatically and pass runtime variables:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -X POST "https://api.roark.ai/v1/simulation/plan/{planId}/job" \
-H "Authorization: Bearer $ROARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"variables": {
"orderNumber": "12345",
"environment": "staging"
}
}'
```
The response includes the `simulationRunPlanJobId` you can poll via `GET /v1/simulation/plan/job/{jobId}`.
The REST API keeps the older naming: customer flows are called `scenarios`, so flow-scoped runtime variables are passed as an array of `{ scenarioId, variables }` objects.
## Run statuses
Runs show one of five statuses:
| Status | Meaning |
| :------------ | :-------------------------------------------------------- |
| **Queued** | Accepted and waiting to start |
| **Running** | Building test calls, executing them, or compiling results |
| **Completed** | Every call finished and the report is ready |
| **Failed** | The run hit an unrecoverable error or timed out |
| **Cancelled** | You cancelled the run before it finished |
The API exposes finer-grained backend statuses (`CREATING_SNAPSHOTS`, `CREATING_SIMULATIONS`, `RUNNING_SIMULATIONS`, `ENDING_SIMULATIONS`, `CANCELLING`). All of these render as **Running** in the UI, and `TIMED_OUT` renders as **Failed**.
## Watching a live run
Open a run while it's executing and you get mission control. The header shows the plan name (or "One-off run #N"), the SR-number, Inbound/Outbound and template badges, the agents under test, and a pulsing **Running · mm:ss** clock. Banners walk you through the phases: "Starting the run", then "All calls landed, compiling the report".
* **Progress strip**: how many calls are done, in flight, and still queued.
* **Live now board**: every active conversation. Voice calls (Phone, WebRTC, LiveKit) stream audio so you can listen in as they happen; chat sessions (WebSocket, ElevenLabs, Google CES, Kore) show a live transcript instead. Pending conversations show their state: Dialing, Connecting, or Waiting for call.
* **What we're measuring**: the run's checks, so you know what pass/fail will be judged on.
* **Calls table**: click any call to open a split detail pane with the transcript, agent expectations, and audio for voice calls.
### Cancelling a run
Click the red **Cancel run** button in the header. Cancellation is graceful: **queued calls stop, but in-flight calls run to completion** and still appear in the results. The run settles with status **Cancelled**.
## The settled report
Once every call lands, the page becomes a report with four sections:
| Section | What's in it |
| :------------- | :-------------------------------------------------------- |
| **Overview** | Verdict hero and headline stats |
| **Metrics** | Per-metric results across the run |
| **Comparison** | This run side by side with the plan's other runs |
| **Calls** | Every conversation, with transcripts and per-call results |
The verdict hero leads with a check pass-rate ring and a plain-language narrative ("Clean sweep: every check passed on every conversation."), backed by stat cards: **Clean calls**, **Calls run**, and **Trend** or **Weakest check**.
Use the **Export** button to download the run's results as a CSV. A run-name dropdown in the header switches between the plan's executions (the newest is tagged "Latest"), or open the **Comparison** tab to view runs side by side.
## Re-running
The settled report's **Re-run** menu offers two distinct modes:
| Mode | What it does | Use it when |
| :------------------------ | :------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- |
| **Fresh run** | Builds new test calls from the plan **as configured today**, collecting any unresolved variables first | You've edited the plan or flows and want to test the current configuration |
| **Re-run with same data** | Replays this run's **exact snapshot**: same flows, personas, and settings, even if the plan has changed since | You want an apples-to-apples comparison, e.g. verifying a bug fix under identical conditions |
Re-runs land in the same plan's run history, so the Comparison tab picks them up automatically.
## Iterations and concurrency
Set in the plan's **Advanced → Run settings**:
* **Iterations**: runs per test case, up to 100. Useful for catching intermittent failures. Outbound runs are limited to 1 iteration. See [Inbound vs Outbound](/documentation/simulation-testing/inbound-vs-outbound).
* **Concurrency**: parallel calls. The default is 5, capped by your account quota. Start there and raise it once you've confirmed your agent handles the load.
## Next steps
Build reusable test suites
Put plans on a cadence
Parameterize flows with runtime values
Score metrics from your agent's side of the call
# Schedules
Source: https://docs.roark.ai/documentation/simulation-testing/schedules
Put a plan on a schedule so it runs itself: hourly, daily, weekly, monthly, or once at a future time
## Overview
A schedule is a property of a plan: each plan can have one schedule that runs it automatically on a cadence, or once at a future time. Instead of triggering runs by hand, put your regression suite on a daily schedule and let Roark catch regressions before you do.
There is no separate schedules page. You create and manage a schedule in two places:
* **While creating a run**: the split button next to **Run simulation** offers **Schedule recurring…** and **Run once, later…**
* **On the plan detail page**: the **Automation** panel (or the **Schedule this plan** call-to-action if the plan runs on demand)
Scheduled executions show up as ordinary runs in the runs hub, tagged with a **Schedule** trigger chip.
***
## Scheduling from the create-run page
Set up agents, template, flows, and settings as usual on **Simulations → New Run**.
Check **Save as plan** in the footer and name the plan. A schedule belongs to a plan, so the configuration needs to be saved.
Open the chevron menu on the primary button and choose **Schedule recurring…** (run on a cadence) or **Run once, later…** (run a single time in the future). The button face changes from **Run simulation** to **Schedule**.
An inline **Schedule** section appears with frequency, time, timezone, day selection, and end condition. A live sentence summarizes your choices, for example, "Every weekday at 9:00 AM, ET · ends never".
Press **Schedule** (or ⌘+↵). The plan is saved and its first run fires at the next scheduled time.
***
## Scheduling from a plan
On a plan's detail page:
* If the plan has no schedule, you'll see a **Runs on demand** card: "This plan only runs when you start it. Put it on a schedule and it'll run itself, catching regressions before you do." Click **Schedule this plan**, set the cadence, and click **Create schedule**.
* If the plan already has a schedule, the **Automation** panel shows it. Each plan has exactly one schedule.
***
## Cadence options
### Frequencies
| Frequency | Behavior | Example |
| :---------- | :---------------------------------------------------------- | :--------------------- |
| **Hourly** | Runs every hour at a set minute | :30 past each hour |
| **Daily** | Runs once per day at a set time | Every day at 9:00 AM |
| **Weekly** | Runs on the days you pick (**On these days**) at a set time | Mon and Fri at 2:00 PM |
| **Monthly** | Runs on a day of the month at a set time | The 15th at 10:00 AM |
| **Once** | Runs a single time at a future date and time | Dec 1 at 3:00 PM |
### Time and timezone
Pick the time of day and a timezone from the curated list. Schedules follow the selected timezone, so choose the one your team works in. The live sentence and **Next run** label confirm exactly when the next execution lands.
### End conditions
| Option | Description | Typical use |
| :--------------- | :------------------------------------- | :--------------------------- |
| **Never** | Runs indefinitely | Continuous monitoring |
| **On a date** | Stops after a specific date | Campaign or seasonal testing |
| **After N runs** | Stops after a set number of executions | Trialing a new schedule |
***
## Managing a schedule
The plan's **Automation** panel is where you manage an existing schedule:
* **Status badge**: **Active** or **Paused**
* **Pause / Resume**: pause without losing the configuration, resume when ready
* **Edit**: change the cadence, time, timezone, or end condition
* **Delete**: remove the schedule entirely (with a confirmation step); the plan and its run history stay intact
* **Next run / Last run**: when the schedule fires next and when it last ran
Editing an active schedule recalculates the next run time from your new settings.
***
## Scheduled runs in the runs hub
There is no separate job history per schedule. Each scheduled execution is a normal run:
* It appears in the plan's **Run history** and in the runs hub, labelled **SR-** like any other run
* It carries a **Schedule** trigger chip, distinguishing it from **Manual**, **Re-run**, and **System** runs
* It moves through the usual run statuses: **Running**, **Queued**, **Completed**, **Failed**, **Cancelled**
If any variable in the plan lacks a default value, scheduled runs can't prompt for it at execution time. Set defaults for every variable before scheduling. See [Variables](/documentation/simulation-testing/variables).
***
## Best practices
Begin with daily or weekly schedules to validate the plan before moving to hourly.
Set **After N runs** on a new schedule to limit usage while you confirm the configuration.
Check the first scheduled runs in the runs hub to confirm agents connect and checks behave as expected.
Avoid scheduling multiple high-concurrency plans at the same time.
Pausing keeps the cadence configured so you can resume with one click. Deleting means re-creating it later.
***
## Troubleshooting
**Schedule not firing**
* Check the **Automation** panel: the badge should read **Active**, not **Paused**
* Verify the timezone matches your expectation; the **Next run** label shows the exact upcoming time
* Confirm the end condition hasn't been reached (past its end date, or the run count is exhausted)
**Scheduled runs failing**
* Open the failed run from the plan's **Run history** and review the report
* Verify the plan's agents and endpoints are still reachable
* Make sure every variable in the plan has a default value
***
## Next steps
Build the plans your schedules execute
Follow runs live and read the report
Design the conversations your plans test
Set defaults so scheduled runs never stall
# Templates
Source: https://docs.roark.ai/documentation/simulation-testing/templates
Start a simulation run from a goal: each template ships preset metrics, Pass/Fail checks, and its own configuration panel
## Overview
Templates are step **01 · Template** of [creating a run](/documentation/simulation-testing/run-plans). Instead of assembling metrics and checks from scratch, you pick a goal (measure conversation quality, probe for jailbreaks, hammer one flow at volume) and the template seeds a sensible starting configuration:
* **Preset system metrics**: a curated set from the [metric library](/documentation/metrics/system-metrics), auto-selected in the Advanced section.
* **Pass/Fail checks**: [thresholds](/documentation/metrics/thresholds) attached alongside the metrics, so the run reports a clean pass rate out of the box.
* **A configuration panel**: step 03 of the create page changes per template (languages to test, volume to run at, adversarial cases to include).
* **Flow sourcing**: some templates pre-attach [customer flows](/documentation/simulation-testing/customer-flows) for you, by system label or by happy path.
Everything a template seeds is a starting point, not a lock. You can add or remove metrics, adjust checks, and change flows in the **Advanced** section before running.
***
## The catalogue
Pick a template from the card grid. The grid shows the common set; **Show more** expands it in place to reveal the rest.
| Template | Category | What it tests |
| :----------------------- | :---------------- | :-------------------------------------------------------------------------------------------- |
| **Blank** | Your templates | Start from scratch: no presets; choose what to measure, which flows to run, and who's calling |
| **Flow adherence** | Quality | How closely the agent stays on the flows you authored, under pressure |
| **Conversation quality** | Quality | How natural, empathetic, and helpful the agent is across realistic conversations |
| **Multilingual** | Quality | Language detection, comprehension, and response-language handling across languages |
| **Tool call accuracy** | Quality | Whether the agent calls the right tools, with the right arguments, at the right moments |
| **Red teaming** | Safety | Adversarial probing: refusals, jailbreak resistance, and policy adherence |
| **Voicemail testing** | Voice & telephony | Whether the agent detects voicemail, leaves a usable message, and hangs up |
| **Load testing** | Performance | Latency, drops, and concurrency limits when many simulations run in parallel |
| **Health check** | Performance | A minimal probe call on a schedule: the agent answers, speaks, and stays responsive |
### What each template seeds
No presets. The Advanced section shows only run settings. You pick metrics, flows, and checks yourself. Blank is the default selection.
Seeds adherence-focused metrics (scenario adherence, instruction following, and call outcome) with Pass/Fail checks on adherence and instruction following. Its configuration panel is an **Expected flows** picker: attach the flows the agent should follow, and the run measures how closely it stays on them.
Seeds the broadest metric set: sentiment, frustration, user effort, call outcome, instruction following, comprehension failures, redundant questions, missed responses, interruption appropriateness, talk-to-listen ratio, overtalk, and response time, with Pass/Fail checks on user effort, instruction following, and redundant questions.
Seeds comprehension, instruction-following, scenario-adherence, call-outcome, and sentiment metrics with adherence and instruction-following checks, then fans each attached flow out across the languages you pick (see below).
Seeds the three tool-call metrics (correct invocation, correct parameters, correct order) each with a Pass/Fail check. You pick the tools to evaluate once, and the template applies that list to all three metrics.
Seeds six compliance metrics (prompt-injection resistance, PII handling, prohibited language, scope adherence, hallucination boundary, and identity consistency) each with a Pass/Fail check. It sources its test cases from adversarial flows (see below).
Seeds the three voicemail metrics: detection, whether a message was left, and handling quality. Its configuration panel is a **Greetings to test** checklist over Roark's recordings, and it lowers the run's max duration to two minutes since voicemail calls are short by construction. See [Voicemail Testing](/documentation/simulation-testing/voicemail).
Seeds a minimal at-scale health set: agent responsiveness, response time, and time to first word, with a Pass/Fail check on responsiveness, so a load run reports one thing clearly: did the agent hold up at volume.
Seeds the liveness trio (the agent spoke, time to first word, and response time) with a Pass/Fail check on the first. The probe call itself is fixed (Roark's ping flow) so every check is comparable; you pick the agents and the cadence. Health checks have their own home under **Health checks**, so the template sits in the expanded set rather than the default grid.
***
## Template-specific configuration
Step 03 of the create page is the selected template's own panel. Three templates have configuration that goes beyond attaching flows.
### Load testing: the Volume panel
Load testing owns the run's scale, so the generic Iterations field in Advanced is hidden. The **Volume** panel is the single source of truth:
| Field | What it controls |
| :------------------- | :---------------------------------------------------------------------------------------- |
| **Concurrent calls** | How many simulated calls run at the same time, capped by your account's concurrency quota |
| **Total iterations** | The total number of simulation runs across the test |
The panel projects an **estimated runtime** live as you adjust the numbers, so you know how long the run will take before you start it.
Below the volume controls, **Flow to test** takes a single flow: no variant selection. A load test hammers one known-good path at volume, so the run pins the chosen flow's happy path (its default variant) on every call.
### Multilingual: language fan-out
Pick the languages to test from a chip grid: Spanish, French, German, Portuguese, Italian, Dutch, Chinese, Japanese, Hindi, Arabic, Turkish, Greek, Indonesian, Thai, Tagalog, Malay, and Hebrew. **Each attached flow runs once per language**: the template attaches the flow's happy path with a persona override that swaps in that language's system speaker persona, so a run with 3 flows and 4 languages produces 12 test conversations.
A **Code-switching** toggle swaps the persona set to English-mix speakers, callers who mix their language with English mid-conversation (e.g. Spanglish), to test how the agent keeps up when the language shifts.
### Red teaming: adversarial sourcing
Red teaming gives you two ways to source its test cases:
A unified, filterable catalogue that merges **Roark's curated adversarial library** with **your own flows carrying the Adversarial system label**. Each card shows its source badge (Roark or Yours) and failure-mode tags. A flow that's only partly adversarial (some variants labelled, some not) runs just its adversarial variants, and the card names them.
Draft fresh adversarial edge cases tailored to one of your own Improv flows. Pick the source flow, choose how many cases to generate (3–10), and set a difficulty: **easy**, **medium**, or **hard**. Ask Roark drafts the variants, labels each one Adversarial, and appends them to the flow; the panel then switches to the catalogue with your flow selected so the new cases flow straight into the run.
Generation targets Improv flows only. Scripted flows derive their variants from the conversation graph, so adversarial variants can't be appended to them.
### Voicemail testing: the Greetings panel
Voicemail's panel is a checklist of Roark's voicemail recordings, all selected by default, each with a play button to preview it. One call runs per selected greeting, per agent. There is no flow picker: the greetings *are* the run, and attaching an unrelated flow would make it a different test. Full detail in [Voicemail Testing](/documentation/simulation-testing/voicemail).
### Everything else
Flow adherence, Conversation quality, and Tool call accuracy configure through the shared inline **Flows** panel: attach the customer flows the run exercises, with the usual variant selection. Tool call accuracy adds one control above it: **Tools to evaluate**, defaulting to all current tools, applied to all three tool-call metrics so you don't repeat yourself.
***
## Template provenance
When you check **Save as plan**, the plan records which template it came from. The template shows up as a badge on the plan's detail page and on every run's header, and when you edit a plan the template is read-only: it defined the plan's shape, so you adjust the configuration rather than swap the goal.
One plan, one template: to test the same agents against a different goal, create a new run from the other template and save it as its own plan.
***
## Next steps
Save a template-seeded configuration as a reusable test suite
Author the conversations templates attach and grade against
Watch a run live and read its report
How the Pass/Fail checks templates seed actually work
# Variables
Source: https://docs.roark.ai/documentation/simulation-testing/variables
Feed dynamic values into customer flows and run plans with {{variable}} placeholders
Variables let you reuse the same customer flow with different data on every run. Instead of hardcoding details into a flow's brief or steps, you write `{{variableName}}` placeholders that get replaced with real values at runtime.
Variables show up in three places:
1. **On a flow variant**: key/value entries the simulator hands to the customer-side model, so the simulated customer can answer the agent's questions consistently
2. **On a run plan**: named variables with a type and an optional default, referenced as `{{name}}`, with per-attachment overrides when you attach flows
3. **At runtime**: a modal collects any values that still need filling in before the run starts
***
## 1. Variables on a flow variant
Each variant of a customer flow carries its own set of variables: key/value entries like `name`, `account_id`, or `last_visit` that the simulator gives to the customer-side model. When the agent asks "Can I get your account number?", the simulated customer answers with the value you set, consistently, every run.
### Editing variant variables
Open the variant and click the **Variables** chip (next to the Persona and Environment chips). From there you can:
* **Add variable**: add a key/value row inline
* **Apply a test profile**: replace the variant's entries with a saved test profile's properties in one click
### `{{variable}}` autocomplete
Text fields in the flow editor (the customer setup brief in Improv mode, step text in Scripted mode) support `{{variable}}` tokens with autocomplete sourced from your project's property definitions:
* `{{name}}`: flow-scoped properties, resolved from the variant's variables
* `{{persona.name}}`: persona-scoped properties, resolved from the variant's persona
Type `{{` to open the autocomplete and pick a property, or keep typing to reference a new one.
***
## 2. Variables on a run plan
Run plans define their own variables in the **Advanced** section of the create-run page. Each run-plan variable has:
| Field | Description |
| :---------- | :--------------------------------------------------------------- |
| **Name** | Referenced as `{{name}}` in attached flows |
| **Type** | The value type (string, number, boolean, date) |
| **Default** | Optional. If omitted, the value must be provided before each run |
Variables with defaults let a plan run unattended (on a schedule or from the API) without prompting anyone. Variables without defaults turn the plan into a parameterized suite: every run asks for fresh values.
### Per-attachment overrides
When you attach customer flows to a plan via the **Attach flows** picker, each attachment can override `{{variable}}` values for that flow. Overrides win over the values stored on the variant, so you can attach the same flow with different data (for example, one attachment with `appointmentType: urgent` and another with `appointmentType: routine checkup`) and each becomes its own set of test calls.
***
## 3. Providing values at runtime
If any variable lacks a value when you start a run (**Run now**, ⌘↵, or a "Fresh run" re-run) a modal collects the missing values first. It gathers, in one place:
* **Run-plan variables** without defaults
* **Flow variant values**: per-variant values for attached scripted flows
* **Persona property values**: values for `{{persona.*}}` references, which persist onto the persona for future runs
Once every required value is filled in, the run starts.
**"Re-run with same data"** replays a run's exact captured snapshot, so it never prompts for variables. **"Fresh run"** builds new test calls from the plan as configured today and collects unresolved variables first.
***
## Reserved variables
Some variable names are reserved for system use and resolved automatically:
| Variable | Resolves to |
| :---------------------- | :------------------------------------------------------------ |
| `{{persona.*}}` | Properties of the variant's persona (e.g. `{{persona.name}}`) |
| `{{phoneNumberToDial}}` | The phone number to dial for outbound simulations |
| `{{simulationJobId}}` | The current simulation job's ID |
Don't define your own variables with these names.
***
## Setting variables via the API
The REST API keeps the older naming: flows are passed in a `scenarios` field, and runtime overrides are keyed by `scenarioId`.
### Pre-set variables when creating a plan
Add a `variables` object to each entry in `scenarios` when [creating a run plan](/api-reference/simulation-run-plan/create-a-run-plan). The same flow ID can appear multiple times with different values. Each entry becomes a separate test case:
```json POST /v1/simulation/plan theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"name": "Patient Scheduling - Multi Profile",
"direction": "INBOUND",
"iterationCount": 2,
"maxSimulationDurationSeconds": 300,
"scenarios": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"variables": {
"patientName": "John Doe",
"appointmentType": "urgent",
"insuranceProvider": "Aetna"
}
},
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"variables": {
"patientName": "Jane Smith",
"appointmentType": "routine checkup",
"insuranceProvider": "Blue Cross"
}
}
],
"personas": [{ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8" }],
"agentEndpoints": [{ "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" }],
"metrics": [{ "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }],
"autoRun": false
}
```
### Override variables when triggering a run
Runtime values are passed in the request body when [triggering a job](/api-reference/simulation-run-plan-job/run-a-simulation-plan). They override any values stored on the plan.
**Global format** (the same values apply to every flow in the plan):
```json POST /v1/simulation/plan/:planId/job theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"variables": {
"orderNumber": "12345",
"environment": "staging"
}
}
```
**Per-flow format** (different values per flow, keyed by `scenarioId`):
```json POST /v1/simulation/plan/:planId/job theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"variables": [
{
"scenarioId": "550e8400-e29b-41d4-a716-446655440000",
"variables": {
"orderNumber": "12345",
"claimType": "damaged item"
}
},
{
"scenarioId": "7a3d2e1f-c4b5-6a89-0d1e-2f3a4b5c6d7e",
"variables": {
"orderNumber": "67890",
"claimType": "missing package"
}
}
]
}
```
If everything has a stored value or default, the body is optional. `POST /v1/simulation/plan/:planId/job` with no body starts the run as configured.
***
## Related
Author the flows and variants that variables plug into
Compose flows, personas, metrics, and variables into a reusable suite
Trigger runs and follow them live
The customer identities behind values
# Voicemail Testing
Source: https://docs.roark.ai/documentation/simulation-testing/voicemail
Check that your agent notices it has reached voicemail, leaves a usable message, and hangs up
## Overview
An outbound agent that dials a real customer will reach voicemail regularly. What it does next is worth testing on its own: a good agent recognises it isn't talking to a person, leaves a complete message, and hangs up. A bad one waits for a reply that never comes, talks over the greeting, or leaves a message no one can act on.
**Voicemail testing** is a run template. Pick it, choose which greetings to call into, and run. There is nothing else to configure, because a voicemail has nothing to configure. No script, no persona, no conversation: a recording plays, then the line stays silent while your agent decides what to do.
***
## Running a voicemail test
On [create a run](/documentation/simulation-testing/run-plans), choose **Voicemail testing** in step 01.
Step 02 as usual. Voicemail is **voice-only**: a greeting is audio, so a chat endpoint has nothing to play. Attaching one is rejected before the run starts.
Step 03 is a **Greetings to test** checklist. Every greeting is selected by default; untick the ones you don't need. Each row has a play button so you can hear the recording before you commit to a run.
One call runs per selected greeting, per agent. Ten greetings against two agents is twenty calls.
The call count multiplies. Leaving every greeting selected across several agents adds up quickly, so untick down to the greetings you actually care about if you're iterating.
***
## What happens on the call
Each call follows the same shape:
1. Roark dials your agent (or answers it, for an inbound endpoint).
2. The selected greeting plays as the first thing your agent hears: a real recording, not synthesised speech.
3. The line then goes **completely silent**. The simulated caller says nothing for the rest of the call, and won't interrupt.
4. The call ends when your agent hangs up, or after 15 seconds of silence once it stops speaking.
That silence is the test. Everything Roark grades is a property of what your agent does into a line that never answers back.
Because voicemail calls are short by construction, the template lowers the run's **max duration** default to two minutes. A hung call fails fast instead of billing the usual fifteen.
***
## The greetings
Roark ships a fixed set of real voicemail recordings, covering the shapes an outbound agent meets in the wild:
| Family | What it is |
| :--------------------- | :------------------------------------------------------------------------------------------------------ |
| **Carrier robots** | The automated greetings networks play when a subscriber has set no personal message: Verizon, O2, Apple |
| **Mailbox full** | A carrier notice that there's no room to leave a message at all |
| **Personal greetings** | Real people recording their own outgoing message, across a range of voices, accents, and lengths |
**Mailbox full is the interesting one.** There's nowhere to leave a message, so an agent that launches into its script anyway is doing the wrong thing. The correct behaviour is to recognise it and hang up.
These are Roark-managed and read-only: you can select and preview them, but you can't add your own or edit the set.
***
## What gets measured
The template attaches three [system metrics](/documentation/metrics/system-metrics#voicemail-detection):
| Metric | What it answers |
| :----------------------------- | :----------------------------------------------------------------------------------- |
| `voicemail_detected` | Did the agent recognise it had reached a voicemail system rather than a live person? |
| `voicemail_agent_left_message` | Did it leave a message at all? |
| `voicemail_handling_score` | How good was the handling: beep timing, message clarity, completeness (1–5) |
As with any template, these are a starting point. Add or remove metrics in the **Advanced** section before running.
Read the run report per greeting, not just in aggregate. An agent can handle a carrier robot perfectly and still talk over a personal greeting, and the run report separates the calls by greeting so that shows up.
***
## Next steps
The full template catalogue and what each one seeds
How the voicemail metrics are scored
Save a voicemail configuration as a reusable suite
Run voicemail checks on a cadence
# WebRTC Simulations
Source: https://docs.roark.ai/documentation/simulation-testing/webrtc
Run simulations over WebRTC instead of a phone line
## Overview
By default, Roark simulations are carried out over a phone line: the simulated caller dials your agent through a telephony provider and the entire conversation runs as a real PSTN call. This matches how most production voice agents are reached today.
If your agent supports **WebRTC**, the Roark simulation agent can connect to it directly over WebRTC instead of placing a phone call. This is useful when:
* Your agent is primarily accessed through a web or mobile client and you want simulations to exercise the same transport as production.
* You don't want to pay for telephony minutes just to run tests.
* Your agent doesn't expose a phone number at all (for example, an in-app voice assistant).
* You want lower-latency test runs without SIP in the middle.
Roark currently supports two WebRTC transports: **LiveKit** and **SmallWebRTC**.
The direction configured on the endpoint feeds the derived direction of any run that uses it. See [Inbound vs Outbound](/documentation/simulation-testing/inbound-vs-outbound).
***
## LiveKit
If your agent runs on LiveKit, Roark can join the same LiveKit room as your agent and carry out the simulation end-to-end over LiveKit's WebRTC infrastructure.
You can either let Roark create the room in your LiveKit project and dispatch your agent into it, or provide a callback URL where your own backend creates the room and returns the room name for Roark to join. Room metadata, named-agent dispatch, and automatic-dispatch behavior are all supported.
WebRTC endpoints are managed from the LiveKit integration page itself: go to **Agents**, expand the connected sources at the top of the page, and choose your **LiveKit** source: the integration form opens on the right, where you can use the **WebRTC** section to add or edit endpoints. Each endpoint is attached to one of the agents managed by the integration and can be reused across any run plan.
See the LiveKit integration docs for the full list of endpoint settings and room management modes.
***
## SmallWebRTC
If your agent uses peer-to-peer / serverless WebRTC via Pipecat's [`SmallWebRTCTransport`](https://docs.pipecat.ai/server/services/transport/small-webrtc), Roark can connect to it directly with no intermediate media server. This is a good fit for Pipecat agents that don't use LiveKit or Daily and instead rely on the P2P WebRTC transport that ships with Pipecat.
To add a SmallWebRTC endpoint, navigate to **Agents → \{your agent} → Create Endpoint** and choose **SmallWebRTC**. You'll provide the URL that Roark should POST an SDP offer to, and Roark's simulation caller will negotiate the peer connection directly with your agent at simulation time.
SmallWebRTC is a good choice when you're running Pipecat locally or on minimal infrastructure and don't want to introduce a SFU. For Pipecat deployments that already use LiveKit or another SFU, prefer the matching transport endpoint.
***
## Next Steps
Connect LiveKit and configure WebRTC endpoints
Use a WebRTC endpoint in a run plan
Execute simulations against your WebRTC agent
Learn about the SmallWebRTC transport
# Tool Invocations
Source: https://docs.roark.ai/documentation/tool-invocations
Submit and track tool calls as part of post-call analysis
### Overview
Tool invocations allow you to capture and analyze the tools used during calls. This documentation covers how to submit tool calls, parameter options, result formats, and how Roark tracks versioning for tools.
### Submitting Tool Calls
Tool calls can be submitted as part of your `post-call-analysis` by including a `toolInvocations` array in your request payload when you [create the call](/documentation/observability/overview#how-calls-get-into-roark). If your tool data is not ready at call-creation time, you can also [attach it afterward](#attaching-tool-calls-after-a-call). Each tool invocation requires:
* `name`: The name of the tool being invoked
* `description`: A brief description of what the tool does
* `startOffsetMs`: The start time offset in milliseconds with respect to the start of the call
* `endOffsetMs`: The end time offset in milliseconds with respect to the start of the call
* `parameters`: The parameters passed to the tool
* `result`: The result of the tool invocation
```typescript TypeScript {22-38} theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
recordingUrl: 'https://example.com/recording.wav',
callDirection: 'INBOUND',
interfaceType: 'WEB',
startedAt: '2025-03-29T15:30:00Z',
isTest: false,
properties: {},
participants: [
{
name: 'Sales Agent',
phoneNumber: '+15551234567',
role: 'AGENT',
spokeFirst: true,
},
{
name: 'John Doe',
phoneNumber: '+15557654321',
role: 'CUSTOMER',
spokeFirst: false,
},
],
// Tool invocations are passed as part of the post-call-analysis payload
toolInvocations: [
{
name: 'getDentalAppointments',
description: 'Get available dental appointments',
startOffsetMs: 2000,
endOffsetMs: 3000,
parameters: {
patientName: 'John Doe',
patientPhone: '+1234567890',
},
result: {
appointments: ['cleaning', 'whitening', 'rootCanal']
},
},
// More tool invocations...
],
}
```
```python Python {22-38} theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"recordingUrl": "https://example.com/recording.wav",
"callDirection": "INBOUND",
"interfaceType": "WEB",
"startedAt": "2025-03-29T15:30:00Z",
"isTest": False,
"properties": {},
"participants": [
{
"name": "Sales Agent",
"phoneNumber": "+15551234567",
"role": "AGENT",
"spokeFirst": True,
},
{
"name": "John Doe",
"phoneNumber": "+15557654321",
"role": "CUSTOMER",
"spokeFirst": False,
},
],
# Tool invocations are passed as part of the post-call-analysis payload
"toolInvocations": [
{
"name": "getDentalAppointments",
"description": "Get available dental appointments",
"startOffsetMs": 2000,
"endOffsetMs": 3000,
"parameters": {
"patientName": "John Doe",
"patientPhone": "+1234567890",
},
"result": {
"appointments": ["cleaning", "whitening", "rootCanal"]
},
},
# More tool invocations...
],
}
```
### Attaching Tool Calls After a Call
Sometimes your tool data is not ready when the call is created, for example when a Roark [simulation](/documentation/simulation-testing/enriched-simulations) has finished but your backend needs a moment to assemble the tool calls, or when you submit a call before its tools are available. In those cases you can attach tool invocations to an existing call:
```
POST /v1/call/{callId}/tool-invocations
```
The body takes the same `toolInvocations` array as call creation:
```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
curl -X POST https://api.roark.ai/v1/call/{callId}/tool-invocations \
-H "Authorization: Bearer $ROARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"toolInvocations": [
{
"name": "getDentalAppointments",
"description": "Get available dental appointments",
"startOffsetMs": 2000,
"endOffsetMs": 3000,
"parameters": { "patientName": "John Doe" },
"result": { "appointments": ["cleaning", "whitening"] }
}
]
}'
```
The response reports how many invocations were `added` and how many were `skipped`.
Attaching is **idempotent**. Re-sending an invocation that is already on the call (same tool name and timing) is skipped rather than duplicated, so a retried request converges instead of double-counting.
To score the tool metrics over the newly attached tools in the same request, pass a `metrics` array of metric definition IDs. This starts a [metric collection job](/documentation/metrics/metric-collection-jobs) (billed, and requires the `metric:create` permission). Omit it to attach tools only. If scoring can't be started (for example, insufficient credits), the tools are still attached and the response returns a `metricCollectionError` you can act on.
### Tracking Input Parameters
Parameters can be submitted in two different formats:
#### Simple Key-Value Pairs
The most basic format is a simple key-value pair. Roark will automatically infer the type of the parameter, and rely on the name of the parameter to understand the context.
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
parameters: {
patientName: 'John Doe',
appointmentType: 'cleaning',
}
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
parameters = {
"patientName": "John Doe",
"appointmentType": "cleaning",
}
```
#### Detailed Key-Value Pairs
To ensure that the usage of the tool is clear and the types are correctly inferred, you can manually provide the `description` and `type` for each parameter. Type can be one of: `string`, `number`, `boolean`.
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
parameters: {
patientName: {
value: 'John Doe',
description: 'Name of the patient', // optional
type: 'string', // optional
},
appointmentType: {
value: 'cleaning',
description: 'Type of dental appointment',
type: 'string',
}
}
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
parameters = {
"patientName": {
"value": "John Doe",
"description": "Name of the patient", # optional
"type": "string", # optional
},
"appointmentType": {
"value": "cleaning",
"description": "Type of dental appointment",
"type": "string",
}
}
```
### Tracking Results
The result returned by the tool can be a string or a JSON object.
#### String Results
For simple responses, you can pass in a string:
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
result: 'Success'
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
result = "Success"
```
#### JSON Object Results
For structured data, you can pass a JSON object:
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
result: {
appointments: ['cleaning', 'whitening', 'rootCanal'],
nextAvailable: '2025-04-15T10:00:00Z'
}
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
result = {
"appointments": ["cleaning", "whitening", "rootCanal"],
"nextAvailable": "2025-04-15T10:00:00Z"
}
```
### Tool Execution Duration
Roark calculates execution duration automatically using the difference between `startOffsetMs` and `endOffsetMs`:
* `startOffsetMs`: Time offset (in milliseconds) from the beginning of the call when the tool execution started
* `endOffsetMs`: Time offset (in milliseconds) from the beginning of the call when the tool execution ended
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
// This tool invocation lasted 1000ms (1 second)
{
name: 'getDentalAppointments',
startOffsetMs: 2000, // 2 seconds into the call
endOffsetMs: 3000, // 3 seconds into the call
// ...
}
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# This tool invocation lasted 1000ms (1 second)
{
"name": "getDentalAppointments",
"startOffsetMs": 2000, # 2 seconds into the call
"endOffsetMs": 3000, # 3 seconds into the call
# ...
}
```
### Tool Versioning
The system automatically tracks changes to your tool calls and creates new versions when:
1. A tool with the same name receives a different schema for input parameters
2. A tool with the same name is called with a different description
#### Example
If you previously called a tool:
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
name: 'bookAppointment',
description: 'Book a dental appointment',
parameters: { patientName: 'string', appointmentType: 'string' }
}
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"name": "bookAppointment",
"description": "Book a dental appointment",
"parameters": { "patientName": "string", "appointmentType": "string" }
}
```
And later change it to:
```typescript TypeScript theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
name: 'bookAppointment',
description: 'Book a dental appointment for the client',
parameters: {
patientName: 'string',
patientPhone: 'string',
appointmentType: { type: 'object' }
}
}
```
```python Python theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
{
"name": "bookAppointment",
"description": "Book a dental appointment for the client",
"parameters": {
"patientName": "string",
"patientPhone": "string",
"appointmentType": { "type": "object" }
}
}
```
The system will automatically create a new version to track these changes.