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

# Get Job Status

> Get the status of a download job

## Overview

Retrieves the current status of a download job. When completed, includes a presigned S3 URL for downloading the file.

## Header Parameters

<ParamField header="x-api-key" type="string" required>
  Your API key for authentication
</ParamField>

## Path Parameters

<ParamField path="id" type="string" required>
  The job UUID returned from `POST /jobs`
</ParamField>

## Response

<ResponseField name="id" type="string">
  Job UUID
</ResponseField>

<ResponseField name="url" type="string">
  Original source URL
</ResponseField>

<ResponseField name="status" type="string">
  Job status: `Pending`, `Processing`, `Completed`, `Failed`, `Warning`, or `Skipped`
</ResponseField>

<ResponseField name="s3_url" type="string">
  Presigned download URL (only when `Completed`)
</ResponseField>

<ResponseField name="subtitle_url" type="string">
  Presigned URL for subtitles (if available)
</ResponseField>

<ResponseField name="thumbnail_url" type="string">
  Presigned URL for the thumbnail (only when `download_thumbnail` was enabled). Omitted otherwise.
</ResponseField>

<ResponseField name="error" type="string">
  Error message (when `Failed`, `Warning`, or `Skipped`)
</ResponseField>

<ResponseField name="error_type" type="string">
  Error classification (when `Failed`, `Warning`, or `Skipped`): `error` for technical failures (rate limits, bot detection, connection issues), `warning` for content issues (private video, members-only, geo-blocked, audio-only Spotify episodes)
</ResponseField>

<ResponseField name="step" type="string">
  Current processing step: `Queued`, `Downloading`, `Muxing`, `Uploading`, `Finished`
</ResponseField>

<ResponseField name="title" type="string">
  Video title (only present for completed jobs fetched from database)
</ResponseField>

<ResponseField name="description" type="string">
  Video/episode description from the source platform (only present for completed jobs)
</ResponseField>

<ResponseField name="release_date" type="string">
  Release or upload date. Spotify: exact publish date. YouTube: upload date (midnight UTC). ISO 8601 format.
</ResponseField>

<ResponseField name="folder" type="string">
  S3 folder prefix if provided at job creation
</ResponseField>

<ResponseField name="batch_id" type="string">
  Batch UUID if this job is part of a batch (Spotify show or YouTube playlist)
</ResponseField>

<ResponseField name="download_speed_mbps" type="number">
  Download speed in MB/s (only present for completed jobs)
</ResponseField>

<ResponseField name="upload_speed_mbps" type="number">
  Upload speed in MB/s (only present for completed jobs)
</ResponseField>

<ResponseField name="extract_duration_ms" type="integer">
  Metadata extraction duration in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="download_duration_ms" type="integer">
  Download stage duration in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="mux_duration_ms" type="integer">
  Mux (FFmpeg) stage duration in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="upload_duration_ms" type="integer">
  Upload stage duration in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="total_duration_ms" type="integer">
  Total pipeline duration from pop to completion in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="precheck_duration_ms" type="integer">
  YouTube API pre-check duration in milliseconds (only present when pre-check was performed)
</ResponseField>

<ResponseField name="io_wait_ms" type="integer">
  Time spent waiting for IO semaphore in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="cpu_wait_ms" type="integer">
  Time spent waiting for CPU semaphore in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="upload_wait_ms" type="integer">
  Time spent waiting for upload semaphore in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="subtitle_duration_ms" type="integer">
  Subtitle download duration in milliseconds (only present for completed jobs with subtitles)
</ResponseField>

<ResponseField name="file_move_ms" type="integer">
  File move duration in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="file_size" type="integer">
  File size in bytes (only present for completed jobs)
</ResponseField>

<ResponseField name="native_video_codec" type="string">
  Video codec of the source stream (e.g., `"avc1"`, `"vp9"`, `"av01"`). Only present for completed jobs.
</ResponseField>

<ResponseField name="native_audio_codec" type="string">
  Audio codec of the source stream (e.g., `"mp4a"`, `"opus"`). Only present for completed jobs.
</ResponseField>

<ResponseField name="download_strategy" type="string">
  Download strategy used: `native`, `ytdlp`, `cascade`. Only present for completed jobs.
</ResponseField>

<ResponseField name="cascade_total_attempts" type="integer">
  Total download attempts across all strategies (only present for completed jobs)
</ResponseField>

<ResponseField name="download_retries" type="integer">
  Number of download retries attempted (only present for completed jobs)
</ResponseField>

<ResponseField name="upload_retries" type="integer">
  Number of upload retries attempted (only present for completed jobs)
</ResponseField>

<ResponseField name="queue_wait_ms" type="integer">
  Time spent waiting in queue in milliseconds (only present for completed jobs)
</ResponseField>

<ResponseField name="requested_quality" type="string">
  Quality requested by the user (only present for completed jobs)
</ResponseField>

<ResponseField name="actual_quality" type="string">
  Actual quality of the downloaded video (only present for completed jobs)
</ResponseField>

<ResponseField name="webhook_status" type="string">
  Webhook delivery status (only present when webhook\_url was set)
</ResponseField>

<ResponseField name="created_at" type="integer">
  Creation timestamp in milliseconds since epoch
</ResponseField>

<ResponseField name="finished_at" type="integer">
  Completion timestamp in milliseconds since epoch (only present for completed jobs)
</ResponseField>

## Examples

<CodeGroup>
  ```bash Request theme={null}
  curl -X GET "https://api.tornadoapi.io/jobs/550e8400-e29b-41d4-a716-446655440000" \
    -H "x-api-key: sk_your_api_key"
  ```

  ```json Pending theme={null}
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
    "status": "Pending",
    "s3_url": null,
    "subtitle_url": null,
    "error": null,
    "error_type": null,
    "step": "Queued"
  }
  ```

  ```json Processing theme={null}
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
    "status": "Processing",
    "s3_url": null,
    "subtitle_url": null,
    "error": null,
    "error_type": null,
    "step": "Downloading"
  }
  ```

  ```json Completed theme={null}
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
    "status": "Completed",
    "s3_url": "https://cdn.example.com/videos/video.mp4?X-Amz-Algorithm=...",
    "subtitle_url": null,
    "error": null,
    "step": "Finished",
    "title": "Rick Astley - Never Gonna Give You Up",
    "description": "The official video for Never Gonna Give You Up...",
    "release_date": "2009-10-25T00:00:00Z",
    "folder": "music-videos",
    "download_speed_mbps": 45.2,
    "upload_speed_mbps": 120.5,
    "extract_duration_ms": 800,
    "download_duration_ms": 3200,
    "mux_duration_ms": 1500,
    "upload_duration_ms": 2100,
    "total_duration_ms": 8050,
    "file_size": 52428800,
    "native_video_codec": "avc1",
    "native_audio_codec": "mp4a",
    "download_strategy": "native",
    "cascade_total_attempts": 1,
    "download_retries": 1,
    "upload_retries": 0,
    "queue_wait_ms": 450,
    "requested_quality": "best",
    "actual_quality": "1080p",
    "webhook_status": "delivered",
    "created_at": 1705507200000,
    "finished_at": 1705507260000
  }
  ```

  ```json Failed theme={null}
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
    "status": "Failed",
    "s3_url": null,
    "subtitle_url": null,
    "error": "Video unavailable",
    "error_type": "warning",
    "step": "Downloading"
  }
  ```

  ```json Skipped theme={null}
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://open.spotify.com/episode/329nSfeZ5vRoJooKsZh9kH",
    "status": "Skipped",
    "s3_url": null,
    "subtitle_url": null,
    "error": "Audio-only Spotify episode (DRM protected) - skipped. This episode contains only audio which is Widevine-encrypted and cannot be downloaded.",
    "error_type": "warning",
    "step": "Downloading"
  }
  ```
</CodeGroup>

## Status Values

| Status       | Description                                                                                                  |
| ------------ | ------------------------------------------------------------------------------------------------------------ |
| `Pending`    | Job is in queue, waiting to be processed                                                                     |
| `Processing` | Job is actively being downloaded/processed                                                                   |
| `Completed`  | Job finished successfully                                                                                    |
| `Failed`     | Job encountered a technical error                                                                            |
| `Warning`    | Job failed due to a content issue (private video, members-only, geo-blocked, unavailable)                    |
| `Skipped`    | Job was skipped because the content cannot be processed (e.g. audio-only Spotify episodes with Widevine DRM) |

## Processing Steps

| Step          | Description                     |
| ------------- | ------------------------------- |
| `Queued`      | Waiting in queue                |
| `Downloading` | Downloading video/audio streams |
| `Muxing`      | Combining streams with FFmpeg   |
| `Uploading`   | Uploading to S3                 |
| `Finished`    | Complete                        |

## Polling Example

```python theme={null}
import requests
import time

def wait_for_job(job_id, api_key, timeout=600):
    start = time.time()

    while time.time() - start < timeout:
        response = requests.get(
            f"https://api.tornadoapi.io/jobs/{job_id}",
            headers={"x-api-key": api_key}
        )
        data = response.json()

        print(f"Status: {data['status']} - {data.get('step', 'N/A')}")

        if data["status"] == "Completed":
            return data["s3_url"]
        elif data["status"] == "Failed":
            raise Exception(f"Job failed: {data['error']}")
        elif data["status"] == "Skipped":
            print(f"Job skipped: {data['error']}")
            return None

        time.sleep(3)

    raise Exception("Timeout waiting for job")
```

## Success Response

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
    "status": "Completed",
    "s3_url": "https://cdn.example.com/videos/video.mp4?X-Amz-Algorithm=...",
    "subtitle_url": null,
    "error": null,
    "step": "Finished",
    "title": "Rick Astley - Never Gonna Give You Up",
    "description": "The official video for Never Gonna Give You Up...",
    "release_date": "2009-10-25T00:00:00Z",
    "download_speed_mbps": 45.2,
    "upload_speed_mbps": 120.5,
    "download_duration_ms": 3200,
    "mux_duration_ms": 1500,
    "upload_duration_ms": 2100,
    "total_duration_ms": 8050,
    "file_size": 52428800,
    "native_video_codec": "avc1",
    "native_audio_codec": "mp4a",
    "download_strategy": "native",
    "download_retries": 1,
    "upload_retries": 0,
    "queue_wait_ms": 450,
    "requested_quality": "best",
    "actual_quality": "1080p",
    "created_at": 1705507200000,
    "finished_at": 1705507260000
  }
  ```
</ResponseExample>

## Error Responses

<ResponseExample>
  ```json 404 Not Found theme={null}
  null
  ```
</ResponseExample>

<Note>
  The presigned `s3_url` and `subtitle_url` are valid for 24 hours. Download the files before they expire.
</Note>

<Note>
  Performance and metadata fields (`title`, `description`, `release_date`, `folder`, `batch_id`, `download_speed_mbps`, `upload_speed_mbps`, `extract_duration_ms`, `download_duration_ms`, `mux_duration_ms`, `upload_duration_ms`, `total_duration_ms`, `precheck_duration_ms`, `io_wait_ms`, `cpu_wait_ms`, `upload_wait_ms`, `subtitle_duration_ms`, `file_move_ms`, `file_size`, `native_video_codec`, `native_audio_codec`, `download_strategy`, `cascade_total_attempts`, `download_retries`, `upload_retries`, `queue_wait_ms`, `requested_quality`, `actual_quality`, `webhook_status`, `finished_at`) are only present for completed/failed jobs that have been persisted to the database. Active jobs (Pending/Processing) return only the base fields (`id`, `url`, `status`, `s3_url`, `subtitle_url`, `error`, `error_type`, `step`, `created_at`). Fields with no value are omitted from the response.
</Note>

<Note>
  Completed jobs also echo back the original request parameters (`format`, `video_codec`, `audio_codec`, `audio_bitrate`, `video_quality`, `filename`, `audio_only`, `download_subtitles`, `download_thumbnail`, `quality_preset`, `max_resolution`, `clip_start`, `clip_end`, `live_recording`, `live_from_start`, `max_duration`, `wait_for_video`, `enable_progress_webhook`). These are omitted when null.
</Note>
