> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://onepin.ai/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://onepin.ai/docs/_mcp/server.

# Get Run Steps

GET https://api.onepin.ai/api/v1/workflows/{workflow_id}/runs/{run_id}/steps

List per-node execution steps for a workflow run.

Returns one entry per node execution attempt, ordered by execution sequence.
By default, the response is lightweight: `result` is null, `has_result`
reports whether a stored result exists, and `active_ports` is projected from
the result without loading the full JSON payload.

Set `include_result=true` to restore the full result payload. Audio results
are then hydrated with short-lived `playback_url` values (valid for 15
minutes). `node_type` and `node_id` filters combine with AND semantics.

`node_display_name` is resolved from the run's definition snapshot, so it
reflects the name the node had when the run executed. Repeated executions of
the same node share that name and are distinguished by `iteration`.

For a higher-level view with aggregated metrics (pass rates, audio duration
by language), use `GET /runs/{run_id}/overview`. For paginated, grouped
script+audio rows suitable for a data table, use `GET /runs/{run_id}/data`.

Reference: https://onepin.ai/docs/reference/api-reference/workflows/runs/steps

## Authentication

- `Authorization` header (bearer token, required) — Clerk JWT token
- `Authorization` header (bearer token, required) — Onepin live API key (`op_live_...`). Test and public keys are reserved in Phase 1.

## Servers

- `https://api.onepin.ai` (prod, default)
- `https://dev-api.onepin.ai` (dev)

## Request

### Path parameters

- `workflow_id` (string, required)
- `run_id` (string, required)

### Query parameters

- `include_result` (boolean, optional, default: false) — Include the full step result payload and hydrate audio playback URLs.
- `node_type` (enum, optional, nullable) — Filter steps by node type.
  - Allowed values: `source_script`, `operator_translator`, `operator_normalizer`, `operator_generator`, `operator_phoneme_injector`, `sink_preview`, `validator_error_rate`, `validator_naturalness`, `validator_noise`, `validator_pronunciation`, `operator_pronunciation_corrector`
- `node_id` (string, optional, nullable) — Filter steps by node ID.

### Headers

- `X-Workspace-Id` (string, optional, nullable)

## Response

### 200

Successful Response

- `data` (list of WorkflowRunStepOut, required)
- `meta` (Meta, required)

## Errors

### 422 Unprocessable Entity Error

Validation Error

- `detail` (list of ValidationError, optional)

## Types

### WorkflowRunStepOut

- `id` (string, required)
- `node_id` (string, required)
- `node_type` (string, required)
- `status` (string, required)
- `node_display_name` (string, optional, default: )
- `iteration` (integer, optional, default: 1)
- `started_at` (datetime, optional, nullable)
- `completed_at` (datetime, optional, nullable)
- `result` (map from string to any, optional, nullable)
- `has_result` (boolean, optional, default: false)
- `active_ports` (list of string, optional, nullable)
- `locales` (list of string, optional, nullable) — Locales of the lines this step processes, sorted and de-duplicated, stamped once when the step was created. Null for a node with no line input and for steps created before this field existed. On a fail-wins wave this may be a superset of the locales actually synthesized: it is taken from the raw gathered input, before delivery filtering.
- `pause_reason` (enum, optional, nullable) — Why this step was interrupted recoverably. Null for steps that were not affected by the current or historical pause episode.
  - Allowed values: `user`, `provider_outage`, `provider_rate_limited`, `provider_billing`, `node_timeout`
- `pause_error` (string, optional, nullable) — Persistent customer-facing explanation for this step's recoverable interruption. Separate from terminal error.
- `error` (string, optional, nullable)

### Meta

- `request_id` (string, required)
- `timestamp` (datetime, required)

### ValidationError

- `loc` (list of ValidationErrorLocItems, required)
- `msg` (string, required)
- `type` (string, required)
- `input` (any, optional)
- `ctx` (ValidationErrorCtx, optional)

### ValidationErrorLocItems

### ValidationErrorCtx

## Examples

**Request**

```json
{}
```

**Response**

```json
{
  "data": [
    {
      "id": "a3f1c2d4-8e57-4b9a-bc12-3d7e5f901234",
      "node_id": "node-translator-01",
      "node_type": "operator_translator",
      "status": "completed",
      "node_display_name": "Translate to Spanish",
      "iteration": 1,
      "started_at": "2024-06-10T14:22:05Z",
      "completed_at": "2024-06-10T14:22:18Z",
      "result": null,
      "has_result": true,
      "active_ports": [
        "output"
      ],
      "locales": [
        "es-ES",
        "es-MX"
      ],
      "pause_reason": null,
      "pause_error": null,
      "error": null
    },
    {
      "id": "b7e2d3f5-9f68-4c0b-cd23-4e8f6a012345",
      "node_id": "node-tts-preview-02",
      "node_type": "sink_preview",
      "status": "completed",
      "node_display_name": "Preview Audio Output",
      "iteration": 1,
      "started_at": "2024-06-10T14:22:19Z",
      "completed_at": "2024-06-10T14:22:47Z",
      "result": null,
      "has_result": true,
      "active_ports": [
        "audio_out"
      ],
      "locales": [
        "es-ES",
        "es-MX"
      ],
      "pause_reason": null,
      "pause_error": null,
      "error": null
    },
    {
      "id": "c9f4e5a6-0a79-4d1c-de34-5f9a7b123456",
      "node_id": "node-validator-03",
      "node_type": "validator_pronunciation",
      "status": "failed",
      "node_display_name": "Pronunciation Validator",
      "iteration": 2,
      "started_at": "2024-06-10T14:22:50Z",
      "completed_at": "2024-06-10T14:23:05Z",
      "result": null,
      "has_result": false,
      "active_ports": null,
      "locales": [
        "es-MX"
      ],
      "pause_reason": "provider_rate_limited",
      "pause_error": "The pronunciation validation provider is temporarily rate-limited. The step will be retried automatically.",
      "error": "Provider returned HTTP 429 after 3 retry attempts."
    }
  ],
  "meta": {
    "request_id": "req_7d4e2f1a3b8c9e05",
    "timestamp": "2024-06-10T14:23:10Z"
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.onepin.ai/api/v1/workflows/workflow_id/runs/run_id/steps"

payload = {}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.get(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.onepin.ai/api/v1/workflows/workflow_id/runs/run_id/steps';
const options = {
  method: 'GET',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.onepin.ai/api/v1/workflows/workflow_id/runs/run_id/steps"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("GET", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.onepin.ai/api/v1/workflows/workflow_id/runs/run_id/steps")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.onepin.ai/api/v1/workflows/workflow_id/runs/run_id/steps")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.onepin.ai/api/v1/workflows/workflow_id/runs/run_id/steps', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.onepin.ai/api/v1/workflows/workflow_id/runs/run_id/steps");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.onepin.ai/api/v1/workflows/workflow_id/runs/run_id/steps")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```