---
title: "GPT-Video SDKs for JavaScript, Python and n8n"
description: "Install the official GPT-Video SDK for JavaScript or Python, or the n8n nodes. Retries with the same Idempotency-Key, job waits, uploads and webhooks."
url: "https://www.gpt-video.com/developers/sdks"
---

# SDKs

GPT-Video has official SDKs for JavaScript and Python, and nodes for n8n. They call the same API as the rest of these pages, with the same API keys. Each one has a method for every endpoint, named after its page in the [API reference](/developers/api.md), and does for you the parts that cost money when they go wrong: retries that never pay twice, waiting for jobs, uploads in parts and webhook signatures.

## Install

JavaScript and TypeScript, for Node.js 20.19 or later, Deno, Bun and Cloudflare Workers. The package has no dependencies, and its TypeScript types come with it.

```sh
npm install gpt-video
```

Python 3.10 or later. The package uses the standard library only, and you import it as `gpt_video`.

```sh
pip install gpt-video
```

n8n: in n8n, open Settings, then Community Nodes, select Install, and enter `n8n-nodes-gpt-video`. The GPT-Video node has an operation for each endpoint, and the GPT-Video Trigger starts a workflow when a job or an export ends.

## The API key

Create a key on the Developers page of the GPT-Video app, with the scopes `clips:read`, `clips:write` and `spend`. [Authentication](/developers/authentication.md) has the details. Put the key in the environment variable `GPT_VIDEO_API_KEY`, where both SDKs read it. You can also pass it to the client, as `apiKey` in JavaScript and `api_key` in Python.

```sh
export GPT_VIDEO_API_KEY=gvk_...
```

## The first call

`getMe` uses no credits. It shows that the key works and which scopes it has.

```js title="JavaScript"
import { GptVideo } from "gpt-video";

const gv = new GptVideo();
const me = await gv.getMe();
console.log(me.key.scopes);
```

```python title="Python"
from gpt_video import GptVideo

gv = GptVideo()
me = gv.get_me()
print(me["key"]["scopes"])
```

The fields of a call keep the names of the API: `projectId`, `sourceAssetId`. A call returns the body of the answer as the API sends it. In Python, pass the fields as keyword arguments or as one dict.

Each operation page in the [API reference](/developers/api.md) shows its call with the JavaScript SDK next to curl, TypeScript and Python.

## Retries and the Idempotency-Key

A call that spends or creates something gets an `Idempotency-Key` made once, before the first attempt, and the SDK sends that same key on every retry. [Pagination and idempotency](/developers/pagination-and-idempotency.md) explains what the API does with it.

- The SDK retries a failure only when sending the call again is safe. It waits the seconds of `Retry-After` when the API sends them, and retries at most twice by default (`maxRetries`, `max_retries` in Python).
- `IDEMPOTENCY_IN_PROGRESS` means the first request with the key is still running. The SDK waits for its answer, and this does not count as a retry.
- One attempt has 60 seconds and a whole call has 180 seconds, pauses included.

When a call that spends ends without a clear answer, for example after a timeout, the API may have charged it. Its error then has `mayHaveSpent` (`may_have_spent` in Python) set to true. Call again with the key of the error: if the first request ran, you get its answer and pay nothing more. A new key is a new request, and it can be charged again.

```js title="JavaScript"
import { GptVideoError } from "gpt-video";

try {
  await gv.analyzeProject({ projectId });
} catch (error) {
  if (!(error instanceof GptVideoError) || !error.mayHaveSpent) throw error;
  await gv.analyzeProject({ projectId }, { idempotencyKey: error.idempotencyKey });
}
```

```python title="Python"
from gpt_video import GptVideoError

try:
    gv.analyze_project(projectId=project_id)
except GptVideoError as error:
    if not error.may_have_spent:
        raise
    gv.analyze_project(projectId=project_id, idempotency_key=error.idempotency_key)
```

[Errors](/developers/errors.md) lists every code and what to do next.

## Lists

`paginate` follows `nextCursor` for you and asks for the next page only when your code reaches it.

```js title="JavaScript"
for await (const project of gv.paginate("listProjects")) console.log(project.id);
```

```python title="Python"
for project in gv.paginate("list_projects"):
    print(project["id"])
```

## Jobs

An analysis, an export and the other calls that start work answer at once. `wait` takes that answer and reads the work again until it is done, failed or cancelled. Work that failed or was cancelled ends the wait with an error. [Jobs](/developers/jobs.md) explains the states.

```js title="JavaScript"
const analysis = await gv.analyzeProject({ projectId });
await gv.wait(analysis);
```

```python title="Python"
analysis = gv.analyze_project(projectId=project_id)
gv.wait(analysis)
```

## Uploads and downloads

`uploadFile` (`upload_file` in Python) sends a video in parts, reads one part at a time from the disk, and retries each part on its own. `download` fetches the signed link of a finished export and never sends your API key to it. [Upload a video file](/developers/guides/upload-a-video.md) describes the parts.

## Webhooks

Both SDKs check the signature of a webhook on the raw body of the request, before you parse it: `constructEvent` from `gpt-video/webhooks` in JavaScript, and `construct_event` from `gpt_video.webhooks` in Python. A delivery whose signature does not match raises an error. [Receive webhooks in your app](/developers/guides/receive-webhooks.md) has a receiver.

## From MCP to the SDK

Most MCP tools have a method in the SDKs with the same name: `get_credits` (`GET /v1/credits`) is `getCredits` in JavaScript and `get_credits` in Python. These tools have another name in the SDKs.

| MCP tool | JavaScript | Python | How they relate |
| --- | --- | --- | --- |
| `create_auto_clip_project` | `createProject` | `create_project` (`POST /v1/projects`) | The same operation |
| `configure_project` | `updateProject` | `update_project` (`PATCH /v1/projects/{projectId}`) | Close work with other arguments |
| `find_clips` | `analyzeProject` | `analyze_project` (`POST /v1/projects/{projectId}/analyze`) | Close work with other arguments |
| `export_clip` | `acceptClip` | `accept_clip` (`POST /v1/clips/{clipId}/accept`) | Close work with other arguments |
| `export_clip` | `createExport` | `create_export` (`POST /v1/compositions/{compositionId}/exports`) | Close work with other arguments |
| `wait_for_job` | `getJob` | `get_job` (`GET /v1/jobs/{jobId}`) | The same operation |

For close work, read the page of the method before you switch. Five of the first MCP tools, which came before the API, share a name with a method but take arguments of their own: `list_projects` (`GET /v1/projects`), `create_project` (`POST /v1/projects`), `get_project` (`GET /v1/projects/{projectId}`), `list_clips` (`GET /v1/projects/{projectId}/clips`) and `get_job` (`GET /v1/jobs/{jobId}`). Read the page of the method for these too.

On MCP, a tool that spends needs `confirm: true`. The SDKs have no such field: calling the method is the decision.

## Versions and cost

Version 1.0.1 of each SDK calls `/v1`. Each package has its own changelog. A field the API adds reaches your code before the SDK knows it, because the SDKs return the answer as the API sends it.

The SDKs use credits the way the API does. [API pricing](/developers/pricing.md) says what each call uses and how the API bonus works.
