---
title: Submit a download | Vid Kraken API
description: Queue a YouTube video for download.
url: https://vidkraken.com/docs/endpoint/submit-download
updated: '2026-10-24'
---

# Submit a download

For AI agents: docs index at https://vidkraken.com/docs/llms.txt; any page as Markdown by adding .md to its URL.

`POST https://vidkraken.com/api/v2/download`

Queue a YouTube video for download. Returns a `jobId` that you poll on
`GET /download/{jobId}` until status is `COMPLETED` (or `FAILED`).

Prefer push over polling? Pass a `webhookUrl` and we POST the final
status to it instead — the body is byte-for-byte the
`GET /download/{jobId}` response. See the `download.finished`
webhook below for headers, signature verification and retries.

The actually-delivered format is reported as `actualFormat` on the
completed status response.

**Cost:** bandwidth at the actual file size delivered (20 MB
minimum), charged when the background job completes —
submitting the job is free. There is no per-download fee.
Failures are not charged.

```bash
curl -X POST "https://vidkraken.com/api/v2/download" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://youtu.be/dQw4w9WgXcQ","format":"audio"}'
```

## Authorization

Bearer token. API key issued from the dashboard, sent as `Authorization: Bearer YOUR_API_KEY`. Get a key at https://vidkraken.com/login.

## Parameters

- `Idempotency-Key` (header, string)

  Optional. A unique string (e.g. a UUID), up to 255 visible ASCII
  characters. Retrying with the same key and body within 24 hours
  returns the original response instead of creating a second job.

  Example: `4f1c9a2e-8d3b-4c7a-9f0e-2b6d5a1c3e7f`.

## Request body

Download parameters.

- `url` (string, required)

  YouTube video URL. Supported patterns:

  - `https://youtube.com/watch?v=ID`

  - `https://youtu.be/ID`

  - `https://youtube.com/live/ID`

- `format` (`MediaFormat`)

  Target format. Defaults to `audio`.

  Default: `audio`.

- `startTime` (integer)

  Trim start in seconds (inclusive). Must be in `[0, duration)`. Omit to start from the beginning.

- `endTime` (integer)

  Trim end in seconds (exclusive). Must be in `(0, duration]` and greater than `startTime`. Omit to go to the end.

- `language` (string)

  Preferred audio track language as a BCP-47 tag (e.g. `en`,
  `en-US`, `th`). Use `POST /info` with `includeAudioTracks: true`
  to discover what's available for a given video —
  `audio[].language` lists the languages and `audio[].type`
  indicates `original` vs. `dubbed`.

  **Fallback chain:** if the requested language isn't available
  on the video, the download falls back to (1) any track whose
  language tag starts with the requested value (e.g. `en`
  matches `en-US`), then to (2) the video's original/default
  audio. Omit this field to always use the original audio.

- `webhookUrl` (string (uri))

  Optional. A public `https://` (or `http://`) URL we POST the job's
  final status to once it reaches `COMPLETED` or `FAILED`. The body is
  identical to the `GET /download/{jobId}` response, so one handler
  can serve both. Requests are signed with your API key and retried
  with exponential backoff for up to 10 attempts (~4 hours); see the
  `download.finished` webhook for details.

  Loopback, private-network and single-label hostnames (e.g.
  `localhost`, `10.x`, `192.168.x`, `intranet`) are rejected with
  `400`. Redirects are not followed — respond `2xx` directly.

### Examples

Download the audio track:

```json
{
  "url": "https://youtu.be/dQw4w9WgXcQ",
  "format": "audio"
}
```

Download the audio track as an MP3:

```json
{
  "url": "https://youtu.be/dQw4w9WgXcQ",
  "format": "audio-mp3"
}
```

Trim a 60-second 1080p clip:

```json
{
  "url": "https://youtu.be/dQw4w9WgXcQ",
  "format": "1080",
  "startTime": 30,
  "endTime": 90
}
```

Force the English-dubbed audio track:

```json
{
  "url": "https://www.youtube.com/watch?v=omW5PrTMz-c",
  "format": "1080",
  "language": "en-US"
}
```

Get notified by webhook instead of polling:

```json
{
  "url": "https://youtu.be/dQw4w9WgXcQ",
  "format": "720",
  "webhookUrl": "https://example.com/hooks/vidkraken"
}
```

## Responses

### 200

Job enqueued.

Headers:

- `Idempotent-Replayed`: `true` when this response is a replay of an earlier request with the same Idempotency-Key.
- `RateLimit-Policy`: The pending-downloads cap as an IETF RateLimit policy (draft-ietf-httpapi-ratelimit-headers): `q` downloads may be queued or in progress at once.
- `RateLimit`: How many more downloads can be queued right now (`r`).

- `jobId` (string, required)

  Unique ID for the download job. Use this to poll the status endpoint.

- `title` (string, required)

  Video title as reported by YouTube.

- `duration` (integer, required)

  Full video duration in seconds.

- `status` (string, required)

  One of: `IN_QUEUE`.

- `message` (string, required)

  Human-readable next-step hint.

```json
{
  "jobId": "69cfc45e8a3b4d2f9c1e0a7b",
  "title": "Rick Astley - Never Gonna Give You Up",
  "duration": 213,
  "status": "IN_QUEUE",
  "message": "Download job enqueued. Poll GET /api/v2/download/{jobId} for status."
}
```

### 400

Invalid input — missing or malformed field.

Body: `ApiError`.

missing-url:

```json
{
  "error": "Missing required field: url",
  "code": "INVALID_REQUEST"
}
```

bad-format:

```json
{
  "error": "Invalid format. Must be one of: 1080, 720, 480, 360, audio",
  "code": "INVALID_REQUEST"
}
```

live-in-progress:

```json
{
  "error": "Live on-going videos are not supported. Try again after the live stream ends.",
  "code": "INVALID_REQUEST"
}
```

too-long:

```json
{
  "error": "This video is too long to download in audio-aac (21,812 hours, about 1,265 GB; the limit is 100 GB per file).",
  "code": "INVALID_REQUEST"
}
```

### 401

Missing or invalid API key.

Body: `ApiError`.

missing:

```json
{
  "error": "Missing API key. Provide it as 'Bearer YOUR_API_KEY'",
  "code": "UNAUTHORIZED"
}
```

invalid:

```json
{
  "error": "Invalid API key",
  "code": "UNAUTHORIZED"
}
```

### 403

Usage limit reached for the current plan — the bandwidth
allowance (plus any overage allowance) or a request quota is
exhausted. Upgrade or wait for the next billing cycle. The
error string is `Credit limit reached` for backwards
compatibility.

Body: `ApiError`.

```json
{
  "error": "Credit limit reached",
  "code": "LIMIT_REACHED"
}
```

### 404

No such video — it is private, deleted, or the id is wrong.

Body: `ApiError`.

```json
{
  "error": "Video not found. Check the URL — the video may be private, deleted, or the id may be wrong.",
  "code": "VIDEO_NOT_FOUND"
}
```

### 409

A request with this Idempotency-Key is still being processed. Retry after `Retry-After` seconds.

Headers:

- `Retry-After`: Seconds to wait before retrying.

Body: `ApiError`.

```json
{
  "error": "A request with this Idempotency-Key is still being processed. Retry shortly.",
  "code": "IDEMPOTENCY_CONFLICT"
}
```

### 422

This Idempotency-Key was already used with a different request body.

Body: `ApiError`.

```json
{
  "error": "This Idempotency-Key was already used with a different request body. Use a new key.",
  "code": "IDEMPOTENCY_KEY_REUSED"
}
```

### 429

You already have 100 downloads queued or in progress. Wait for
some to reach `COMPLETED` / `FAILED` (poll or use a `webhookUrl`),
then resubmit. Not charged.

Headers:

- `X-Pending-Downloads`: Your downloads currently queued or in progress.
- `X-Pending-Downloads-Limit`: Maximum allowed queued or in progress at once.
- `Retry-After`: Seconds to wait before submitting again.
- `RateLimit-Policy`: The pending-downloads cap as an IETF RateLimit policy (draft-ietf-httpapi-ratelimit-headers): `q` downloads may be queued or in progress at once.
- `RateLimit`: How many more downloads can be queued right now (`r`).

Body: `ApiError`.

```json
{
  "error": "Too many pending downloads (100). Wait for some of your jobs to finish before submitting more (limit: 100 queued or in progress).",
  "code": "TOO_MANY_PENDING"
}
```

### 500

Unexpected server error.

Body: `ApiError`.

```json
{
  "error": "Failed to enqueue download job",
  "code": "INTERNAL_ERROR"
}
```

### 503

Temporarily unable to accept the job. Not charged; retry after a short backoff.

Body: `ApiError`.

```json
{
  "error": "The download could not be queued. Please try again in a minute.",
  "code": "UNAVAILABLE"
}
```

## Schemas

### `MediaFormat`

Target media format. Numeric values are vertical resolutions in
pixels, delivered as MP4.

`audio` delivers the best audio stream YouTube serves, in whatever
container YouTube gives it — usually M4A (AAC) or WebM (Opus), at
roughly 130–160 kbps. Nothing is re-encoded, so it is the fastest
option and the highest quality available. Use this unless your
pipeline needs one specific container. Note that YouTube does not
serve higher audio bitrates for regular videos, so no option here
produces better audio than this one.

`audio-aac` prefers YouTube's AAC stream (M4A, ~128 kbps) over Opus —
the most widely compatible audio codec, and useful when a pipeline was
calibrated on AAC. Nothing is re-encoded. If YouTube offers no AAC
stream for a video, the best available audio is delivered instead and
`actualFormat` reports `audio` rather than `audio-aac`, so the
substitution is visible.

`audio-mp3` always delivers an MP3, encoded at 320 kbps CBR — the
highest rate the MP3 format defines — using LAME's best-quality
analysis. The source's sample rate and channel layout are preserved
rather than forced: 48 kHz audio stays 48 kHz with no resampling, and
a mono source stays mono.

Type: string.

One of: `1080`, `720`, `480`, `360`, `audio`, `audio-aac`, `audio-mp3`.

### `ApiError`

- `error` (string, required)

  Human-readable error message.

- `code` (string, required)

  Machine-readable error code; branch on this rather than on
  `error`. New codes may be added, so treat an unknown one by
  its HTTP status. See the table under Errors in the API
  description for what each one means.

  One of: `INVALID_REQUEST`, `UNAUTHORIZED`, `LIMIT_REACHED`, `NOT_FOUND`, `VIDEO_NOT_FOUND`, `IDEMPOTENCY_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`, `TOO_MANY_PENDING`, `INTERNAL_ERROR`, `UNAVAILABLE`.

- `details` (string)

  Optional extra context — e.g. yt-dlp's error message on `500`s.

Full API reference, including the download.finished webhook: https://vidkraken.com/docs.md · OpenAPI: https://vidkraken.com/openapi.json
