> For the complete documentation index, see [llms.txt](https://docs.concurrence.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.concurrence.com/developer-guide/platform-api/conversations/runs.md).

# Runs

Browse conversation runs and best-effort projected framework history, inspect structural trajectories, and control channel-aware operator takeover by run ID.

The unified runs endpoint is the public browse surface for conversation runs across voice, SMS, WhatsApp, email, web, and iMessage, plus terminal framework runs that reached the durable projection. Framework-run projection is best-effort, so this endpoint is not a live or guaranteed-complete framework-run ledger. Use `kind=framework` to browse the projected framework history.

{% hint style="info" %}
Live voice entries combine retained conversation records with best-effort active-call visibility. Realtime-provider calls attempt to open a record at call start and finalize that same record at call end. Other voice paths can appear through the live listing before their retained record is available. Persistence or live-source failures can leave entries missing or stale; the listing is not a complete audit ledger.
{% endhint %}

## List Runs

Returns a paginated, newest-first list of conversation runs and projected terminal framework runs under one contract. Voice runs take `started_at` and `ended_at` from the call's start and end and sort by the call start. A text run starts at the conversation's creation. A finished text run ends at its close, or when the session that finished it ended. With no recorded end time, it ends at its last update.

An ended conversation run carries how it finished in `completion_reason`, `final_state`, and `duration_seconds`. A voice run reports the call's own values. A text conversation that reaches a terminal Context Graph state records `completion_reason` `completed`, the state it ended in as `final_state`, and its time from creation to that end as `duration_seconds`. A text conversation [closed](/developer-guide/platform-api/conversations.md#close-a-conversation) through the API or by `force_new` records `completion_reason` `closed`, its time from creation to the close, and `final_state`, the Context Graph state it was in, when known. These fields are `null` on a live run and on a run that recorded none, such as a text conversation closed because its use case was rebound to another service.

Use `run_id` for run-scoped operations. A voice run's `source_call_sid` is a separate, opaque call handle that can be a UUID-shaped reference or a carrier-style value. Preserve the returned string without parsing its format; see [Calls](/developer-guide/platform-api/conversations/calls.md).

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs" method="get" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Authentication

Workspace API key (bearer token) or operator identity token. The bearer's workspace must match the path workspace.

### Status Mapping

The `status` field on each run is a canonical value derived from the underlying source:

* **Framework runs**: Terminal status is projected on a best-effort basis. A framework run may be absent if its terminal projection was not recorded.
* **Conversation runs (voice)**: An active call with no completion reason maps to `running`. A timed-out call maps to `timed_out`. Calls that ended due to errors or no audio map to `failed`. All other completed calls map to `completed`.
* **Conversation runs (non-voice)**: An open conversation updated within its channel's activity threshold maps to `running`. One that has been idle past the threshold maps to `dormant`: 5 minutes for web, 30 minutes for SMS, and 60 minutes for email, WhatsApp, and iMessage. The non-voice channels are SMS, WhatsApp, email, web, and iMessage. A dormant run is not live, has no `ended_at`, and is not eligible for takeover, and its next turn makes it `running` again. A conversation an operator has taken over stays `paused` however long it is idle. Closed or completed conversations map to `completed`. Failed conversations map to `failed`.

The virtual `live` status filter expands to `running` + `paused`, returning active conversation runs. It does not include `dormant`, which has its own filter value. Projected framework history is terminal-only.

### What Ran a Run <a href="#what-ran-a-run" id="what-ran-a-run"></a>

Each conversation run reports what ran it, as its session recorded it at start, so a run keeps saying what handled it after the service's configuration changes:

* `session_provider` is the runtime that handled the run: `gpt_realtime`, or `amigo` for a platform-managed call and for every text run.
* `manifest` holds the agent and Context Graph versions the session loaded, the version set it resolved them through, the model that wrote the replies (`llm_model`), the model that chose Context Graph hops (`navigation_model`, null on `gpt_realtime`, which doesn't navigate), the speech-to-text and text-to-speech vendors and models (null on a text run), and `platform_config_fingerprint`, one value for each combination of agent version, graph version, and model.

Nothing in either field is read from the service's current configuration. Both are null when the run recorded none, such as on a framework run or on a text conversation before its first session starts. On a live platform-managed call, `manifest` is null until the call ends, and `session_provider` can be too. [Conversation detail](/developer-guide/platform-api/conversations.md#get-conversation-detail) returns the same two fields.

### Filter by Channel <a href="#filter-by-channel" id="filter-by-channel"></a>

The `channel` filter on the run list and Runs Summary accepts `voice`, `sms`, `whatsapp`, `email`, `web`, and `imessage`, the channels a conversation run can carry. Repeat it to match any of several. Any other value returns `422`.

### Search Runs

Pass `search` for a case-insensitive substring match on a conversation run's entity name, caller ID, or phone number. The match runs before pagination, so every page holds only matching runs. Framework runs carry none of these fields and are excluded while `search` is set. It combines with the other filters using AND.

### Filter by Service

Pass `service_id` to list only the runs of one or more services, up to 100 IDs. Repeated values are ORed, and the filter combines with `kind`, `channel`, and `status` using AND. It applies to conversation runs and live voice runs before pagination, so every page holds only matching runs. Historical framework runs have no stored service, so they are excluded whenever `service_id` is set. The Runs Summary operation does not take this filter. In the Platform SDK, `client.runs.list({ serviceId: [...] })` sends the service filter.

### Example Request

```
GET /v1/{workspace_id}/runs?limit=20&status=running&status=paused&kind=conversation&channel=voice
Authorization: Bearer {api_key}
```

### Example Response

```json
{
  "items": [
    {
      "run_id": "a1b2c3d4-...",
      "workspace_id": "w1x2y3z4-...",
      "kind": "conversation",
      "status": "running",
      "channel": "voice",
      "framework": null,
      "entity_id": "e1f2g3h4-...",
      "service_id": "s1t2u3v4-...",
      "started_at": "2026-07-15T14:30:00Z",
      "ended_at": null,
      "source_call_sid": "2c725f98-a9c6-4be2-8a48-6eb42d1f81cb",
      "source_conversation_id": "c1d2e3f4-...",
      "source_framework_run_id": null,
      "session_provider": "amigo",
      "manifest": null
    }
  ],
  "has_more": true,
  "continuation_token": 20
}
```

### Error Responses

| Status                     | Condition                                                                                                                                                                  |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `403 Forbidden`            | Bearer workspace does not match the path workspace.                                                                                                                        |
| `422 Unprocessable Entity` | Invalid query parameter, such as an invalid `continuation_token` or an unsupported `sort_by`. A `sort_by` error lists the allowed values, `+started_at` and `-started_at`. |
| `502 Bad Gateway`          | Run data is temporarily unavailable.                                                                                                                                       |

## Runs Summary

Summarizes the same unified inventory. Repeated kind and channel filters use OR semantics, and a channel filter restricts results to conversation runs. `live` counts `running` and `paused` runs, and `dormant` counts idle open conversations, which are not live.

The summary excludes a live entry when the same call already has a running conversation record. Reads can still observe different moments during call startup or completion, so counts can briefly differ from the list. Reconcile individual run identifiers when an exact operational count is required.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs/summary" method="get" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

## Get a Run

Returns one available framework or conversation run. Framework results are limited to terminal runs that reached the best-effort projection. The workspace-scoped `run_id` is channel-neutral; inspect `kind`, `channel`, and the `source_*` fields to decide whether to open conversation detail, call detail, or framework trajectory next. Returns `404` when the run does not exist in the workspace.

A conversation run resolves by `run_id` at any age, for this read and for the run-scoped operations below. A projected framework run is looked up among recent framework history, so an older framework run can return `404` even though it appears in an earlier list page.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs/{run\_id}" method="get" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

## Get a Framework Trajectory

Returns ordered structural steps for a framework run. Check `truncated` before treating the returned sequence as complete.

Returns `404` when the run does not exist and `409` when the run is a conversation run. Use conversation detail for per-turn conversation history.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs/{run\_id}/trajectory" method="get" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

## Takeover Eligibility

Every run carries a server-computed `takeover` object based on its channel and status. Clients should use it for those eligibility checks and also confirm that the selected voice runtime supports the requested control. A live realtime-provider call can be listed without supporting operator takeover or guidance.

The voice eligibility object exposes `listen` and `takeover`; execution requires a supported platform-managed voice session. Live non-voice runs (SMS, WhatsApp, email, web, and iMessage) expose `takeover` only; an operator drives those channels by staging authored turns rather than joining a live media stream. Framework and non-live runs are not eligible.

## Operator Takeover

The Developer Console does not provide operator pages or takeover controls. These API operations require an integration that provides its own controls, authorized operator identity, and a supported runtime. The Console's All runs Live filter is only a browse filter.

An operator can take over a live run and hand it back by channel-neutral `run_id`. Voice takeover uses a live media session and supports listen or drive mode. Non-voice takeover pauses the agent while the operator stages the next outbound turn.

All five operations below require `Operator:Update`, which the operator, admin, and owner roles carry. The `operator_id` in the body must match the authenticated operator; impersonation is rejected.

### Take Over a Run

Registers the operator on a live run. In `takeover` mode the agent is suspended so the human drives; in `listen` mode the operator monitors without driving. Non-voice channels support `takeover` mode only.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs/{run\_id}/takeover" method="post" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Hand a Run Back

Releases the operator from the run. For non-voice channels, the stored status moves from `paused` back to `active`; for voice, the operator leaves the live media session and the agent is resumed. Subsequent behavior uses whatever conversation state and takeover evidence the runtime successfully retained; the handback response does not guarantee a complete transcript of operator activity.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs/{run\_id}/handback" method="post" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Author a Non-Voice Turn

Stages the next operator-authored reply on a taken-over SMS, WhatsApp, email, web, or iMessage run. A run can hold one pending authored reply; staging another before it is consumed replaces the earlier text. The reply is consumed before downstream delivery completes, so a later delivery failure does not restore that pending value automatically. This is not an exactly-once delivery guarantee. Wait for a staged turn to be consumed before staging another.

Voice runs return `409` because the operator drives them through live audio.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs/{run\_id}/authored-turn" method="post" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Switch Mode

Toggles an operator who has already joined a voice run between `listen` and `takeover`. Non-voice runs return `409` because they support authored-turn takeover only.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs/{run\_id}/switch-mode" method="post" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Operator Audio

Operators join supported voice calls by phone through the [operator join workflow](/developer-guide/platform-api/conversations/operators.md), not through browser audio. Use run takeover and mode-switch controls for the corresponding operator state changes.

### Send Guidance

Sends text guidance to a live voice run. Guidance is delivered as a prioritized instructional event that can interrupt current agent speech, unlike a factual [external event](/developer-guide/platform-api/conversations/voice-agent.md), which queues behind current speech. The returned delivery status does not guarantee exact response timing or that the model follows the instruction verbatim. Non-voice channels return `409`; use authored-turn takeover when an operator must send a reply.

The audit record captures the run, channel, operator identity, and a content fingerprint, never the raw guidance text.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/runs/{run\_id}/guidance" method="post" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

## Agent Forge CLI

Agent Forge `v0.1.44+` exposes the unified read surface:

```bash
forge platform run list \
  --kind conversation \
  --channel voice \
  --status live \
  --limit 50 \
  --env <env>

forge platform run summary --kind conversation --env <env>
forge platform run get <run_id> --env <env>
```

Add `--json` for the raw response. The CLI accepts one value per filter flag and prints `--continuation-token` when another page is available; pass that token unchanged to the next `run list` call. The REST API itself supports repeated filter values.

## Rate Limits

Each list, summary, detail, and trajectory operation allows 60 requests per minute. Each takeover, handback, authored-turn, mode-switch, and guidance operation allows 10 requests per minute. Limits are enforced per authenticated credential and operation. Successful limit checks return `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`; a `429` response also returns `Retry-After`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.concurrence.com/developer-guide/platform-api/conversations/runs.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
