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

GET https://api.onepin.ai/api/v1/templates/{template_id}/estimate

Estimate the credit cost of running a workflow built from this template.

Returns a per-unit pricing guide expressed in credits per
`unit_chars` input characters (default 1,000). Because the template does not
contain the caller's actual script, the estimate uses a synthetic fixed-length
input to compute a reproducible per-unit rate. Multiply by your expected
character count to project total cost.

The response distinguishes variable costs (scale with script length, e.g.
synthesis) from fixed costs (apply once per run regardless of length). A
node-level breakdown is included so callers can see which processing steps
drive the cost.

Results are cached against the template definition and current pricing rates.
`cache_status` indicates whether this response was served from cache (`hit`),
computed fresh (`miss`), or recomputed because the definition or rates changed
(`stale`).

Visibility rules match `GET /templates/{id}` — own-workspace templates use the
draft definition; cross-workspace templates use the published snapshot.

Dual-auth: Bearer JWT or API key (scope `templates:read`).

Reference: https://onepin.ai/docs/reference/api-reference/templates/estimate-template-api-v-1-templates-template-id-estimate-get

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

- `template_id` (string, required) — Case-sensitive 8-character base62 template identifier.

### Headers

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

## Response

### 200

Successful Response

- `data` (TemplateEstimateResponse, required) — Per-unit pricing guide for a visible template snapshot. Templates do not contain the caller's final script. Costs are therefore quoted for a fixed input-character unit plus fixed workflow overhead; cloned workflows still use `EstimateResponse` once real script text exists.
- `meta` (Meta, required)

## Errors

### 422 Unprocessable Entity Error

Validation Error

- `detail` (list of ValidationError, optional)

## Types

### TemplateEstimateResponse

Per-unit pricing guide for a visible template snapshot. Templates do not contain the caller's final script. Costs are therefore quoted for a fixed input-character unit plus fixed workflow overhead; cloned workflows still use `EstimateResponse` once real script text exists.

- `unit_chars` (integer, required) — Character count of the synthetic input used to compute the per-unit rates. Divide your expected script length by this value and multiply by the per-unit credit fields to project total cost.
- `variable_min_credits_per_unit` (integer, required) — Minimum variable credits per `unit_chars` characters (best-case pricing, scales with script length).
- `variable_expected_credits_per_unit` (integer, required) — Expected variable credits per `unit_chars` characters (typical pricing).
- `variable_max_credits_per_unit` (integer, required) — Maximum variable credits per `unit_chars` characters (worst-case pricing).
- `fixed_min_credits` (integer, required) — Minimum fixed credits charged once per run regardless of script length.
- `fixed_expected_credits` (integer, required) — Expected fixed credits per run.
- `fixed_max_credits` (integer, required) — Maximum fixed credits per run.
- `total_min_credits_per_unit` (integer, required) — Total minimum credits per `unit_chars` (variable_min + fixed_min).
- `total_expected_credits_per_unit` (integer, required) — Total expected credits per `unit_chars`.
- `total_max_credits_per_unit` (integer, required) — Total maximum credits per `unit_chars`.
- `breakdown` (list of NodeEstimate, required) — Per-node credit breakdown showing which processing steps drive the cost.
- `source_snapshot` (enum, required) — `draft` when the estimate is based on the owner's live definition; `published` when based on the gallery snapshot.
  - Allowed values: `draft`, `published`
- `source_node_ids` (list of string, required) — IDs of the source nodes used to anchor the estimate calculation.
- `definition_fingerprint` (string, required) — Hash of the template definition at the time this estimate was computed. Changes when the workflow graph is modified.
- `rate_fingerprint` (string, required) — Hash of the pricing rates used. Changes when credit rates are updated.
- `cache_status` (enum, required) — `hit` — served from cache; `miss` — computed fresh (no prior cache); `stale` — recomputed because the definition or rates changed.
  - Allowed values: `hit`, `miss`, `stale`
- `computed_at` (datetime, required) — UTC timestamp when this estimate was last computed.

### 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": {
    "unit_chars": 1000,
    "variable_min_credits_per_unit": 5,
    "variable_expected_credits_per_unit": 8,
    "variable_max_credits_per_unit": 12,
    "fixed_min_credits": 3,
    "fixed_expected_credits": 5,
    "fixed_max_credits": 7,
    "total_min_credits_per_unit": 8,
    "total_expected_credits_per_unit": 13,
    "total_max_credits_per_unit": 19,
    "breakdown": [
      {
        "node_id": "n1a2b3c4",
        "node_type": "text_synthesis",
        "min_credits": 4,
        "expected_credits": 6,
        "max_credits": 9
      },
      {
        "node_id": "n5d6e7f8",
        "node_type": "data_fetch",
        "min_credits": 1,
        "expected_credits": 2,
        "max_credits": 3
      },
      {
        "node_id": "n9g0h1i2",
        "node_type": "post_processing",
        "min_credits": 3,
        "expected_credits": 5,
        "max_credits": 7
      }
    ],
    "source_snapshot": "draft",
    "source_node_ids": [
      "n1a2b3c4",
      "n5d6e7f8",
      "n9g0h1i2"
    ],
    "definition_fingerprint": "a3f5c9d7e1b2f4a6",
    "rate_fingerprint": "b7d8e9f0a1c2d3e4",
    "cache_status": "hit",
    "computed_at": "2024-01-15T09:30:00Z"
  },
  "meta": {
    "request_id": "req_20240115_093000_abc123",
    "timestamp": "2024-01-15T09:30:00Z"
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.onepin.ai/api/v1/templates/template_id/estimate"

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

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

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/templates/template_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('GET', 'https://api.onepin.ai/api/v1/templates/template_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/templates/template_id/estimate");
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/templates/template_id/estimate")! 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()
```