---
title: "Get a job | GPT-Video API reference"
description: "Returns a job that another call started: its kind, status and progress."
url: "https://www.gpt-video.com/developers/api/get-job"
operation: "get_job"
method: "GET"
path: "/v1/jobs/{jobId}"
scope: "clips:read"
---

Do not assume endpoints have parameters other than the ones explicitly specified.

# Get a job

Operation id `get_job`. Request `GET /v1/jobs/{jobId}`.

Returns a job that another call started: its kind, status and progress.

Beta. This endpoint is supported and can gain new fields. If a change would break your code, we tell you before it takes effect and list it in the changelog.

## About

Call this to follow the work another call started. analyze_project (`POST /v1/projects/{projectId}/analyze`) and the build calls return their job, and so do the import of a video from a link and the story and voice calls of a fake text project; a tool run, an Editor Assistant turn and a composition's framing carry the id of their job. While the job is queued or running, add ?wait= with up to 25 seconds to hold the request until it ends, then call again if it is still working. A job that failed or stopped says what happened in errorMessage. A finished import job gives the new video in assetId. A tool job gives its run in toolRunId, to read with get_tool_run (`GET /v1/tool-runs/{toolRunId}`), and an assistant_turn job gives its turn in turnId, to read with get_assistant_turn (`GET /v1/assistant/turns/{turnId}`). An export is easier to follow with get_export (`GET /v1/exports/{exportId}`), which also gives its download link; an export id (rnd_) is not a job id here.

## Example use

A script that started an analysis keeps the job id from analyze_project (`POST /v1/projects/{projectId}/analyze`), calls get_job (`GET /v1/jobs/{jobId}`) with ?wait= until the job ends, and then reads the clips with list_clips (`GET /v1/projects/{projectId}/clips`) or reads errorMessage when the job failed.

## Endpoint

```text
GET https://api.gpt-video.com/v1/jobs/{jobId}
```

- Scope `clips:read`.
- Rate limit 120 requests a minute for each key and 240 for each account, shared by every `read` call. See [rate limits](/developers/rate-limits.md).
- No `Idempotency-Key`.

## Parameters

### Path

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `jobId` | string | Required | The id of the job. The calls that start work return it, and so do analysisJobId of a project, framing.jobId of a composition and the jobId of an export, a tool run or an Editor Assistant turn. It starts with job_. Matches `^job_[0-9a-f]{20}$`. |

### Query

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `wait` | integer | Optional | Seconds to hold the request while the work is still running, up to 25. Leave it out to answer at once. If many requests of the account are already waiting, it answers at once; wait a few seconds before you read again. From 0 to 25. |

## Request

Get a finished analysis.

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

```ts title="TypeScript"
const res = await fetch("https://api.gpt-video.com/v1/jobs/job_6c1e8b4a2f0d4a97e3b5", {
  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/jobs/job_6c1e8b4a2f0d4a97e3b5",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
print(data)
```

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

## Response

A successful call answers HTTP 200. Returns a [Job object](/developers/objects.md#job) in `job`.

### Fields to check

| Name | Type | Description |
| --- | --- | --- |
| `job.id` | string | The job id. It starts with job_. |
| `job.status` | string | queued and running are still working; done, failed and cancelled are final. |

### Response example

```json title="HTTP 200"
{
  "job": {
    "id": "job_6c1e8b4a2f0d4a97e3b5",
    "kind": "analysis",
    "status": "done",
    "progress": 1,
    "errorMessage": null,
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "compositionId": null,
    "exportId": null,
    "assetId": null,
    "toolRunId": null,
    "turnId": null,
    "createdAt": "2026-09-01T09:11:00.000Z",
    "startedAt": "2026-09-01T09:12:00.000Z",
    "finishedAt": "2026-09-01T09:40:00.000Z"
  }
}
```

**All response fields**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `job` | Job object | Required | Described in [Job object](/developers/objects.md#job). |

**More examples**

#### Get the job of an export

`GET /v1/jobs/job_a3e9d05c7b1f4e2896d4`

```json title="HTTP 200"
{
  "job": {
    "id": "job_a3e9d05c7b1f4e2896d4",
    "kind": "export",
    "status": "done",
    "progress": 1,
    "errorMessage": null,
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "compositionId": "cmp_4e8c2a6f09d14b73a1e5",
    "exportId": "rnd_0f6b3e9d2c7a4158b4a3",
    "assetId": null,
    "toolRunId": null,
    "turnId": null,
    "createdAt": "2026-09-01T10:10:00.000Z",
    "startedAt": "2026-09-01T10:11:00.000Z",
    "finishedAt": "2026-09-01T10:15:00.000Z"
  }
}
```

#### Wait for a job to finish

`GET /v1/jobs/job_6c1e8b4a2f0d4a97e3b5?wait=25`

```json title="HTTP 200"
{
  "job": {
    "id": "job_6c1e8b4a2f0d4a97e3b5",
    "kind": "analysis",
    "status": "done",
    "progress": 1,
    "errorMessage": null,
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "compositionId": null,
    "exportId": null,
    "assetId": null,
    "toolRunId": null,
    "turnId": null,
    "createdAt": "2026-09-01T09:11:00.000Z",
    "startedAt": "2026-09-01T09:12:00.000Z",
    "finishedAt": "2026-09-01T09:40:00.000Z"
  }
}
```

#### A job that does not exist

`GET /v1/jobs/job_8d4b2f6a0e9c41b7d5f1`

```json title="HTTP 404"
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Job job_8d4b2f6a0e9c41b7d5f1 not found",
    "hint": "Check the id. An id that belongs to another account is not found either.",
    "requestId": "req_4d8b1f6a3c0e7b2d9a5f1e63"
  }
}
```

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`NOT_FOUND`](/developers/errors.md#NOT_FOUND) | 404 | A job that does not exist | Change the request, then send it again. |

Every call can also answer these errors. [`VALIDATION_FAILED`](/developers/errors.md#VALIDATION_FAILED), [`UNKNOWN_PARAMETER`](/developers/errors.md#UNKNOWN_PARAMETER), [`UNAUTHORIZED`](/developers/errors.md#UNAUTHORIZED), [`SCOPE_MISSING`](/developers/errors.md#SCOPE_MISSING), [`RATE_LIMITED`](/developers/errors.md#RATE_LIMITED), [`INTERNAL`](/developers/errors.md#INTERNAL), [`UNAVAILABLE`](/developers/errors.md#UNAVAILABLE).

## Cost

No credits are used.

## Related operations

- [`analyze_project`](/developers/api/analyze-project.md) (`POST /v1/projects/{projectId}/analyze`) Analyze a project
- [`get_export`](/developers/api/get-export.md) (`GET /v1/exports/{exportId}`) Get an export
- [`get_composition`](/developers/api/get-composition.md) (`GET /v1/compositions/{compositionId}`) Get a composition
- [`get_project`](/developers/api/get-project.md) (`GET /v1/projects/{projectId}`) Get a project
- [`reframe_composition`](/developers/api/reframe-composition.md) (`POST /v1/compositions/{compositionId}/reframe`) Frame a composition again
- [`create_export`](/developers/api/create-export.md) (`POST /v1/compositions/{compositionId}/exports`) Export a composition

## Questions

### Which jobs can I read?

The analysis of an Auto Clip project or the preparation of a Vibe Editing project (kind analysis), the build of a split screen, streamer or Reddit story video (kind build), the export of a composition (kind export), the face framing of a clip (kind framing), the import of a video from a link (kind import), the story of a fake text project (kind story) and its voices (kind voices), a voiceover, an audio separation or a brainstorm (kind tool), and a turn of the Editor Assistant (kind assistant_turn). Other work the GPT-Video app does in the background is not a job of the API.

### How do I wait for a job without polling in a tight loop?

Send ?wait=25. The request is held until the job ends or 25 seconds pass, whichever comes first, and then answers the job as it is. If many requests of the account are already waiting, it answers at once instead; wait a few seconds before you read again.

### Can I pass an export id (rnd_) here?

No: the call answers 400, because only job ids (job_) are accepted, and the error names get_export (`GET /v1/exports/{exportId}`). Follow an export with get_export, which also gives its download link.
