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

# Download every output

> List everything a Rask API project produced, from dubbed video and audio to subtitles and lip-synced video, and download each output.

[listProjectOutputs](https://docs.api.rask.ai/operations/listProjectOutputs.md) is the one list of everything a project can give you. [getProject](https://docs.api.rask.ai/operations/getProject.md) embeds the same list in `outputs`.

```typescript TypeScript theme={null}
const { data: outputList } = await rask.exports.listProjectOutputs({ path: { project_id: projectId } });
```

```python Python theme={null}
output_list = client.exports.list_project_outputs(project_id)
```

```go Go theme={null}
outputList, err := client.Exports.ListProjectOutputs(ctx, projectID)
if err != nil {
	return err
}
```

```java Java theme={null}
OutputList outputList = rask.exports().listProjectOutputs(projectId).data();
```

```php PHP theme={null}
$outputList = $rask->exports->listProjectOutputs($projectId);
```

```bash cURL theme={null}
curl "https://api.rask.ai/v3/projects/$PROJECT_ID/outputs" \
  -H "Authorization: Bearer $RASK_API_KEY"
```

```json theme={null}
{
  "object": "output",
  "kind": "dubbed_video",
  "format": "mp4",
  "language": "es-es",
  "available": true,
  "unavailable_reason": null,
  "outdated": false,
  "version": { "number": 7, "job_id": "91c3e7b2-4f8a-4d6e-9b1c-7a3e5f2d8c60", "produced_at": "2026-11-03T09:22:31Z" },
  "url": "https://...",
  "expires_at": "2026-11-03T11:15:02Z",
  "subtitles_request": null,
  "filename": "Q3 webinar teaser_es-es_dubbed_video.mp4"
}
```

An output comes in one of three shapes, and `available` is true for the first two:

1. **A file:** `url` is an absolute presigned link, valid 1 hour after the read. It needs no key. Read the outputs again for a fresh link. `filename` is a suggested name, and the link downloads the file under it.
2. **A rendered subtitle variant:** `url` is null and `subtitles_request` holds the parameters to render it.
3. **Not available:** `url` is null and `unavailable_reason` says why.

## What each kind is

The last column names the plan features an output needs. Without them, it reports `not_in_plan`.

| `kind` | What it is | Plan features |
| - | - | - |
| `source` | The original media | |
| `cover_image` | The cover frame | |
| `dubbed_video` | The dubbed video | |
| `dubbed_audio` | Dubbed audio with background | `audio_download` |
| `voice_track` | The voice-only track | `audio_download` |
| `dubbed_subtitles` | The subtitles snapshot at the last dub (srt, vtt) | `subtitle_download` |
| `source_subtitles` | Source subtitles from the current transcript, one cue per segment | `subtitle_download` |
| `source_subtitles_speakers` | The same with `[SPEAKER_NN]:` labels | `subtitle_download` |
| `target_subtitles` | Target subtitles from the current transcript, one cue per segment | `subtitle_download` |
| `target_subtitles_speakers` | The same with `[SPEAKER_NN]:` labels | `subtitle_download` |
| `burned_subtitles_video` | The dubbed video with burned-in subtitles | `burned_subtitles` |
| `lipsync_video` | The lip-synced video | `lipsync` and `lipsync_download` |
| `lipsync_burned_subtitles_video` | The lip-synced video with burned-in subtitles | `burned_subtitles` |
| `onscreen_video` | The video with translated on-screen text | `onscreen` |
| `localized_video` | The final video of a localization run | `onscreen` when on-screen is the run's last stage, else `lipsync` and `lipsync_download` |

Each subtitle kind is listed twice, once per `format` (`srt` and `vtt`). An audio project lists only `source`, `dubbed_audio`,
`voice_track` and the subtitle kinds.

## Why an output is not available

| `unavailable_reason` | Meaning | What to do |
| - | - | - |
| `not_produced` | Not produced yet, or (`lipsync_video`) cleared by a later dub | Run the work that produces it |
| `in_progress` | Being produced | Wait for the job |
| `not_in_plan` | The plan does not allow the download | Tell the user; see [`GET /v3/plan`](https://docs.api.rask.ai/operations/getPlan) |
| `dub_payment_required` | Subtitles of a project whose text is still closed | Wait for the auto-dub or call [createDub](https://docs.api.rask.ai/operations/createDub.md). See [Paid text](https://docs.api.rask.ai/v3/concepts/paid-text.md) |
| `not_generated` | Never requested for this project | Request it, for example with [createExport](https://docs.api.rask.ai/operations/createExport.md) |
| `restoring` | A restore of the project's archived files is running | Wait for the restore, or call [createFileRestore](https://docs.api.rask.ai/operations/createFileRestore.md) for a job to wait on |
| `temporarily_unavailable` | The file could not be read or linked just now | Read the outputs again later |

## Is it still current?

`outdated` says whether the output still matches the project. `version` names the work that produced it: its `job_id`, the transcript `number` and `produced_at`. `job_id` is null for work started in the web app or API v2, and `number` is set only on the dub outputs. The source, the cover and the rendered subtitle variants have no `version`.

* **Dubbed video, audio, voice track and subtitles snapshot:** outdated when a segment with target text has `dub_state` `outdated` or `not_dubbed`, or the transcript or a voice changed since the last dub.
* **Burned-in subtitle videos:** outdated after a later dub.
* **On-screen video:** outdated after a later dub, or after a later lip-sync when it was rendered over the lip-synced video.
* **Localized video:** the dub rule, or its final stage's result is outdated, or a newer run succeeded.
* **Lip-synced video:** always false, because a later dub removes it. The output then reports `not_produced`, and the job's `result.video` keeps the file.
* **Rendered subtitle variants:** always current.

## Subtitles

`subtitles_request` holds the parameters for [getProjectSubtitles](https://docs.api.rask.ai/operations/getProjectSubtitles.md), which renders from the current transcript and stores nothing. The file comes back inside
the answer, in `content`.

```bash theme={null}
curl "https://api.rask.ai/v3/projects/$PROJECT_ID/subtitles?language=target&format=vtt&speaker_labels=false&cue=segment" \
  -H "Authorization: Bearer $RASK_API_KEY" \
  | jq -r '.content' > target.vtt
```

| Parameter | Values |
| - | - |
| `language` | `target` (default) or `source` |
| `format` | `srt` (default) or `vtt` |
| `speaker_labels` | `false` (default), or `true` to prefix cues with `[SPEAKER_NN]:` |
| `cue` | `segment` (default) writes one cue per segment. `line` splits long segments into lines, as the `dubbed_subtitles` snapshot does |

Target subtitles leave out segments that have no translation. Segments with unusable times are left out too, and listed in
`skipped_segment_ids`. Before the project has a transcript, the request answers 409 [`project_not_ready`](https://docs.api.rask.ai/errors/project_not_ready).

## Burned-in subtitles

[createExport](https://docs.api.rask.ai/operations/createExport.md) burns the target subtitles of the last dub (the `dubbed_subtitles` snapshot) into the video, free of charge. The file appears in the outputs when the `export` job succeeds: `burned_subtitles_video` for
`source_video: dubbed`, `lipsync_burned_subtitles_video` for `source_video: lipsync`. Without that video, the request answers 409
[`project_not_dubbed`](https://docs.api.rask.ai/errors/project_not_dubbed). One export per source video runs at a time: another
answers 409 [`export_in_progress`](https://docs.api.rask.ai/errors/export_in_progress), whose `job_id` names the running export when the API started it.

```typescript TypeScript theme={null}
const { data: job } = await rask.exports.createExport({
  path: { project_id: projectId },
  body: { type: 'burned_subtitles', source_video: 'dubbed' },
});
```

```python Python theme={null}
job = client.exports.create_export(project_id, body={"type": "burned_subtitles", "source_video": "dubbed"})
```

```go Go theme={null}
var body api.CreateExportRequest
if err := body.FromBurnedSubtitlesExportInput(api.BurnedSubtitlesExportInput{
	Type:        api.BurnedSubtitlesExportInputTypeBurnedSubtitles,
	SourceVideo: api.ExportSourceVideoDubbed,
}); err != nil {
	return err
}
job, err := client.Exports.CreateExport(ctx, projectID, body)
if err != nil {
	return err
}
```

```java Java theme={null}
Job job = rask.exports().createExport(projectId, BurnedSubtitlesExportInput.builder()
        .sourceVideo(ExportSourceVideo.DUBBED)
        .build()).data();
```

```php PHP theme={null}
$job = $rask->exports->createExport($projectId, ['type' => 'burned_subtitles', 'source_video' => 'dubbed']);
```

```bash cURL theme={null}
curl -X POST "https://api.rask.ai/v3/projects/$PROJECT_ID/exports" \
  -H "Authorization: Bearer $RASK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"type": "burned_subtitles", "source_video": "dubbed"}'
```

## Clips of single segments

Two places hold a segment's audio. `Segment.audio` is the latest clip, replaced by each new take. A `SegmentClip` in a
[`segment_generation` job's result](https://docs.api.rask.ai/v3/recipes/segment-generation) is a stable take that nothing overwrites. Both links need
[`audio_download`](https://docs.api.rask.ai/v3/concepts/capabilities#plan-features) in the plan; without it they are null.

## Archived files

An archived file still gets a `url`, but the download answers 403. While a restore runs, every stored file of the project reports
`restoring`. [createFileRestore](https://docs.api.rask.ai/operations/createFileRestore.md) starts a free `file_restore` job, which typically takes hours. If nothing is archived, the job
succeeds within moments with `files_restored: 0`. While one restore runs, another answers 409 [`restore_in_progress`](https://docs.api.rask.ai/errors/restore_in_progress) with its `job_id`.

```typescript TypeScript theme={null}
const { data: job } = await rask.exports.createFileRestore({ path: { project_id: projectId } });
```

```python Python theme={null}
job = client.exports.create_file_restore(project_id)
```

```go Go theme={null}
job, err := client.Exports.CreateFileRestore(ctx, projectID)
if err != nil {
	return err
}
```

```java Java theme={null}
Job job = rask.exports().createFileRestore(projectId).data();
```

```php PHP theme={null}
$job = $rask->exports->createFileRestore($projectId);
```

```bash cURL theme={null}
curl -X POST "https://api.rask.ai/v3/projects/$PROJECT_ID/file-restores" \
  -H "Authorization: Bearer $RASK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

## The whole program

```python download_outputs.py theme={null}
"""Download everything a project has: python download_outputs.py PROJECT_ID"""
import pathlib
import sys

from rask_client import call, download

project_id = sys.argv[1]
target = pathlib.Path(project_id)
target.mkdir(exist_ok=True)

for output in call("GET", f"/projects/{project_id}/outputs")["data"]:
    if not output["available"]:
        print(f"{output['kind']}: {output['unavailable_reason']}")
    elif output["url"]:
        download(output["url"], target / output["filename"])
    else:
        rendered = call("GET", f"/projects/{project_id}/subtitles", params=output["subtitles_request"])
        (target / output["filename"]).write_text(rendered["content"], encoding="utf-8")
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.