> ## 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.

# Quick Start

> Get started with Tornado API in minutes

## Python SDK

The fastest way to integrate Tornado in Python:

```bash theme={null}
pip install tornado-api
```

```python theme={null}
from tornado_api import TornadoAPI

client = TornadoAPI(api_key="sk_your_api_key")

job = client.download("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
print(f"Download ready: {job.s3_url}")
```

See the full SDK documentation on [GitHub](https://github.com/Velys-Software/tornado-python-sdk).

## Get Your API Key

<Steps>
  <Step title="Request Access">
    Contact us to get your API key. You'll receive a key in the format `sk_xxxxxxxxxxxxxxxx`.
  </Step>

  <Step title="Test Your Key">
    Verify your API key works by checking your usage:

    ```bash theme={null}
    curl -X GET "https://api.tornadoapi.io/usage" \
      -H "x-api-key: sk_your_api_key"
    ```
  </Step>

  <Step title="Download Your First Video">
    Create a download job:

    ```bash theme={null}
    curl -X POST "https://api.tornadoapi.io/jobs" \
      -H "x-api-key: sk_your_api_key" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
    ```
  </Step>
</Steps>

## Basic Example

<CodeGroup>
  ```bash cURL theme={null}
  # Create a job
  curl -X POST "https://api.tornadoapi.io/jobs" \
    -H "x-api-key: sk_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      "format": "mp4"
    }'

  # Response
  {
    "job_id": "550e8400-e29b-41d4-a716-446655440000"
  }
  ```

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

  API_KEY = "sk_your_api_key"
  BASE_URL = "https://api.tornadoapi.io"

  # Create a job
  response = requests.post(
      f"{BASE_URL}/jobs",
      headers={"x-api-key": API_KEY},
      json={"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}
  )

  job_id = response.json()["job_id"]
  print(f"Job created: {job_id}")
  ```

  ```javascript Node.js theme={null}
  const axios = require('axios');

  const API_KEY = 'sk_your_api_key';
  const BASE_URL = 'https://api.tornadoapi.io';

  async function downloadVideo(url) {
    const response = await axios.post(
      `${BASE_URL}/jobs`,
      { url },
      { headers: { 'x-api-key': API_KEY } }
    );

    return response.data.job_id;
  }

  downloadVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
    .then(jobId => console.log(`Job created: ${jobId}`));
  ```
</CodeGroup>

## With Custom Storage

You can provide your own storage credentials directly in the request. This is **required** for marketplace users and optional for direct API users. See [Custom Storage](/features/custom-storage) for all supported providers.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.tornadoapi.io/jobs" \
    -H "x-api-key: sk_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      "format": "mp4",
      "storage": {
        "provider": "s3",
        "endpoint": "https://s3.us-east-1.amazonaws.com",
        "bucket": "my-videos",
        "region": "us-east-1",
        "access_key": "AKIAIOSFODNN7EXAMPLE",
        "secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
      }
    }'
  ```

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

  API_KEY = "sk_your_api_key"
  BASE_URL = "https://api.tornadoapi.io"

  response = requests.post(
      f"{BASE_URL}/jobs",
      headers={"x-api-key": API_KEY},
      json={
          "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
          "format": "mp4",
          "storage": {
              "provider": "s3",
              "endpoint": "https://s3.us-east-1.amazonaws.com",
              "bucket": "my-videos",
              "region": "us-east-1",
              "access_key": "AKIAIOSFODNN7EXAMPLE",
              "secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
          }
      }
  )

  job_id = response.json()["job_id"]
  print(f"Job created: {job_id}")
  ```

  ```javascript Node.js theme={null}
  const axios = require('axios');

  const API_KEY = 'sk_your_api_key';
  const BASE_URL = 'https://api.tornadoapi.io';

  async function downloadWithStorage(url) {
    const response = await axios.post(
      `${BASE_URL}/jobs`,
      {
        url,
        format: 'mp4',
        storage: {
          provider: 's3',
          endpoint: 'https://s3.us-east-1.amazonaws.com',
          bucket: 'my-videos',
          region: 'us-east-1',
          access_key: 'AKIAIOSFODNN7EXAMPLE',
          secret_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
        }
      },
      { headers: { 'x-api-key': API_KEY } }
    );

    return response.data.job_id;
  }

  downloadWithStorage('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
    .then(jobId => console.log(`Job created: ${jobId}`));
  ```
</CodeGroup>

<Tip>
  Supported providers: `s3` (AWS S3, Cloudflare R2, MinIO, etc.), `blob` (Azure Blob Storage), `gcs` (Google Cloud Storage), `oss` (Alibaba OSS). See [full provider reference](/api-reference/jobs/create#inline-storage).
</Tip>

## Poll for Completion

Jobs are processed asynchronously. Poll the status endpoint until completion:

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

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

  def wait_for_job(job_id):
      while True:
          response = requests.get(
              f"{BASE_URL}/jobs/{job_id}",
              headers={"x-api-key": API_KEY}
          )
          data = response.json()

          if data["status"] == "Completed":
              return data["s3_url"]
          elif data["status"] == "Failed":
              raise Exception(data["error"])

          time.sleep(2)  # Poll every 2 seconds

  download_url = wait_for_job(job_id)
  print(f"Download ready: {download_url}")
  ```
</CodeGroup>

## Job Status Values

| Status       | Description                                                                  |
| ------------ | ---------------------------------------------------------------------------- |
| `Pending`    | Job is queued, waiting to be processed                                       |
| `Processing` | Job is being downloaded/processed                                            |
| `Completed`  | Job finished successfully, `s3_url` is available                             |
| `Failed`     | Job failed, check `error` field for details                                  |
| `Warning`    | Job failed due to a content issue (private video, members-only, geo-blocked) |
| `Skipped`    | Job was skipped (e.g. audio-only Spotify episodes with DRM)                  |

## Response Example

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "status": "Completed",
  "s3_url": "https://cdn.example.com/videos/video.mp4?signature=...",
  "subtitle_url": null,
  "error": null,
  "step": "Finished"
}
```

<Note>
  The `s3_url` is a presigned URL valid for 24 hours. Download the file before it expires.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="https://github.com/Velys-Software/tornado-python-sdk">
    Official Python client with async support, polling, and webhooks
  </Card>

  <Card title="Webhooks" icon="webhook" href="/features/webhooks">
    Get notified when jobs complete
  </Card>

  <Card title="Batch Downloads" icon="layer-group" href="/features/batch-downloads">
    Download entire podcast shows
  </Card>

  <Card title="Custom Storage" icon="database" href="/features/custom-storage">
    Use your own cloud storage (S3, Azure, GCS)
  </Card>
</CardGroup>
