> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sigmora.org/llms.txt
> Use this file to discover all available pages before exploring further.

# From a document to a video

> You have writing — a lesson, an article, a chapter. This is the whole path from that text to a finished, narrated video file, and where the file comes out.

This is the flow the API is named for. You have prose already written, and you
want it narrated and rendered. It is one call, then polling, and the file arrives
as a URL you download.

<Note>
  **Send the text itself.** You do not need to upload anything first, create a
  project first, or find an id for your lesson in some catalogue. `story` is the
  input.
</Note>

## The call

```bash theme={null}
curl -X POST https://api.sigmora.org/v1/video/render \
  -H "Authorization: Bearer $SIGMORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: week5-stats-001" \
  -d '{
    "story": "Statistics is the science of collecting, organising, analysing and interpreting data. Two branches cover almost everything. Descriptive statistics summarise what a dataset shows...",
    "title": "Statistics for AI — Week 5",
    "aspect": "horizontal",
    "quality": "h"
  }'
```

```json theme={null}
{
  "success": true,
  "projectId": "prj_9x2...",
  "jobId": "job_4k1...",
  "status": "queued",
  "credits": 300,
  "creditBalance": 3900
}
```

That is the whole request. `story` carries up to 40,000 characters — a long
lecture fits comfortably. Everything else is optional:

| Field       | What it does                                                                                                                  |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `title`     | What to call the video. Derived from the text when omitted.                                                                   |
| `aspect`    | `horizontal` for long-form, `vertical` for a feed.                                                                            |
| `quality`   | `l`, `m`, `h` or `k`. Costs the same; higher takes longer.                                                                    |
| `renderer`  | `motif` for animated typography and diagrams, `images` for illustrated stills, `blender` for 3D. Chosen for you when omitted. |
| `ttsVoice`  | A specific narration voice. The deployment default is used otherwise.                                                         |
| `projectId` | Only if you want this filed alongside earlier work. One is created otherwise, and its id is on the response.                  |

<Warning>
  `story` is rendered by the `stories` domain, and sending it alongside a
  different `domain` is refused. The other domains (`course`, `science`,
  `algorithms`, `chess`, `howto`, `ugc`) read their subject out of this
  deployment's own catalogue by `videoId`, `slug` or `topic` — they render
  lessons that already exist on our side, not text you supply. If you are
  bringing your own writing, `story` alone is the right call and `domain` should
  be left out.
</Warning>

## Collect the file

`status: "queued"` is acceptance, not completion. Rendering takes minutes. Poll:

```bash theme={null}
curl https://api.sigmora.org/v1/jobs/job_4k1... \
  -H "Authorization: Bearer $SIGMORA_API_KEY"
```

While it works:

```json theme={null}
{ "success": true, "job": { "id": "job_4k1...", "kind": "video.render", "status": "rendering", "phase": "narrating" } }
```

When it is done:

```json theme={null}
{
  "success": true,
  "job": {
    "id": "job_4k1...",
    "kind": "video.render",
    "status": "done",
    "artifacts": {
      "video": "https://.../story_week5_1a2b3c.mp4",
      "thumbnail": "https://.../story_week5_1a2b3c.jpg",
      "srt": "https://.../story_week5_1a2b3c.srt",
      "vtt": "https://.../story_week5_1a2b3c.vtt"
    }
  }
}
```

**`job.artifacts.video` is your video.** Fetch it directly — it points at the
origin holding the bytes, not back through this API, which is why a multi-gigabyte
render downloads at the origin's speed rather than a gateway's. Captions come
with it, as both SubRip and WebVTT.

Branch on `status`, never on `phase`: `status` is a closed set, `phase` is the
pipeline's own stage word and changes between releases. `done` and `failed` are
the terminal ones. Polling every 15–30 seconds is plenty.

## Cut shorts from it

Once the render is `done`, its job id is the input to the derivative pass:

```bash theme={null}
curl -X POST https://api.sigmora.org/v1/video/shorts \
  -H "Authorization: Bearer $SIGMORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobId": "job_4k1...",
    "projectId": "prj_9x2...",
    "platforms": ["tiktok", "shorts", "reels"],
    "maxPerPlatform": 3
  }'
```

Passing the same `projectId` files the render and its clips in one container you
can read back with a single `GET /v1/projects/{projectId}`.

## If the document is not written yet

`video.render` narrates prose you already have. When you have a subject rather
than a manuscript, write it first and render the result:

<CardGroup cols={2}>
  <Card title="Text capabilities" icon="pen" href="/text">
    `text.research` for a brief, `text.paraphrase` to restyle something long,
    `text.script` when you are working from a seed video.
  </Card>

  <Card title="Jobs & polling" icon="clock" href="/jobs">
    The full status table, and why `awaiting_review` never advances on its own.
  </Card>
</CardGroup>

## Retrying without paying twice

The `Idempotency-Key` above is not decoration on a 300-credit call. A retry with
the same key and the same body replays the stored response instead of starting a
second render, so a client timeout cannot become two charges. The same key with a
different body answers `409 conflict` rather than a wrong result.

A render that fails after being accepted is refunded against the key it was
billed under. You are never charged for a video that does not exist.
