---
title: "Estimate the cost before you spend credits"
description: "Price an analysis before you start it, compare the price with the balance, and decide in code whether to go ahead, wait or ask the user."
url: "https://www.gpt-video.com/developers/guides/estimate-before-spending"
---

# Estimate the cost before you spend credits

Some calls spend credits. Before you start one, you can ask what it costs and whether the account can pay for it. The estimate changes nothing and uses nothing.

This guide reads the balance, estimates the price of an analysis and starts the analysis only when the account can pay. The steps use variables, so an id from one answer feeds the next call. The examples read your key from the `GPT_VIDEO_API_KEY` variable, as [Authentication](/developers/authentication.md) explains.

## Read the balance

[`get_credits`](/developers/api/get-credits.md) (`GET /v1/credits`). Check the balance before an analysis.

```bash title="curl"
curl "https://api.gpt-video.com/v1/credits" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY"
```

```ts title="TypeScript"
const res = await fetch("https://api.gpt-video.com/v1/credits", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
console.log(data);
```

```python title="Python"
import os

import requests

r = requests.get(
    "https://api.gpt-video.com/v1/credits",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
print(data)
```

```json title="MCP (Full)"
{
  "method": "tools/call",
  "params": {
    "name": "get_credits",
    "arguments": {}
  }
}
```

**Example response**

`GET /v1/credits`

```json title="HTTP 200"
{
  "plan": "creator",
  "planRequired": false,
  "credits": {
    "period": "2026-09",
    "periodEndsAt": "2026-10-01T00:00:00.000Z",
    "included": 800,
    "remaining": 1020,
    "remainingFromPlan": 770,
    "topUp": 250
  },
  "bonus": null,
  "dailyLimits": {
    "resetsAt": "2026-09-02T00:00:00.000Z",
    "exports": {
      "limit": 100,
      "used": 1,
      "remaining": 99
    },
    "assistantTurns": {
      "limit": 500,
      "used": 12,
      "remaining": 488
    },
    "toolRuns": {
      "limit": 100,
      "used": 2,
      "remaining": 98
    },
    "fakeTextScripts": {
      "limit": 100,
      "used": 0,
      "remaining": 100
    },
    "assistantTurnsThroughApi": {
      "limit": 100,
      "used": 4,
      "remaining": 96
    }
  }
}
```

`credits.remaining` is what the plan credits and the top-up can pay right now. The API bonus is not part of it. The bonus has its own block, `bonus`, with what is left and when it ends.

When `planRequired` is true, the account must choose a plan in the GPT-Video app before it can spend those credits. Through the API, plan credits and top-up credits are spent only on the Creator and Studio plans. [API pricing](/developers/pricing.md) explains what the API bonus pays.

`dailyLimits` shows the exports, Editor Assistant turns, tool runs and fake text scripts that are left today. They start again at 00:00 UTC. Exports use no credits, so check this block before you export.

## Find the project

[`list_projects`](/developers/api/list-projects.md) (`GET /v1/projects`). List the projects.

```bash title="curl"
# This command needs jq to read the answer.
PROJECT_ID=$(curl -sS "https://api.gpt-video.com/v1/projects" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" | jq -r '.data[0].id')
```

```ts title="TypeScript"
const res = await fetch("https://api.gpt-video.com/v1/projects", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
const projectId = data.data[0].id;
```

```python title="Python"
import os

import requests

r = requests.get(
    "https://api.gpt-video.com/v1/projects",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
project_id = data["data"][0]["id"]
```

**Example response**

`GET /v1/projects`

```json title="HTTP 200"
{
  "data": [
    {
      "id": "prj_1d8b6e0c47a24f93b1c2",
      "kind": "autoclip",
      "title": "Weekly podcast",
      "status": "draft",
      "sourceAssetId": null,
      "compositionId": null,
      "settings": {
        "language": null,
        "clipCount": 5,
        "minDurationSec": null,
        "maxDurationSec": null,
        "objective": "educational",
        "captionStyle": null,
        "reframe": null,
        "instructions": null,
        "vocabulary": null
      },
      "missingSettings": [
        "language",
        "minDurationSec",
        "maxDurationSec",
        "captionStyle",
        "reframe"
      ],
      "splitScreen": null,
      "streamer": null,
      "redditStory": null,
      "fakeText": null,
      "vibeEdit": null,
      "errorMessage": null,
      "createdAt": "2026-09-01T09:50:00.000Z",
      "updatedAt": "2026-09-01T09:50:00.000Z"
    },
    {
      "id": "prj_7c2e9a4f1b3d4e8096a5",
      "kind": "autoclip",
      "title": "Founder interview",
      "status": "ready",
      "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
      "compositionId": null,
      "settings": {
        "language": "en",
        "clipCount": 3,
        "minDurationSec": 15,
        "maxDurationSec": 60,
        "objective": "viral",
        "captionStyle": "word-pop",
        "reframe": "face",
        "instructions": null,
        "vocabulary": null
      },
      "missingSettings": [],
      "splitScreen": null,
      "streamer": null,
      "redditStory": null,
      "fakeText": null,
      "vibeEdit": null,
      "errorMessage": null,
      "createdAt": "2026-09-01T09:10:00.000Z",
      "updatedAt": "2026-09-01T09:40:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

The list shows the newest project first. Projects made in the GPT-Video app are listed too. The example keeps the first one in `projectId`. If you know the project you want, set `projectId` yourself.

## Estimate the price

[`estimate_analysis`](/developers/api/estimate-analysis.md) (`POST /v1/projects/{projectId}/analyze/estimate`). Price an analysis before starting it.

```bash title="curl"
curl -X POST "https://api.gpt-video.com/v1/projects/$PROJECT_ID/analyze/estimate" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY"
```

```ts title="TypeScript"
const res = await fetch(`https://api.gpt-video.com/v1/projects/${projectId}/analyze/estimate`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
console.log(data);
```

```python title="Python"
import os

import requests

r = requests.post(
    f"https://api.gpt-video.com/v1/projects/{project_id}/analyze/estimate",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
print(data)
```

**Example response**

`POST /v1/projects/prj_7c2e9a4f1b3d4e8096a5/analyze/estimate`

```json title="HTTP 200"
{
  "estimate": {
    "credits": 30,
    "creditsToCharge": 30,
    "sourceDurationSec": 1800,
    "runningJobId": null,
    "creditsAvailable": 640,
    "planRequired": false,
    "enough": true,
    "payer": "plan",
    "bonusRemaining": null
  }
}
```

The estimate works for the analysis of an Auto Clip project, for the build of a split screen, streamer or Reddit story project, and for the preparation of a Vibe Editing project. Fake text projects have no price here, because their story and voices use the daily fake text allowance. The answer is `CONFLICT` for them.

Read these fields of `estimate`.

- `credits` is the price of a new run of the work.
- `creditsToCharge` is what starting would charge now. It is 0 when the same work is already running, and `runningJobId` is that job.
- `enough` is true when the start would not be refused for credits or for a plan.
- `payer` says who would pay. It is `plan` or `api_bonus`, and it is null when the start would be refused.
- `bonusRemaining` is what is left of the API bonus, or null when the account never had one.

The estimate refuses for the same reasons the start would. A project without a source video answers `SOURCE_MISSING`, and settings still to choose answer `CONFLICT` with the names of the settings. The settings do not change the price of an analysis, which depends only on the length of the video. To price a project whose settings are still open, choose them with [update_project](/developers/api/update-project.md) (`PATCH /v1/projects/{projectId}`), using the values that [get_setting_options](/developers/api/get-setting-options.md) (`GET /v1/reference/setting-options`) lists, and estimate again. They can still change before the analysis starts. A project the estimate accepts is ready to start, and the price it gives is the price the start charges.

The API bonus pays for a whole price or for none of it. It never pays part of a price, and a price is never split between the bonus and the plan.

## Start only when it is enough

If `enough` is false, do not start. Read [Handle running out of credits](/developers/guides/out-of-credits.md) to see what to do for each answer.

If `enough` is true, start the analysis.

[`analyze_project`](/developers/api/analyze-project.md) (`POST /v1/projects/{projectId}/analyze`). Analyze a project.

```bash title="curl"
# This command needs jq to read the answer.
# Create the key once. Send this same key again if you retry this request.
IDEMPOTENCY_KEY=$(uuidgen)
JOB_ID=$(curl -sS -X POST "https://api.gpt-video.com/v1/projects/$PROJECT_ID/analyze" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" | jq -r '.job.id')
```

```ts title="TypeScript"
// Create the key once. Send this same key again if you retry this request.
const idempotencyKey = crypto.randomUUID();
const res = await fetch(`https://api.gpt-video.com/v1/projects/${projectId}/analyze`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
  },
});
const data = await res.json();
const jobId = data.job.id;
```

```python title="Python"
import os
import uuid

import requests

# Create the key once. Send this same key again if you retry this request.
idempotency_key = str(uuid.uuid4())
r = requests.post(
    f"https://api.gpt-video.com/v1/projects/{project_id}/analyze",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
)
data = r.json()
job_id = data["job"]["id"]
```

**Example response**

`POST /v1/projects/prj_7c2e9a4f1b3d4e8096a5/analyze`

```json title="HTTP 202"
{
  "job": {
    "id": "job_b582bc8315e372bf9ba3",
    "kind": "analysis",
    "status": "queued",
    "progress": 0,
    "errorMessage": null,
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "compositionId": null,
    "exportId": null,
    "assetId": null,
    "toolRunId": null,
    "turnId": null,
    "createdAt": "2026-09-01T11:10:00.000Z",
    "startedAt": null,
    "finishedAt": null
  },
  "creditsCharged": 30,
  "reused": false,
  "paidWith": "plan"
}
```

`creditsCharged` is the price the estimate gave. If time has passed since the estimate, another charge from the API or from the app may have lowered the balance, so read the estimate again right before you start.

The estimate does not reserve credits. They are charged when the analysis starts. If the request times out, send it again with the same `Idempotency-Key`. A retry never charges twice, and it answers what was paid and by whom.

Follow the job with [get_job](/developers/api/get-job.md) (`GET /v1/jobs/{jobId}`). [Turn a long video into clips](/developers/guides/clips-from-a-video.md) shows the whole path, from the video to the MP4 file.
