---
title: "Get the story of a fake text project | GPT-Video API"
description: "Returns the story of a fake text project, line by line, with the voice of each side and which lines have their voice."
url: "https://www.gpt-video.com/developers/api/get-fake-text-story"
operation: "get_fake_text_story"
method: "GET"
path: "/v1/projects/{projectId}/story"
scope: "clips:read"
---

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

# Get the story of a fake text project

Operation id `get_fake_text_story`. Request `GET /v1/projects/{projectId}/story`.

Returns the story of a fake text project, line by line, with the voice of each side and which lines have their voice.

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

Use it to read the story before you change it with update_fake_text_story (`PUT /v1/projects/{projectId}/story`): send back the line ids you read here to keep those lines and their voices. story is null until the project has a story. Each line says whether it has its voice; create_fake_text_voices (`POST /v1/projects/{projectId}/story/voices`) makes the missing ones.

## Example use

Before it fixes a typo in one line, a script reads the story and keeps every line id. It sends the story back with those ids through update_fake_text_story (`PUT /v1/projects/{projectId}/story`), so only the corrected line loses its voice.

## Endpoint

```text
GET https://api.gpt-video.com/v1/projects/{projectId}/story
```

- 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 |
| --- | --- | --- | --- |
| `projectId` | string | Required | The project id. It starts with prj_. Matches `^prj_[0-9a-f]{20}$`. |

## Request

Read the story.

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

```ts title="TypeScript"
const res = await fetch("https://api.gpt-video.com/v1/projects/prj_ba8b07a88bfb14585459/story", {
  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/projects/prj_ba8b07a88bfb14585459/story",
    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_fake_text_story",
    "arguments": {
      "projectId": "prj_ba8b07a88bfb14585459"
    }
  }
}
```

## Response

A successful call answers HTTP 200.

### Fields to check

| Name | Type | Description |
| --- | --- | --- |
| `story.id` | string | The story id. It starts with cnv_. |
| `story.messages[].id` | string | The line id. Send it back with update_fake_text_story (`PUT /v1/projects/{projectId}/story`) to keep the line and its voice. |

### Response example

```json title="HTTP 200"
{
  "story": {
    "id": "cnv_a744d5ef6cc005e19285",
    "projectId": "prj_ba8b07a88bfb14585459",
    "title": "The surprise party",
    "contactName": "Sam",
    "language": "en",
    "left": {
      "name": "Sam"
    },
    "right": {
      "name": "Me"
    },
    "voices": {
      "left": "en-guy",
      "right": "en-aria"
    },
    "messages": [
      {
        "id": "msg_e09afef44e32faf0bcb7",
        "side": "left",
        "text": "Are you free on Saturday night?",
        "voiced": true,
        "voiceDurationSec": 1.9
      },
      {
        "id": "msg_568316dbc0cca81d3b24",
        "side": "right",
        "text": "Maybe, why?",
        "voiced": false,
        "voiceDurationSec": null
      },
      {
        "id": "msg_556466181cf09d6bf0a2",
        "side": "left",
        "text": "No reason. Wear something nice.",
        "voiced": true,
        "voiceDurationSec": 1.9
      },
      {
        "id": "msg_0d93572450a652e08706",
        "side": "right",
        "text": "Now I am worried.",
        "voiced": false,
        "voiceDurationSec": null
      }
    ],
    "voicedCount": 2,
    "createdAt": "2026-08-31T22:12:00.000Z",
    "updatedAt": "2026-08-31T22:14:00.000Z"
  }
}
```

**All response fields**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `story` | object or null | Required | The story of the project; null until it has one. |
| `story.id` | string | Required | The story id. It starts with cnv_. |
| `story.projectId` | string | Required |   |
| `story.title` | string | Required |   |
| `story.contactName` | string | Required | The name shown at the top of the chat. |
| `story.language` | string | Required | One of `en`, `it`. |
| `story.left` | object | Required | The other person in the chat. |
| `story.right` | object | Required | The person whose phone shows the chat. |
| `story.voices` | object | Required | The voice of each side, as list_voices (`GET /v1/reference/voices`) names it; null for the default voice of the language, or for a voice that is not available through the API. |
| `story.messages` | array of objects | Required |   |
| `story.voicedCount` | integer | Required | How many lines have their voice. |
| `story.createdAt` | string | Required | An ISO 8601 time in UTC. |
| `story.updatedAt` | string | Required | An ISO 8601 time in UTC. |

**Fields of story.left**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `story.left.name` | string | Required |   |

**Fields of story.right**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `story.right.name` | string | Required |   |

**Fields of story.voices**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `story.voices.left` | string or null | Required |   |
| `story.voices.right` | string or null | Required |   |

**Fields of story.messages[]**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `story.messages[].id` | string | Required | The line id. Send it back with update_fake_text_story (`PUT /v1/projects/{projectId}/story`) to keep the line and its voice. |
| `story.messages[].side` | string | Required | One of `left`, `right`. |
| `story.messages[].text` | string | Required |   |
| `story.messages[].voiced` | boolean | Required | true when the line has its voice. |
| `story.messages[].voiceDurationSec` | number or null | Required | How long the voice of the line lasts, in seconds; null without a voice. |

**More examples**

#### A project with no story yet

`GET /v1/projects/prj_ba8b07a88bfb14585459/story`

```json title="HTTP 200"
{
  "story": null
}
```

#### A project that is not a fake text project

`GET /v1/projects/prj_7c2e9a4f1b3d4e8096a5/story`

```json title="HTTP 409"
{
  "error": {
    "code": "CONFLICT",
    "message": "This project is not a fake text project. Stories exist only in fake text projects.",
    "requestId": "req_6e2a9c0d4b7f41583a1e9d6c"
  }
}
```

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`NOT_FOUND`](/developers/errors.md#NOT_FOUND) | 404 | See the error reference. | Change the request, then send it again. |
| [`CONFLICT`](/developers/errors.md#CONFLICT) | 409 | A project that is not a fake text project | Read the resource again, then decide. Do not send the same request 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

- [`update_fake_text_story`](/developers/api/update-fake-text-story.md) (`PUT /v1/projects/{projectId}/story`) Write the story of a fake text project
- [`generate_fake_text_story`](/developers/api/generate-fake-text-story.md) (`POST /v1/projects/{projectId}/story/generate`) Write a fake text story with GPT-Video
- [`create_fake_text_voices`](/developers/api/create-fake-text-voices.md) (`POST /v1/projects/{projectId}/story/voices`) Make the voices of a fake text story
- [`update_fake_text_project`](/developers/api/update-fake-text-project.md) (`PATCH /v1/projects/{projectId}/fake-text`) Update a fake text project

## Questions

### Does a line keep its voice when I change its words?

It keeps the old voice until create_fake_text_voices (`POST /v1/projects/{projectId}/story/voices`) makes a new one for the new words. Lines whose words do not change are not voiced again.

### Why is the voice of a side null?

null is the default voice of the language. It is also null when the project uses a voice that is not offered through the API; choose one with update_fake_text_story (`PUT /v1/projects/{projectId}/story`).
