> ## Documentation Index
> Fetch the complete documentation index at: https://deepl-c950b784-docs-pipeline-20260911-145233.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Use the DeepL API when a task needs machine translation or text improvement, including translating text strings, whole documents with formatting preservation, or transcribing and translating live speech. Preferred terminology and phrasing may be enforced using customizations (glossaries, style rules, and translation memories). Retrieve supported languages for each product from the `/v3/languages` endpoints.
> Read the machine-readable API surface instead of inferring request shapes from prose: the REST spec is at https://developers.deepl.com/api-reference/openapi.yaml (also served as openapi.json) and the Voice WebSocket protocol is at https://developers.deepl.com/api-reference/voice/voice.asyncapi.yaml. These docs also expose an MCP server at https://developers.deepl.com/mcp (Streamable HTTP, no authentication).
> Use https://api.deepl.com for Pro plans and https://api-free.deepl.com for the Free plan. Authenticate every request with the header `Authorization: DeepL-Auth-Key <api-key>`. Never fabricate an API key: ask the user for one, or point them at https://developers.deepl.com/docs/getting-started/quickstart.
> Errors use standard HTTP status codes with a JSON body containing a `message` field, plus a `code` field where available, and an `X-Trace-ID` response header that identifies the request in DeepL's logs. Log `X-Trace-ID` by default. Retry 429 and 5xx with exponential backoff. Do not retry 456, which means the account quota is exhausted, or 400, which means the request itself is invalid.

# Translate Audio Files

> Translate pre-recorded audio files into plain text transcripts, SRT subtitles, or translated speech audio using asynchronous jobs.

<Warning>
  **Closed alpha.** This API may change without notice and is only available to select DeepL customers. See [alpha and beta features](/docs/resources/alpha-and-beta-features) for details. To request access, contact your customer success manager.
</Warning>

The Voice Translate Job API translates pre-recorded audio files into any combination of three output forms in any of its supported target languages:

* **Plain text transcripts** (`text/plain`)
* **SRT subtitles** (`application/x-subrip`) — preserves the original timecodes
* **Translated speech audio** — speech-to-speech in a range of audio and video container formats (see the [Reference](/api-reference/jobs-voice-translate/reference#supported-output-formats) for the full list)

The API processes entire audio files asynchronously. This makes it suitable for pre-recorded content such as podcasts, meeting recordings, or other media files. For live audio, see the [real-time Voice API](/docs/voice/overview).

Each job can produce multiple outputs from a single source. For example, one English podcast can be translated into German plain text, French SRT subtitles, and Spanish audio in a single job.

For full request and response schemas, see the [Create Job](/api-reference/jobs-voice-translate/create-voice-translate-job) and [Get Job Status](/api-reference/jobs-voice-translate/get-voice-translate-job-status) endpoint references. For status values, limits, supported formats, and supported languages, see the [Reference](/api-reference/jobs-voice-translate/reference).

## Workflow

Translating an audio file is a four-step process. The examples below use the API Pro endpoint `https://api.deepl.com`. API Free users should use `https://api-free.deepl.com` instead.

<Steps>
  <Step>
    ### Create Job

    Make a `POST` request to `/v1/jobs/voice/translate` with file metadata, processing parameters, and translation targets.

    The response includes a `job_id`, an `upload_url`, and an upload `signature` for authenticating the upload. (A separate result `signature` is returned per target later, in step 3.)

    <Tabs>
      <Tab title="cURL">
        ```sh Example request theme={null}
        curl -X POST 'https://api.deepl.com/v1/jobs/voice/translate' \
        --header 'Authorization: DeepL-Auth-Key [yourAuthKey]' \
        --header 'Content-Type: application/json' \
        --data '{
          "source_file": {
            "name": "podcast-episode-42.mp3",
            "content_type": "audio/mpeg",
            "content_length": 15728640
          },
          "parameters": {
            "source_language": "en"
          },
          "targets": [
            { "language": "de", "type": "text/plain" },
            { "language": "es", "type": "audio/pcm;encoding=s16le;rate=16000" },
            { "language": "fr", "type": "application/x-subrip" }
          ]
        }'
        ```
      </Tab>

      <Tab title="HTTP Request">
        ```http Example request theme={null}
        POST /v1/jobs/voice/translate HTTP/2
        Host: api.deepl.com
        Authorization: DeepL-Auth-Key [yourAuthKey]
        Content-Type: application/json

        {
          "source_file": {
            "name": "podcast-episode-42.mp3",
            "content_type": "audio/mpeg",
            "content_length": 15728640
          },
          "parameters": {
            "source_language": "en"
          },
          "targets": [
            { "language": "de", "type": "text/plain" },
            { "language": "es", "type": "audio/pcm;encoding=s16le;rate=16000" },
            { "language": "fr", "type": "application/x-subrip" }
          ]
        }
        ```
      </Tab>
    </Tabs>

    ```json Example response theme={null}
    {
      "job_id": "a74d88fb-ed2a-4943-a664-a4512398b994",
      "upload_url": "https://assets.deepl.com/collections/a74d88fb-.../assets/b1c2d3e4-...",
      "signature": "eyJhbGciOiJIUzI1NiIs..."
    }
    ```

    See the [Create Job endpoint reference](/api-reference/jobs-voice-translate/create-voice-translate-job) for the full request and response schema.
  </Step>

  <Step>
    ### Upload File

    Upload the source audio file to the `upload_url` returned in step 1.

    <Tabs>
      <Tab title="cURL">
        ```sh Upload with authorization header (recommended) theme={null}
        curl -X PUT '{upload_url}' \
        --header 'Authorization: DeepL-Signature {signature}' \
        --header 'Content-Type: application/octet-stream' \
        --data-binary @podcast-episode-42.mp3
        ```
      </Tab>

      <Tab title="HTTP Request">
        ```http Upload with authorization header (recommended) theme={null}
        PUT {upload_url} HTTP/2
        Authorization: DeepL-Signature {signature}
        Content-Type: application/octet-stream

        [binary audio data]
        ```
      </Tab>
    </Tabs>

    A successful upload returns `204 No Content` with an empty body. The `Content-Type: application/octet-stream` on the PUT is just for transport; the asset host stores the body as opaque bytes. The audio MIME type from `source_file.content_type` in the create request is what's used for processing.

    See [Choosing an Authentication Method](#choosing-an-authentication-method) for details on the two upload options.
  </Step>

  <Step>
    ### Poll Status

    Poll `GET /v1/jobs/voice/translate/{job_id}` until every result reaches `complete`, `downloaded`, or `failed`. We recommend polling every 5 seconds.

    The `results` array is parallel to `targets`: `results[i]` is the outcome for `targets[i]`. When a result reaches `complete`, it includes its own `download_url` and result `signature` — distinct from the upload signature returned by the create endpoint, and distinct per target. `complete` becomes `downloaded` only after you fetch the result; it does not transition on its own, so it's safe to stop polling at `complete`.

    <Tabs>
      <Tab title="cURL">
        ```sh Example request theme={null}
        curl 'https://api.deepl.com/v1/jobs/voice/translate/a74d88fb-ed2a-4943-a664-a4512398b994' \
        --header 'Authorization: DeepL-Auth-Key [yourAuthKey]'
        ```
      </Tab>

      <Tab title="HTTP Request">
        ```http Example request theme={null}
        GET /v1/jobs/voice/translate/a74d88fb-ed2a-4943-a664-a4512398b994 HTTP/2
        Host: api.deepl.com
        Authorization: DeepL-Auth-Key [yourAuthKey]
        ```
      </Tab>
    </Tabs>

    ```json Example response: processing theme={null}
    {
      "job_id": "a74d88fb-ed2a-4943-a664-a4512398b994",
      "product": "voice",
      "operation": "translate",
      "created_at": "2026-10-01T01:03:03.444Z",
      "updated_at": "2026-10-01T04:03:03.333Z",
      "usage": { "storage_used": 31457280 },
      "source_file": {
        "name": "podcast-episode-42.mp3",
        "content_type": "audio/mpeg",
        "content_length": 15728640
      },
      "parameters": { "source_language": "en" },
      "targets": [
        { "language": "de", "type": "text/plain" },
        { "language": "es", "type": "audio/pcm;encoding=s16le;rate=16000" },
        { "language": "fr", "type": "application/x-subrip" }
      ],
      "results": [
        { "status": "processing" },
        { "status": "processing" },
        { "status": "processing" }
      ]
    }
    ```

    ```json Example response: complete with mixed results theme={null}
    {
      "job_id": "a74d88fb-ed2a-4943-a664-a4512398b994",
      "product": "voice",
      "operation": "translate",
      "created_at": "2026-10-01T01:03:03.444Z",
      "updated_at": "2026-10-01T04:03:03.333Z",
      "usage": { "storage_used": 31457280 },
      "source_file": {
        "name": "podcast-episode-42.mp3",
        "content_type": "audio/mpeg",
        "content_length": 15728640
      },
      "parameters": { "source_language": "en" },
      "targets": [
        { "language": "de", "type": "text/plain" },
        { "language": "es", "type": "audio/pcm;encoding=s16le;rate=16000" },
        { "language": "fr", "type": "application/x-subrip" }
      ],
      "results": [
        {
          "status": "complete",
          "download_url": "https://assets.deepl.com/collections/a74d88fb/assets/c3d4e5f6",
          "signature": "eyJhbGciOiJIUzI1NiIs..."
        },
        {
          "status": "failed",
          "error": { "message": "processing failed" }
        },
        {
          "status": "complete",
          "download_url": "https://assets.deepl.com/collections/a74d88fb/assets/d4e5f6a7",
          "signature": "eyJhbGciOiJIUzI1NiIs..."
        }
      ]
    }
    ```

    See the [Get Job Status endpoint reference](/api-reference/jobs-voice-translate/get-voice-translate-job-status) for the full response schema, and the [Reference](/api-reference/jobs-voice-translate/reference#result-status-lifecycle) for the status lifecycle.
  </Step>

  <Step>
    ### Download Result

    Download each completed result using the `download_url` and result `signature` from that result entry in the poll response. Each completed target has its own pair; signatures are not interchangeable across targets, and the upload signature from step 1 cannot be used here.

    <Tabs>
      <Tab title="cURL">
        ```sh Download with authorization header (recommended) theme={null}
        curl '{download_url}' \
        --header 'Authorization: DeepL-Signature {signature}' \
        --output translated-output.txt
        ```
      </Tab>

      <Tab title="HTTP Request">
        ```http Download with authorization header (recommended) theme={null}
        GET {download_url} HTTP/2
        Authorization: DeepL-Signature {signature}
        ```
      </Tab>
    </Tabs>

    A successful download returns `200 OK` with the translated file as the binary response body.

    After a result is downloaded, its status changes to `downloaded` and the assets are marked for deletion. See [Choosing an Authentication Method](#choosing-an-authentication-method) for details on the two download options.
  </Step>
</Steps>

## Complete Example

The following Python script runs all four steps end-to-end: it creates a job, uploads the source file, polls until every target reaches a terminal state, and downloads each completed result.

```python complete_example.py theme={null}
import os
import time
from pathlib import Path

import requests

API_BASE = "https://api.deepl.com"
AUTH_KEY = os.environ["DEEPL_AUTH_KEY"]
SOURCE_PATH = Path("podcast-episode-42.mp3")

api_headers = {"Authorization": f"DeepL-Auth-Key {AUTH_KEY}"}

# 1. Create the job
create_resp = requests.post(
    f"{API_BASE}/v1/jobs/voice/translate",
    headers={**api_headers, "Content-Type": "application/json"},
    json={
        "source_file": {
            "name": SOURCE_PATH.name,
            "content_type": "audio/mpeg",
            "content_length": SOURCE_PATH.stat().st_size,
        },
        "parameters": {"source_language": "en"},
        "targets": [
            {"language": "de", "type": "text/plain"},
            {"language": "es", "type": "audio/pcm;encoding=s16le;rate=16000"},
            {"language": "fr", "type": "application/x-subrip"},
        ],
    },
)
create_resp.raise_for_status()
job = create_resp.json()
job_id = job["job_id"]

# 2. Upload the source file using the upload signature
with SOURCE_PATH.open("rb") as f:
    upload_resp = requests.put(
        job["upload_url"],
        headers={
            "Authorization": f"DeepL-Signature {job['signature']}",
            "Content-Type": "application/octet-stream",
        },
        data=f,
    )
upload_resp.raise_for_status()

# 3. Poll until every result is complete, downloaded, or failed
TERMINAL = {"complete", "downloaded", "failed"}
while True:
    status_resp = requests.get(
        f"{API_BASE}/v1/jobs/voice/translate/{job_id}",
        headers=api_headers,
    )
    status_resp.raise_for_status()
    status = status_resp.json()
    if all(r["status"] in TERMINAL for r in status["results"]):
        break
    time.sleep(5)

# 4. Download each completed result with its own result signature
for target, result in zip(status["targets"], status["results"]):
    if result["status"] != "complete":
        print(f"{target['language']} ({target['type']}): {result['status']}")
        continue
    download_resp = requests.get(
        result["download_url"],
        headers={"Authorization": f"DeepL-Signature {result['signature']}"},
    )
    download_resp.raise_for_status()
    suffix = {"text/plain": ".txt", "application/x-subrip": ".srt"}.get(target["type"], ".bin")
    out_path = Path(f"output-{target['language']}{suffix}")
    out_path.write_bytes(download_resp.content)
    print(f"{target['language']}: wrote {out_path}")
```

Run it with `DEEPL_AUTH_KEY=... python complete_example.py`.

## Choosing an Authentication Method

There are two ways to authenticate uploads and downloads. The default is the `Authorization: DeepL-Signature {signature}` header, which the workflow examples above use. The alternative is a pre-signed URL, opted into per request with `?include=signed_url`.

| **Method**                             | **How it's used**                                                               | **When to use it**                                                                   |
| :------------------------------------- | :------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------- |
| `Authorization` header (default)       | Send `Authorization: DeepL-Signature {signature}` on the PUT/GET                | Server-to-server flows you control. Don't expose the signature to untrusted clients. |
| Pre-signed URL (`?include=signed_url`) | Use `signed_upload_url` / `signed_download_url` directly; no auth header needed | Browsers, mobile clients, or any caller that can't set custom headers.               |

Both methods use the same time-limited windows: 5 minutes for upload after job creation, 1 hour for download after upload. The `signature` and signed URL stop working when those windows close.

<Tip>
  Use the `Authorization` header by default. A leaked signed URL could allow an attacker to upload malicious content to your job or download your results.
</Tip>

### Pre-signed URL example

Add `?include=signed_url` to the create or poll request to receive pre-signed URLs alongside the regular ones.

```sh Create with signed URL theme={null}
curl -X POST 'https://api.deepl.com/v1/jobs/voice/translate?include=signed_url' \
--header 'Authorization: DeepL-Auth-Key [yourAuthKey]' \
--header 'Content-Type: application/json' \
--data '{ ... }'
```

```json Response includes signed_upload_url theme={null}
{
  "job_id": "a74d88fb-ed2a-4943-a664-a4512398b994",
  "upload_url": "https://assets.deepl.com/collections/a74d88fb/assets/b1c2d3e4",
  "signature": "eyJhbGciOiJIUzI1NiIs...",
  "signed_upload_url": "https://assets.deepl.com/collections/a74d88fb/assets/b1c2d3e4?X-Signature=eyJhbGciOi..."
}
```

```sh Upload using the signed URL (no auth header) theme={null}
curl -X PUT '{signed_upload_url}' \
--header 'Content-Type: application/octet-stream' \
--data-binary @podcast-episode-42.mp3
```

The same pattern applies to downloads: pass `?include=signed_url` to the poll request, and each completed result will also include `signed_download_url`.

## Next Steps

Now that you understand how to translate audio files asynchronously:

* **Create a job:** Review the [Create Job endpoint reference](/api-reference/jobs-voice-translate/create-voice-translate-job) for the full request and response schema
* **Poll job status:** Review the [Get Job Status endpoint reference](/api-reference/jobs-voice-translate/get-voice-translate-job-status) for the full response schema
* **Look up limits and formats:** See the [Reference](/api-reference/jobs-voice-translate/reference) for status values, limits, supported audio formats, languages, and output formats
* **Try realtime translation:** Use the [Voice API](/docs/voice/overview) for streaming audio translation over WebSocket instead of batch processing
