Skip to main content

Documentation Index

Fetch the complete documentation index at: https://deepl-c950b784-add-usage-logger-cookbook.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Closed alpha. This API may change without notice and is only available to select DeepL customers. See alpha and beta features for details. To request access, contact your customer success manager.
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 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. 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 and Get Job Status endpoint references. For status values, limits, supported formats, and supported languages, see the 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.
1

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.)
Example request
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" }
  ]
}'
Example response
{
  "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 for the full request and response schema.
2

Upload File

Upload the source audio file to the upload_url returned in step 1.
Upload with authorization header (recommended)
curl -X PUT '{upload_url}' \
--header 'Authorization: DeepL-Signature {signature}' \
--header 'Content-Type: application/octet-stream' \
--data-binary @podcast-episode-42.mp3
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 for details on the two upload options.
3

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.
Example request
curl 'https://api.deepl.com/v1/jobs/voice/translate/a74d88fb-ed2a-4943-a664-a4512398b994' \
--header 'Authorization: DeepL-Auth-Key [yourAuthKey]'
Example response: processing
{
  "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" }
  ]
}
Example response: complete with mixed results
{
  "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 for the full response schema, and the Reference for the status lifecycle.
4

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.
Download with authorization header (recommended)
curl '{download_url}' \
--header 'Authorization: DeepL-Signature {signature}' \
--output translated-output.txt
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 for details on the two download options.

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.
complete_example.py
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.
MethodHow it’s usedWhen to use it
Authorization header (default)Send Authorization: DeepL-Signature {signature} on the PUT/GETServer-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 neededBrowsers, 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.
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.

Pre-signed URL example

Add ?include=signed_url to the create or poll request to receive pre-signed URLs alongside the regular ones.
Create with signed URL
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 '{ ... }'
Response includes signed_upload_url
{
  "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..."
}
Upload using the signed URL (no auth header)
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 for the full request and response schema
  • Poll job status: Review the Get Job Status endpoint reference for the full response schema
  • Look up limits and formats: See the Reference for status values, limits, supported audio formats, languages, and output formats
  • Try realtime translation: Use the Voice API for streaming audio translation over WebSocket instead of batch processing