From f81e93a3e6b645a31550d659994085ff597494b6 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 3 Sep 2026 18:10:01 +0000
Subject: [PATCH] docs: add generated pages from pipeline run 20260903-175940
Generated 3 pages for: Languages, Voice, unknown
- docs/voice/translate-a-pre-recorded-audio-file.mdx: No guide (tutorial or how-to) covers the 'Translate Audio Files' endpoints
- docs/languages/query-language-and-feature-availability-dynamically.mdx: No guide (tutorial or how-to) covers the 'Languages' endpoints
- docs/learning-how-tos/cookbook/google-sheets: docs/learning-how-tos/cookbook/google-sheets has under 100 words
---
docs.json | 6 +-
...e-and-feature-availability-dynamically.mdx | 246 ++++++++++++++++
.../translate-a-pre-recorded-audio-file.mdx | 271 ++++++++++++++++++
3 files changed, 522 insertions(+), 1 deletion(-)
create mode 100644 docs/languages/query-language-and-feature-availability-dynamically.mdx
create mode 100644 docs/voice/translate-a-pre-recorded-audio-file.mdx
diff --git a/docs.json b/docs.json
index 38559381..6664a1fd 100644
--- a/docs.json
+++ b/docs.json
@@ -102,6 +102,9 @@
}
]
}
+ ],
+ "pages": [
+ "docs/languages/query-language-and-feature-availability-dynamically"
]
},
{
@@ -149,7 +152,8 @@
"docs/voice/understanding-voice-sessions",
"docs/voice/message-encoding",
"docs/voice/supported-voice-languages",
- "docs/voice/voice-api-requirements"
+ "docs/voice/voice-api-requirements",
+ "docs/voice/translate-a-pre-recorded-audio-file"
]
},
{
diff --git a/docs/languages/query-language-and-feature-availability-dynamically.mdx b/docs/languages/query-language-and-feature-availability-dynamically.mdx
new file mode 100644
index 00000000..8a1647a8
--- /dev/null
+++ b/docs/languages/query-language-and-feature-availability-dynamically.mdx
@@ -0,0 +1,246 @@
+---
+title: "Query language and feature availability dynamically"
+description: "Use GET /v3/languages and GET /v3/languages/resources to build language selectors and feature toggles that stay accurate as DeepL adds new languages."
+covers: [Languages]
+---
+
+The `/v3/languages` endpoint returns which languages each DeepL API resource supports, along with which optional features (formality, glossaries, tag handling, and more) are available per language. Rather than hardcoding a language list that goes stale, you can query it at startup or on a schedule and use the result to drive dropdowns, feature toggles, and validation in your integration.
+
+This guide walks through the two endpoints you need, shows you how to combine them, and covers the practical patterns you'll use most.
+
+
+ If you're currently using the deprecated `GET /v2/languages` endpoint, see the [migration guide](/docs/languages/migrating-from-v2-languages) for the differences and how to update your code.
+
+
+## What you'll build
+
+By the end of this guide, you'll know how to:
+
+- Fetch the languages available for a specific DeepL resource
+- Read per-language feature availability (e.g. formality, glossary support)
+- Use `GET /v3/languages/resources` to understand which language in a pair must support a feature
+- Filter languages by `usable_as_source` and `usable_as_target` to populate language selectors correctly
+
+## Prerequisites
+
+- A DeepL API key. Find yours at [deepl.com/your-account/keys](https://www.deepl.com/your-account/keys).
+- A way to make HTTP requests (curl, an HTTP client, or one of the [DeepL SDKs](/docs/getting-started/client-libraries)).
+
+
+ If you're on a Free API plan, replace `https://api.deepl.com` with `https://api-free.deepl.com` in every request below.
+
+
+## Step 1: Fetch languages for a resource
+
+Call `GET /v3/languages` with the `resource` query parameter set to the DeepL API resource you're building for.
+
+The supported resource values are: `translate_text`, `translate_document`, `glossary`, `voice`, `write`, `style_rules`, and `translation_memory`.
+
+The following example fetches languages for text translation:
+
+```bash
+curl -X GET 'https://api.deepl.com/v3/languages?resource=translate_text' \
+ --header 'Authorization: DeepL-Auth-Key [yourAuthKey]'
+```
+
+```json
+// Example response (truncated)
+[
+ {
+ "lang": "de",
+ "name": "German",
+ "status": "stable",
+ "usable_as_source": true,
+ "usable_as_target": true,
+ "features": {
+ "formality": { "status": "stable" },
+ "glossary": { "status": "stable" },
+ "tag_handling": { "status": "stable" }
+ }
+ },
+ {
+ "lang": "en",
+ "name": "English",
+ "status": "stable",
+ "usable_as_source": true,
+ "usable_as_target": false,
+ "features": {
+ "glossary": { "status": "stable" },
+ "tag_handling": { "status": "stable" }
+ }
+ },
+ {
+ "lang": "en-US",
+ "name": "English (American)",
+ "status": "stable",
+ "usable_as_source": false,
+ "usable_as_target": true,
+ "features": {
+ "glossary": { "status": "stable" },
+ "tag_handling": { "status": "stable" }
+ }
+ }
+]
+```
+
+Each object in the array represents one language (or language variant). Notice that `en` and `en-US` are separate entries: `en` is only usable as a source language, while `en-US` is only usable as a target. Use `usable_as_source` and `usable_as_target` to filter the list correctly when populating your language selectors.
+
+The `features` object lists which optional capabilities that language supports for the given resource. A feature key present in the object means the language supports that capability. The `status` field indicates whether that support is `stable`, `beta`, or `early_access`.
+
+## Step 2: Split source and target languages
+
+Filter the response by `usable_as_source` and `usable_as_target` to build separate lists:
+
+```python
+import httpx
+
+response = httpx.get(
+ "https://api.deepl.com/v3/languages",
+ params={"resource": "translate_text"},
+ headers={"Authorization": "DeepL-Auth-Key [yourAuthKey]"},
+)
+response.raise_for_status()
+languages = response.json()
+
+source_languages = [lang for lang in languages if lang["usable_as_source"]]
+target_languages = [lang for lang in languages if lang["usable_as_target"]]
+
+print("Source languages:", [lang["lang"] for lang in source_languages])
+print("Target languages:", [lang["lang"] for lang in target_languages])
+```
+
+```text Example output
+Source languages: ['ar', 'bg', 'cs', 'da', 'de', 'el', 'en', ...]
+Target languages: ['ar', 'bg', 'cs', 'da', 'de', 'el', 'en-GB', 'en-US', ...]
+```
+
+Both lists can include the same base language (like `de`), but only the target list will include regional variants like `en-US` and `en-GB` that aren't usable as source languages.
+
+
+ Do not hardcode assumptions about language code format. Codes follow BCP 47 and may include region, script, or variant subtags (e.g. `zh-Hans`, `sr-Cyrl-RS`). Always treat them as opaque identifiers. See the [language release process](/docs/resources/language-release-process) for more detail.
+
+
+## Step 3: Check feature availability for a language pair
+
+The `features` object on each language tells you what that language supports. But some features (like glossaries) require both the source and target language to support them. To understand which side of the pair must support a feature, call `GET /v3/languages/resources`.
+
+```bash
+curl -X GET 'https://api.deepl.com/v3/languages/resources' \
+ --header 'Authorization: DeepL-Auth-Key [yourAuthKey]'
+```
+
+```json
+// Example response (truncated)
+[
+ {
+ "name": "translate_text",
+ "features": [
+ { "name": "formality", "needs_target_support": true },
+ { "name": "tag_handling", "needs_source_support": true, "needs_target_support": true },
+ { "name": "glossary", "needs_source_support": true, "needs_target_support": true },
+ { "name": "auto_detection", "needs_source_support": true }
+ ]
+ }
+]
+```
+
+Each feature entry tells you whether `needs_source_support`, `needs_target_support`, or both must be true. If a field is absent, it defaults to `false`.
+
+Combine this with the per-language `features` objects from Step 1 to determine whether a feature is available for a given language pair:
+
+```python
+import httpx
+
+# Fetch languages for translate_text (from Step 1)
+languages_response = httpx.get(
+ "https://api.deepl.com/v3/languages",
+ params={"resource": "translate_text"},
+ headers={"Authorization": "DeepL-Auth-Key [yourAuthKey]"},
+)
+languages = languages_response.json()
+
+# Build a lookup dict from lang code -> language object
+languages_by_code = {lang["lang"]: lang for lang in languages}
+
+# Fetch resource feature definitions (from GET /v3/languages/resources)
+resources_response = httpx.get(
+ "https://api.deepl.com/v3/languages/resources",
+ headers={"Authorization": "DeepL-Auth-Key [yourAuthKey]"},
+)
+resources = resources_response.json()
+
+# Extract the feature definitions for translate_text
+translate_text_resource = next(r for r in resources if r["name"] == "translate_text")
+translate_text_features = translate_text_resource["features"]
+
+
+def feature_available(feature_name, source_lang, target_lang, resource_features, languages_by_code):
+ """
+ Returns True if the feature is available for the given source/target pair.
+ resource_features: the features list for the resource from GET /v3/languages/resources
+ languages_by_code: dict mapping lang code -> language object from GET /v3/languages
+ """
+ # Find the feature definition for this resource
+ feature_def = next(
+ (f for f in resource_features if f["name"] == feature_name), None
+ )
+ if feature_def is None:
+ return False # Feature not supported by this resource at all
+
+ needs_source = feature_def.get("needs_source_support", False)
+ needs_target = feature_def.get("needs_target_support", False)
+
+ source = languages_by_code.get(source_lang, {})
+ target = languages_by_code.get(target_lang, {})
+
+ if needs_source and feature_name not in source.get("features", {}):
+ return False
+ if needs_target and feature_name not in target.get("features", {}):
+ return False
+
+ return True
+
+# Example: is a glossary available for DE -> EN-US?
+available = feature_available(
+ "glossary",
+ source_lang="de",
+ target_lang="en-US",
+ resource_features=translate_text_features,
+ languages_by_code=languages_by_code,
+)
+print(f"Glossary available for DE→EN-US: {available}")
+```
+
+## Step 4: Include beta languages (optional)
+
+By default, the endpoint returns only `stable` languages and features. To include beta languages and features, add `include=beta` to the query string:
+
+```bash
+curl -X GET 'https://api.deepl.com/v3/languages?resource=translate_text&include=beta' \
+ --header 'Authorization: DeepL-Auth-Key [yourAuthKey]'
+```
+
+You can combine values with repeated parameters:
+
+```bash
+curl -X GET 'https://api.deepl.com/v3/languages?resource=voice&include=beta&include=external' \
+ --header 'Authorization: DeepL-Auth-Key [yourAuthKey]'
+```
+
+`include=external` adds features provided by third-party service partners (relevant for the `voice` resource). Beta languages and features are subject to change; see [Alpha and beta features](/docs/resources/alpha-and-beta-features) before using them in production.
+
+## Caching the response
+
+The supported language list changes infrequently. Fetching it on every translation request adds unnecessary latency. A practical approach:
+
+- Fetch both endpoints at application startup
+- Cache the results in memory
+- Refresh on a schedule (daily is usually sufficient) or when you receive an unexpected `400` for a language code
+
+The responses are the same for all users of a given API key, so a single cached copy is shared across your application.
+
+## Next steps
+
+- See the [supported languages table](/docs/getting-started/supported-languages) for a reference view of all currently stable languages
+- Read the [language release process](/docs/resources/language-release-process) to understand how DeepL codes new languages and what to expect when support is added
+- If you use glossaries, check which language pairs support them using the pattern in Step 3, or see [Glossaries](/docs/customize/glossaries) for the full workflow
\ No newline at end of file
diff --git a/docs/voice/translate-a-pre-recorded-audio-file.mdx b/docs/voice/translate-a-pre-recorded-audio-file.mdx
new file mode 100644
index 00000000..c5c1b65b
--- /dev/null
+++ b/docs/voice/translate-a-pre-recorded-audio-file.mdx
@@ -0,0 +1,271 @@
+---
+title: "Translate a Pre-Recorded Audio File"
+description: "Submit a pre-recorded audio file to the Voice Translate Job API, poll for completion, and download translated text or audio results."
+covers: [Translate Audio Files]
+---
+
+The Voice Translate Job API translates pre-recorded audio files asynchronously. You submit a file, poll a status endpoint until results are ready, then download them. This guide walks through the complete flow using a podcast episode as the example: one English MP3 in, German plain text and Spanish audio out.
+
+For live audio that needs low-latency results, see the [real-time Voice API](/docs/voice/overview) instead.
+
+
+ **Closed alpha.** This API is only available to select DeepL customers and may change without notice. See [alpha and beta features](/docs/resources/alpha-and-beta-features) for details.
+
+
+## Prerequisites
+
+- A DeepL API key with Voice Translate Job API access
+- `curl` for the API calls in this guide
+- An audio file to translate (MP3, WAV, or another [supported format](/api-reference/jobs-voice-translate/reference#supported-source-audio-formats))
+
+The examples below use `https://api.deepl.com`. API Free users should replace this with `https://api-free.deepl.com`.
+
+## Step 1: Create the job
+
+Send a POST request to `/v1/jobs/voice/translate` with three pieces of information:
+
+- The source file's name, size in bytes, and content type
+- The source language
+- One or more translation targets, each specifying a language and output type
+
+```bash
+curl https://api.deepl.com/v1/jobs/voice/translate \
+ --request POST \
+ --header "Authorization: DeepL-Auth-Key YOUR_AUTH_KEY" \
+ --header "Content-Type: application/json" \
+ --data '{
+ "source_file": {
+ "name": "podcast-episode-42.mp3",
+ "content_length": 15728640,
+ "content_type": "audio/mpeg"
+ },
+ "parameters": {
+ "source_language": "en"
+ },
+ "targets": [
+ { "language": "de", "type": "text/plain" },
+ { "language": "es", "type": "audio/pcm;encoding=s16le;rate=16000" }
+ ]
+ }'
+```
+
+The `content_length` must be the exact byte size of the file you will upload in the next step.
+
+A successful response returns HTTP 201 with a `job_id`, a one-time `upload_url`, and a `signature`:
+
+```json
+{
+ "job_id": "a74d88fb-ed2a-4943-a664-a4512398b994",
+ "signature": "eyJhbGciOiJIUzI1NiIs...",
+ "upload_url": "https://assets.deepl.com/collections/a74d88fb-ed2a-4943-a664-a4512398b994/assets/b1c2d3e4-f5a6-7890-abcd-ef1234567890"
+}
+```
+
+Save the `job_id` — you need it to check status and retrieve results.
+
+## Step 2: Upload the source file
+
+PUT the audio file directly to the `upload_url` from the previous response. You must complete the upload within 5 minutes of creating the job.
+
+```bash
+curl "https://assets.deepl.com/collections/a74d88fb-ed2a-4943-a664-a4512398b994/assets/b1c2d3e4-f5a6-7890-abcd-ef1234567890" \
+ --request PUT \
+ --header "Content-Type: audio/mpeg" \
+ --data-binary @podcast-episode-42.mp3
+```
+
+The `Content-Type` header must match the `content_type` you declared when creating the job.
+
+
+ Do not include your DeepL API key in the upload request. The `upload_url` is pre-authorized and expires after 5 minutes.
+
+
+## Step 3: Poll for status
+
+Check the job status by sending a GET request to `/v1/jobs/voice/translate/{job_id}`. The API processes each target independently, so results may become available at different times.
+
+```bash
+curl "https://api.deepl.com/v1/jobs/voice/translate/a74d88fb-ed2a-4943-a664-a4512398b994" \
+ --header "Authorization: DeepL-Auth-Key YOUR_AUTH_KEY"
+```
+
+While processing, the response looks like this:
+
+```json
+{
+ "job_id": "a74d88fb-ed2a-4943-a664-a4512398b994",
+ "operation": "translate",
+ "product": "voice",
+ "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" }
+ ],
+ "results": [
+ { "status": "processing" },
+ { "status": "processing" }
+ ],
+ "created_at": "2026-10-01T01:03:03.444Z",
+ "updated_at": "2026-10-01T04:03:03.333Z"
+}
+```
+
+Results appear in the same order as the targets in the create request. Poll at a reasonable interval — every 10-30 seconds is appropriate for audio files, since processing time scales with duration.
+
+When processing finishes, each completed result includes a `download_url` and `signature`:
+
+```json
+{
+ "job_id": "a74d88fb-ed2a-4943-a664-a4512398b994",
+ "results": [
+ {
+ "status": "complete",
+ "download_url": "https://assets.deepl.com/collections/a74d88fb/assets/c3d4e5f6",
+ "signature": "eyJhbGciOiJIUzI1NiIs..."
+ },
+ {
+ "status": "failed",
+ "error": { "message": "processing failed" }
+ }
+ ]
+}
+```
+
+A result's `status` can be `pending`, `uploaded`, `processing`, `complete`, `downloaded`, or `failed`. See the [status lifecycle](/api-reference/jobs-voice-translate/reference#result-status-lifecycle) for how these progress. A `failed` status on one target does not affect the others.
+
+## Step 4: Download the results
+
+For each result with `"status": "complete"`, download the output from its `download_url`. No authentication header is required — the URL is pre-authorized.
+
+```bash
+# Download the German plain text transcript
+curl "https://assets.deepl.com/collections/a74d88fb/assets/c3d4e5f6" \
+ --output transcript-de.txt
+```
+
+Download results promptly. Results expire 1 hour after the source file is uploaded, and are deleted once downloaded or expired. After deletion, the job returns `404`.
+
+## Putting it together
+
+Here is the complete flow as a Python script. It creates the job, uploads the file, polls until all results are complete or failed, then downloads each completed result.
+
+```python translate_audio.py
+import time
+import sys
+import requests
+
+AUTH_KEY = "YOUR_AUTH_KEY"
+BASE_URL = "https://api.deepl.com"
+AUDIO_FILE = "podcast-episode-42.mp3"
+POLL_INTERVAL = 15 # seconds
+TARGETS = [
+ {"language": "de", "type": "text/plain"},
+ {"language": "es", "type": "audio/pcm;encoding=s16le;rate=16000"},
+]
+
+
+def create_job(file_path: str) -> dict:
+ # Get exact file size
+ with open(file_path, "rb") as f:
+ f.seek(0, 2)
+ file_size = f.tell()
+
+ response = requests.post(
+ f"{BASE_URL}/v1/jobs/voice/translate",
+ headers={"Authorization": f"DeepL-Auth-Key {AUTH_KEY}"},
+ json={
+ "source_file": {
+ "name": file_path,
+ "content_length": file_size,
+ "content_type": "audio/mpeg",
+ },
+ "parameters": {"source_language": "en"},
+ "targets": TARGETS,
+ },
+ )
+ response.raise_for_status()
+ return response.json()
+
+
+def upload_file(file_path: str, upload_url: str) -> None:
+ with open(file_path, "rb") as f:
+ response = requests.put(
+ upload_url,
+ headers={"Content-Type": "audio/mpeg"},
+ data=f,
+ )
+ response.raise_for_status()
+
+
+def poll_until_done(job_id: str) -> list:
+ terminal = {"complete", "failed", "downloaded"}
+ while True:
+ response = requests.get(
+ f"{BASE_URL}/v1/jobs/voice/translate/{job_id}",
+ headers={"Authorization": f"DeepL-Auth-Key {AUTH_KEY}"},
+ )
+ response.raise_for_status()
+ data = response.json()
+ results = data.get("results", [])
+
+ if all(r.get("status") in terminal for r in results):
+ return results
+
+ statuses = [r.get("status") for r in results]
+ print(f"Waiting... statuses: {statuses}")
+ time.sleep(POLL_INTERVAL)
+
+
+def download_results(results: list, targets: list) -> None:
+ for i, (result, target) in enumerate(zip(results, targets)):
+ if result["status"] == "complete":
+ lang = target["language"]
+ ext = "txt" if target["type"] == "text/plain" else "pcm"
+ output_path = f"result-{lang}.{ext}"
+ content = requests.get(result["download_url"])
+ content.raise_for_status()
+ with open(output_path, "wb") as f:
+ f.write(content.content)
+ print(f"Downloaded: {output_path}")
+ else:
+ error = result.get("error", {}).get("message", "unknown error")
+ print(f"Target {i} failed: {error}", file=sys.stderr)
+
+
+def main():
+ print("Creating job...")
+ job = create_job(AUDIO_FILE)
+ job_id = job["job_id"]
+ print(f"Job created: {job_id}")
+
+ print("Uploading file...")
+ upload_file(AUDIO_FILE, job["upload_url"])
+ print("Upload complete.")
+
+ print("Polling for results...")
+ results = poll_until_done(job_id)
+
+ print("Downloading results...")
+ download_results(results, TARGETS)
+
+
+if __name__ == "__main__":
+ main()
+```
+
+## Common issues
+
+**400 on job creation**: The `content_length` must exactly match the file you will upload. Read the file size before sending the create request, don't estimate it.
+
+**Upload times out**: The upload window is 5 minutes from job creation. If your file is large or your connection is slow, start the upload immediately after creating the job.
+
+**Results expire before download**: Download results within 1 hour of uploading the source file. If your polling loop is slow, check `updated_at` in the status response to estimate how much time remains.
+
+**One target fails, others succeed**: Failures are per-target. Check the `error.message` field on failed results and download the successful ones independently.
+
+For format support, per-language availability, and job limits, see the [Translate Audio Files reference](/api-reference/jobs-voice-translate/reference).
\ No newline at end of file