Skip to content
Create a key

Paste a key to use it in the examples on these pages. The key stays in this browser tab. These pages never send it anywhere.

Handle running out of credits

A call that spends credits can be refused when the account cannot pay. The answer carries a code that says why, and each code needs a different next step. This guide shows how to read the balance, what each refusal means and what your program should do.

The examples read your key from the GPT_VIDEO_API_KEY variable, as Authentication explains. The steps use variables, so an id from one answer feeds the next call.

Who can spend through the API

API bonus: 50 credits. No card needed. API bonus credits can be spent only through the API. After the bonus, the API needs a Creator or Studio plan.

On the Creator and Studio plans, a call that costs credits spends the bonus first when the bonus covers the whole price. Otherwise the plan credits and the top-up pay. On any other plan, a call spends only the bonus. A price the bonus cannot cover in full is refused with PLAN_REQUIRED, and the bonus never pays part of a price. Choose a plan to keep going after the bonus. The API pricing page explains each case.

Read the balance

get_credits (GET /v1/credits). Check the balance with the API bonus.

Request example
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

HTTP 200
{
  "plan": "free",
  "planRequired": true,
  "credits": {
    "period": "2026-09",
    "periodEndsAt": "2026-10-01T00:00:00.000Z",
    "included": 0,
    "remaining": 0,
    "remainingFromPlan": 0,
    "topUp": 0
  },
  "bonus": {
    "status": "active",
    "granted": 50,
    "remaining": 47,
    "grantedAt": "2026-09-01T08:00:00.000Z",
    "expiresAt": "2026-10-01T08:00:00.000Z",
    "resumesAt": null
  },
  "dailyLimits": {
    "resetsAt": "2026-09-02T00:00:00.000Z",
    "exports": {
      "limit": 5,
      "used": 1,
      "remaining": 4
    },
    "assistantTurns": {
      "limit": 20,
      "used": 1,
      "remaining": 19
    },
    "toolRuns": {
      "limit": 10,
      "used": 0,
      "remaining": 10
    },
    "fakeTextScripts": {
      "limit": 10,
      "used": 0,
      "remaining": 10
    },
    "assistantTurnsThroughApi": null
  }
}

Read these fields before you start work that costs credits.

  • bonus.status is active, used_up, expired, paused or cancelled. Only an active bonus can be spent. bonus.remaining is what is left and bonus.expiresAt is when it ends.
  • bonus is null when the account never received a bonus.
  • credits.remaining is the plan credits and the top-up. It does not include the bonus. It can be spent through the API only on the Creator and Studio plans.
  • planRequired is true when the account must choose a plan before it can spend its credits.
  • dailyLimits shows what is left today of exports, Editor Assistant turns, tool runs and fake text scripts.

We can pause the API bonus. While it is paused, a call it would pay for answers PLAN_REQUIRED. A bonus that reached its limit for today also shows as paused. Its bonus.resumesAt is the next 00:00 UTC, and a call it would pay for answers DAILY_LIMIT_REACHED.

Find a project

list_projects (GET /v1/projects). List the projects.

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

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 example keeps the newest project in projectId, so the next step has something to start.

When the answer is INSUFFICIENT_CREDITS

analyze_project (POST /v1/projects/{projectId}/analyze). Not enough credits.

Request example
# Create the key once. Send this same key again if you retry this request.
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST "https://api.gpt-video.com/v1/projects/$PROJECT_ID/analyze" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY"
// 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();
console.log(data);
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()
print(data)
Example response

POST /v1/projects/prj_7c2e9a4f1b3d4e8096a5/analyze

HTTP 402
{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Out of credits: 10 of 800 left this month and this video needs 30. Top up or upgrade your plan.",
    "hint": "Buy credits or change plan at https://www.gpt-video.com/app/billing.",
    "requestId": "req_3e8b5f1a0c7d4e29b6a4f0c1"
  }
}

This call starts a real analysis when the account can pay for it. When it cannot, the answer is status 402 and the code INSUFFICIENT_CREDITS. The message says how many credits are left and how many the work needs, and the hint says where to add credits. Nothing was charged.

Do not send the request again by itself. It fails the same way until a person adds credits or changes the plan in the GPT-Video app. Tell your user, and stop the work that needs the credits.

Call estimate_analysis (POST /v1/projects/{projectId}/analyze/estimate) before you start work, and you see this answer only when the balance changed in between. Estimate the cost before you spend credits shows how.

When the answer is PLAN_REQUIRED

PLAN_REQUIRED has status 402 too. It means the call needs a plan that the account does not have, or that the bonus could not pay. It comes in these cases.

  • The account is not on the Creator or Studio plan, and its bonus is used up, expired or cancelled.
  • The account is not on the Creator or Studio plan, and we paused the bonus.
  • The account is not on the Creator or Studio plan, and the price is higher than what is left in the bonus. The bonus pays a whole price or none of it.
  • An export is longer than the API bonus pays for. create_export (POST /v1/compositions/{compositionId}/exports) gives the answer for this case.

The message and the hint say which case it is. Do not retry. Tell your user to choose a Creator or Studio plan on the pricing page, then send the call again.

When the answer is DAILY_LIMIT_REACHED

DAILY_LIMIT_REACHED has status 429. A daily limit is used up, or the API bonus reached its limit for today. Every daily limit starts again at 00:00 UTC.

Stop sending calls of that kind until then. Retrying earlier fails the same way. Read dailyLimits from the balance to see which limit is used up. The API bonus has its own daily limits.

Limit while the API bonus paysValue
Exports a day5
Longest export600 seconds
Editor Assistant turns a day20
Tool runs a day10
Fake text scripts a day10
Jobs running at once1

Rate limits are a different case. RATE_LIMITED means you sent requests too fast. Wait the seconds in Retry-After and send the same request again.

What your program should do

CodeStatusWhat to do
INSUFFICIENT_CREDITS402Stop the work and tell your user to add credits in the app.
PLAN_REQUIRED402Stop the work and tell your user to choose a Creator or Studio plan.
DAILY_LIMIT_REACHED429Stop that kind of call until 00:00 UTC and tell your user when it starts again.
RATE_LIMITED429Wait for Retry-After, then send the same request.

None of the first three is worth a timer-based retry. They end when a person acts, or when the day changes.

After the account can pay

Read the estimate again, then send the call. If you are not sure whether an earlier try was charged, send the call with the same Idempotency-Key as before. A retry never charges twice, and it answers what was paid and by whom. GPT-Video API errors and what to do next lists every code.