# Estimate a price

`createEstimate`

`POST /v1/estimates`

Prices a create body without creating a generation or holding Credits. The body is the same as createGeneration; webhook_endpoint_id and metadata are accepted and ignored. The input and its media are checked and priced exactly as a create would, including measuring a public input video, so a body the estimate refuses gets the same input error from createGeneration. A create can still fail where an estimate succeeds: on funding or budget (402, 403), on the rate and concurrency limits (429) or on a route change (409). An estimate that must measure an input video takes one token from the organization's generation rate limit and answers 429 rate_limited with Retry-After when none is left; other estimates take one token from the organization's generation read limit and answer 429 rate_limited with Retry-After when none is left. Prices can change between an estimate and a create.

## Authentication

Send your project key in the request header, for example `Authorization: Bearer $VIZO_API_KEY`. Keep the key on your server.

## Request body

- `input` (object, Required) The model input. Its schema is the model's inputSchema in the catalog.
- `metadata` (map<string, string>, Optional) Your own string values, returned unchanged. Put client_reference here so a lost response can be matched later.
- `model` (string, Required) Model ID from listModels or the model page. At least 1 characters
- `webhook_endpoint_id` (string, Optional) Webhook endpoint to notify when the task changes state. Create endpoints in the console.

```json
{
  "input": {},
  "model": "MODEL_ID"
}
```

## Responses

### 200 OK

- `basis` (object, Required)
  - `auto` (boolean, Required) The model chooses the length (for example duration -1 or an inferred video task), so the hold covers the longest output the tier allows or, for an edit whose source was measured, the source length (capped by the tier).
  - `billed_seconds` (integer, Required) Per-second models only: seconds billed after the tier's minimum billing rule. 0 on per-image models.
  - `input_images` (integer, Required) Per-image models only: input images counted for the price. Always 0 on per-second (video) models, whatever images the input has.
  - `input_seconds` (integer, Required) Per-second models only: input video seconds priced, on a tier that bills input video. 0 otherwise.
  - `large` (boolean, Required) Per-image models only: the large-image surcharge applies. Always false on per-second models.
  - `output_seconds` (integer, Required) Per-second models only: output seconds priced. 0 on per-image models.
  - `outputs` (integer, Required) Per-image models only: output images priced. Always 0 on per-second (video) models.
- `estimate` (object, Required)
  - `credits` (string, Required) The Credits a create with the same body would hold.
  - `usd` (object, Required)
    - `amount` (string, Optional) Pattern ^(0\|[1-9][0-9]*)([.][0-9]+)?$
    - `as_of` (date-time, Optional)
    - `basis` (string, Optional) Allowed values: `current_standard_pack`
    - `status` (string, Required) Available when a current USD reference pack supplies the estimate; unavailable when no valid reference exists. This is a display estimate, not the charge. Allowed values: `available`, `unavailable`
    - `tax_included` (boolean, Required) Allowed values: `false`

```json
{
  "basis": {
    "auto": false,
    "billed_seconds": 1,
    "input_images": 1,
    "input_seconds": 1,
    "large": false,
    "output_seconds": 1,
    "outputs": 1
  },
  "estimate": {
    "credits": "157.000",
    "usd": {
      "amount": "string",
      "as_of": "2026-11-02T08:00:00Z",
      "basis": "current_standard_pack",
      "status": "available",
      "tax_included": false
    }
  }
}
```

### 401 Unauthorized

```json
{
  "error": {
    "code": "invalid_credentials",
    "message": "API key is missing, malformed, expired or revoked.",
    "retryable": false,
    "request_id": "9f2c41d07ab3e5c8"
  }
}
```

### 403 Forbidden

```json
{
  "error": {
    "code": "scope_denied",
    "message": "The key does not allow this operation for this project.",
    "retryable": false,
    "request_id": "9f2c41d07ab3e5c8"
  }
}
```

### 404 Not Found

```json
{
  "error": {
    "code": "resource_not_found",
    "message": "The resource is not available to this project.",
    "retryable": false,
    "request_id": "9f2c41d07ab3e5c8"
  }
}
```

### 422 Unprocessable Entity

```json
{
  "error": {
    "code": "invalid_input",
    "message": "Input does not match the published schema.",
    "retryable": false,
    "request_id": "9f2c41d07ab3e5c8"
  }
}
```

### 429 Too Many Requests

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Check Retry-After.",
    "retryable": true,
    "request_id": "9f2c41d07ab3e5c8"
  }
}
```

### 500 Internal Server Error

```json
{
  "error": {
    "code": "request_failed",
    "message": "The request failed. Check recent generations before submitting again.",
    "retryable": false,
    "request_id": "9f2c41d07ab3e5c8"
  }
}
```

### 503 Service Unavailable

```json
{
  "error": {
    "code": "temporarily_unavailable",
    "message": "Service is temporarily unavailable.",
    "retryable": true,
    "request_id": "9f2c41d07ab3e5c8"
  }
}
```

## Errors

Errors share one body. Include the request_id when you contact support.

### Error body

- `error` (object, Required) Details of the error.
  - `code` (string, Required) Stable request error code. invalid_input rejects the request schema; invalid_credentials/scope_denied reject authentication/authorization; ip_not_allowed/model_not_allowed reject API key IP/model restrictions; resource_not_found hides missing or inaccessible resources; conflict rejects incompatible state; insufficient_balance/budget_exceeded/spending_blocked reject funding; balance_negative (402) refuses new tasks while the organization owes Credits from a charge above its hold, until a top-up repays them; rate_limited/concurrency_limited ask for a later retry; temporarily_unavailable is transient service capacity; request_failed covers other HTTP failures. tier_not_sold, draft_not_found, draft_expired and input_duration_out_of_range reject model-specific input. input_media_invalid rejects a detected format/type mismatch; input_media_too_large rejects a probed byte size above the model limit. storage_quota_exceeded rejects an upload when the organization's storage is full. See errors for field-level details. Allowed values: `invalid_input`, `invalid_credentials`, `insufficient_balance`, `balance_negative`, `scope_denied`, `resource_not_found`, `conflict`, `rate_limited`, `concurrency_limited`, `temporarily_unavailable`, `request_failed`, `ip_not_allowed`, `model_not_allowed`, `tier_not_sold`, `draft_not_found`, `draft_expired`, `input_duration_out_of_range`, `input_media_invalid`, `input_media_too_large`, `budget_exceeded`, `spending_blocked`, `storage_quota_exceeded`
  - `errors` (object[], Optional, can be null)
    - `path` (string, Required)
    - `rule` (string, Required)
  - `message` (string, Required) Readable explanation. Do not parse it.
  - `request_id` (string, Required) Include this when you contact support.
  - `retryable` (boolean, Required) True for 429 and 503 only. Even then, check recent tasks before you send a create request again.
  - `task_id` (string, Optional) The task this error belongs to, when one exists.

| Status | What it means and what to do |
| --- | --- |
| 401 | The key is missing, malformed, expired or revoked. Correct the key on your server, then continue. |
| 403 | The key scope, email verification, a budget or an account restriction blocks this. Check the key type, scopes and project budget. |
| 404 | The model or resource does not exist or is outside this project. Check the project, the ID and that the model is enabled. |
| 422 | A field or value is invalid. Compare the request with the field table and the model page. |
| 429 | Too many requests. No task was created. Wait for the Retry-After time before sending again. |
| 500 | The server failed and the task may already be accepted. Do not resend; list recent tasks first. |
| 503 | The service is temporarily unavailable. For a create request, list recent tasks and confirm none was accepted before you submit again. |

### cURL

```bash
curl -X POST "https://api.vizoapi.com/v1/estimates" \
  -H "Authorization: Bearer $VIZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {},
    "model": "MODEL_ID"
  }'
```

### Python

```python
import os, requests

res = requests.post(
    "https://api.vizoapi.com/v1/estimates",
    headers={"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"},
    json={
      "input": {},
      "model": "MODEL_ID"
    },
    timeout=60,
)
print(res.status_code, res.json())
```

### Node.js

```javascript
const res = await fetch("https://api.vizoapi.com/v1/estimates", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.VIZO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "input": {},
    "model": "MODEL_ID"
  }),
});
console.log(res.status, await res.json());
```
