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 explains.
Read the balance
get_credits (GET /v1/credits). Check the balance before an analysis.
curl "https://api.gpt-video.com/v1/credits" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY"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);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){
"method": "tools/call",
"params": {
"name": "get_credits",
"arguments": {}
}
}Example response
GET /v1/credits
{
"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 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 (GET /v1/projects). List the projects.
# 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')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;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
{
"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 (POST /v1/projects/{projectId}/analyze/estimate). Price an analysis before starting it.
curl -X POST "https://api.gpt-video.com/v1/projects/$PROJECT_ID/analyze/estimate" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY"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);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
{
"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.
creditsis the price of a new run of the work.creditsToChargeis what starting would charge now. It is 0 when the same work is already running, andrunningJobIdis that job.enoughis true when the start would not be refused for credits or for a plan.payersays who would pay. It isplanorapi_bonus, and it is null when the start would be refused.bonusRemainingis 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 (PATCH /v1/projects/{projectId}), using the values that get_setting_options (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 to see what to do for each answer.
If enough is true, start the analysis.
analyze_project (POST /v1/projects/{projectId}/analyze). Analyze a project.
# 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')// 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;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
{
"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 (GET /v1/jobs/{jobId}). Turn a long video into clips shows the whole path, from the video to the MP4 file.