> 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.

# Preview Run

POST https://api.onepin.ai/api/v1/workflows/{workflow_id}/runs/preview
Content-Type: application/json

Dry-run credit estimate for a workflow — no run is created.

Returns a per-node-type credit breakdown based on the workflow's current
definition. No run is enqueued, no credits are charged, and the workflow
state is not modified. The optional request body accepts the same
run-scoped `script_text`/`source_language` overrides as `POST /runs`
(applied to this preview only, same as a run's snapshot) — so estimating
with a script and then running with that script prices the operation
that will actually be charged. Use this before calling `POST /runs` to
confirm the expected cost. Equivalent to `POST /estimate`.

Reference: https://onepin.ai/docs/reference/api-reference/workflows/preview-run-api-v-1-workflows-workflow-id-runs-preview-post

## 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)

### Headers

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

### Body (application/json)

This endpoint expects a WorkflowRunStartIn.

- `script_text` (string, optional, nullable) — Run this workflow with this script text instead of the text saved in the workflow's source_script node. Applied to the run's definition snapshot only.
- `source_language` (string, optional, nullable) — BCP-47 language of script_text (e.g. en-us). Optional; when omitted the saved source_language (or automatic detection) applies.

## Response

### 200

Successful Response

- `data` (EstimateResponse, required) — Pre-flight estimate range returned by POST /workflows/\{id}/runs/preview. `min_credits` ≤ owner balance is the run-start gate (don't refuse runs that *might* fit). FE displays "min-max credits" or single value if min == max.
- `meta` (Meta, required)

## Errors

### 422 Unprocessable Entity Error

Validation Error

- `detail` (list of ValidationError, optional)

## Types

### EstimateResponse

Pre-flight estimate range returned by POST /workflows/\{id}/runs/preview. `min_credits` ≤ owner balance is the run-start gate (don't refuse runs that *might* fit). FE displays "min-max credits" or single value if min == max.

- `min_credits` (integer, required)
- `expected_credits` (integer, required)
- `max_credits` (integer, required)
- `breakdown` (list of NodeEstimate, required)
- `can_run` (boolean, required)
- `current_balance` (integer, optional, nullable)
- `deficit_at_max` (integer, optional, nullable)
- `overage_rate_cents_per_credit` (double, optional, nullable)
- `will_incur_overage` (boolean, optional, default: false)
- `estimated_overage_cents` (integer, optional, default: 0)

### 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)

### NodeEstimate

Per-node breakdown row for EstimateResponse.

- `node_id` (string, required)
- `node_type` (string, required)
- `min_credits` (integer, required)
- `expected_credits` (integer, required)
- `max_credits` (integer, required)

### ValidationErrorLocItems

### ValidationErrorCtx

## Examples

**Request**

```json
{}
```

**Response**

```json
{
  "data": {
    "min_credits": 1,
    "expected_credits": 1,
    "max_credits": 1,
    "breakdown": [
      {
        "node_id": "string",
        "node_type": "string",
        "min_credits": 1,
        "expected_credits": 1,
        "max_credits": 1
      }
    ],
    "can_run": true,
    "current_balance": 1,
    "deficit_at_max": 1,
    "overage_rate_cents_per_credit": 1.1,
    "will_incur_overage": false,
    "estimated_overage_cents": 0
  },
  "meta": {
    "request_id": "string",
    "timestamp": "2024-01-15T09:30:00Z"
  }
}
```

**SDK Code**

```python
import requests

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

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

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

print(response.json())
```

```javascript
const url = 'https://api.onepin.ai/api/v1/workflows/workflow_id/runs/preview';
const options = {
  method: 'POST',
  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/preview"

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

	req, _ := http.NewRequest("POST", 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/preview")

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

request = Net::HTTP::Post.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.post("https://api.onepin.ai/api/v1/workflows/workflow_id/runs/preview")
  .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('POST', 'https://api.onepin.ai/api/v1/workflows/workflow_id/runs/preview', [
  '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/preview");
var request = new RestRequest(Method.POST);
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/preview")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
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()
```