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

# Estimate Workflow

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

Estimate the credit cost of running a workflow without creating a run.

Computes a breakdown of expected credits per node type based on the
workflow's current definition. No run is created, no credits are charged,
and no side effects occur. The optional request body accepts the same
run-scoped `script_text`/`source_language` overrides as `POST /runs`, so
an estimate that will be followed by a run with those overrides prices
the text that run will actually speak. Equivalent to `POST /runs/preview`;
prefer that path in new integrations as it is co-located with the run
lifecycle.

Reference: https://onepin.ai/docs/reference/api-reference/workflows/estimate-workflow-api-v-1-workflows-workflow-id-estimate-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/estimate"

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/estimate';
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/estimate"

	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/estimate")

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/estimate")
  .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/estimate', [
  '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/estimate");
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/estimate")! 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()
```