diff --git a/docs.json b/docs.json index 6ab5aa0b..5a58d1d8 100644 --- a/docs.json +++ b/docs.json @@ -102,6 +102,9 @@ } ] } + ], + "pages": [ + "docs/languages/check-feature-availability-for-a-language-pair" ] }, { @@ -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/check-feature-availability-for-a-language-pair.mdx b/docs/languages/check-feature-availability-for-a-language-pair.mdx new file mode 100644 index 00000000..c7d8ac93 --- /dev/null +++ b/docs/languages/check-feature-availability-for-a-language-pair.mdx @@ -0,0 +1,216 @@ +--- +title: "Check feature availability for a language pair" +description: "Use the v3/languages endpoints to look up which features are available for a specific source and target language combination." +covers: [Languages] +--- + +The `GET /v3/languages` endpoint tells you which features — formality, glossaries, tag handling, and more — are available for a given language and DeepL resource. Use it to validate language pairs and conditionally enable features in your integration rather than hardcoding assumptions. + +This guide walks through the most common task: given a source language and a target language, determine which features you can use when translating between them. + + +If you're currently using `GET /v2/languages`, see the [migration guide](/docs/languages/migrating-from-v2-languages) for differences and code examples. The v2 endpoint is deprecated. + + +## Before you start + +You'll need a DeepL API key. Set it as an environment variable: + +```bash +export DEEPL_API_KEY=your-api-key +``` + +## Step 1: Fetch languages for your resource + +Call `GET /v3/languages` with the `resource` parameter set to the DeepL API resource you're building for. For text translation, use `translate_text`. + +```bash +curl -X GET 'https://api.deepl.com/v3/languages?resource=translate_text' \ + --header "Authorization: DeepL-Auth-Key $DEEPL_API_KEY" +``` + +Each item in the response describes one language and whether it can be used as a source, a target, or both, along with the features it supports: + +```json +[ + { + "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" } + } + } +] +``` + +Notice that `en` is source-only and `en-US` is target-only. Some features appear only on target languages (like `formality`) and some on both (like `glossary`). The next step explains how to determine which is which. + +## Step 2: Understand which side a feature applies to + +Some features require support on the source language, some on the target, and some on both. To check the rules for your resource, call `GET /v3/languages/resources`: + +```bash +curl -X GET 'https://api.deepl.com/v3/languages/resources' \ + --header "Authorization: DeepL-Auth-Key $DEEPL_API_KEY" +``` + +```json +[ + { + "name": "translate_text", + "features": [ + { "name": "formality", "needs_target_support": true }, + { "name": "style_rules", "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 } + ] + } +] +``` + +For `translate_text`: +- `formality` requires only target-language support +- `glossary` and `tag_handling` require support on both the source and target language +- `auto_detection` requires only source-language support (it applies when you omit the source language) + +## Step 3: Check availability for a specific language pair + +With the language data from Step 1 and the feature rules from Step 2, you can now determine which features are available for any pair. The logic is straightforward: a feature is available for a pair when every required side supports it. + +Here's a function that encapsulates the check: + +```python +def check_pair_features(languages_by_code, resources_by_name, source_code, target_code, resource_name): + """ + Returns a dict of feature -> status for a given source/target pair and resource. + Only includes features available for the pair (all required sides support it). + """ + source = languages_by_code.get(source_code) + target = languages_by_code.get(target_code) + + if not source or not source.get("usable_as_source"): + raise ValueError(f"{source_code!r} is not a valid source language") + if not target or not target.get("usable_as_target"): + raise ValueError(f"{target_code!r} is not a valid target language") + + resource_features = { + f["name"]: f + for f in resources_by_name.get(resource_name, {}).get("features", []) + } + + available = {} + for feature_name, rules in resource_features.items(): + needs_source = rules.get("needs_source_support", False) + needs_target = rules.get("needs_target_support", False) + + source_ok = not needs_source or feature_name in source.get("features", {}) + target_ok = not needs_target or feature_name in target.get("features", {}) + + if source_ok and target_ok: + # Pick the most restrictive status across all required sides. + # Index order: stable=0, beta=1, early_access=2 — higher index means less stable. + statuses = [] + if needs_source and feature_name in source.get("features", {}): + statuses.append(source["features"][feature_name]["status"]) + if needs_target and feature_name in target.get("features", {}): + statuses.append(target["features"][feature_name]["status"]) + status_order = ["stable", "beta", "early_access"] + available[feature_name] = max(statuses, key=lambda s: status_order.index(s)) if statuses else "stable" + + return available +``` + +To use it, fetch both endpoints once at startup, index by code/name, then call the function for any pair: + +```python +import os +import requests + +API_KEY = os.environ["DEEPL_API_KEY"] +HEADERS = {"Authorization": f"DeepL-Auth-Key {API_KEY}"} +BASE_URL = "https://api.deepl.com" + +# Fetch once and cache +languages = requests.get( + f"{BASE_URL}/v3/languages", + params={"resource": "translate_text"}, + headers=HEADERS, +).json() + +resources = requests.get( + f"{BASE_URL}/v3/languages/resources", + headers=HEADERS, +).json() + +languages_by_code = {lang["lang"]: lang for lang in languages} +resources_by_name = {res["name"]: res for res in resources} + +# Check English → German +features = check_pair_features( + languages_by_code, resources_by_name, + source_code="en", + target_code="de", + resource_name="translate_text", +) +print(features) +# {'formality': 'stable', 'tag_handling': 'stable', 'glossary': 'stable'} +``` + +The output tells you exactly which features you can pass to the [translate endpoint](/api-reference/translate) for this pair. + +## Step 4: Include beta languages (optional) + +By default, `GET /v3/languages` returns only stable languages. To include languages in beta, add `include=beta` to your request: + +```bash +curl -X GET 'https://api.deepl.com/v3/languages?resource=translate_text&include=beta' \ + --header "Authorization: DeepL-Auth-Key $DEEPL_API_KEY" +``` + +Beta languages have `"status": "beta"` in the response. Their features may also carry a `"status": "beta"` marker. Check the `status` field before surfacing beta languages or features to end users. + + +Beta languages and features can change or be removed without notice. See [Alpha and beta features](/docs/resources/alpha-and-beta-features) before relying on them in production. + + +## Caching recommendations + +The language list changes infrequently. Fetching it on every request adds latency and counts against your rate limits. Cache both `/v3/languages` and `/v3/languages/resources` responses and refresh them once daily, or on application startup. When DeepL adds a new language, the [language release process](/docs/resources/language-release-process) page describes what to expect in the API response. + + +Do not hardcode language codes or feature lists in your application. Language codes follow BCP 47 and may use subtags beyond the common two-letter form (e.g. `zh-Hans`, `sr-Cyrl-RS`). Always treat codes as opaque identifiers. See [Language release process](/docs/resources/language-release-process) for details. + + +## Next steps + +- [Using the Languages API](/docs/languages/using-the-languages-api) — full reference for both v3 endpoints, including all `resource` values and the complete response schema +- [Supported languages](/docs/getting-started/supported-languages) — static table of all currently supported languages +- [Migrating from v2/languages](/docs/languages/migrating-from-v2-languages) — if you're upgrading from the deprecated v2 endpoint \ 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..b113267a --- /dev/null +++ b/docs/voice/translate-a-pre-recorded-audio-file.mdx @@ -0,0 +1,239 @@ +--- +title: "Translate a Pre-Recorded Audio File" +description: "Submit an audio file to the DeepL Voice Translate Job API and download plain text, SRT, or audio results using the async job workflow." +covers: [Translate Audio Files] +--- + +The Voice Translate Job API translates pre-recorded audio files asynchronously. You submit a file, poll for completion, and download one or more outputs — plain text transcripts, SRT subtitles, or translated speech audio — in any combination of target languages. This guide walks you through the complete workflow with a real English MP3 podcast episode translated into German text and Spanish audio. + +For live audio, see the [Real-Time Voice Quickstart](/docs/voice/real-time-voice-quickstart) instead. + + + **Closed alpha.** This API is only available to select DeepL customers and may change without notice. Contact your customer success manager to request access. + + +## Prerequisites + +- A DeepL API account with Voice Translate Job API access +- Your DeepL API key +- An audio file to translate (see [supported source formats](/api-reference/jobs-voice-translate/reference#supported-source-audio-formats)) +- `curl` and `jq` installed, or a language environment of your choice + +## The workflow at a glance + +Translating an audio file takes four steps: + +1. Create a job — get back an upload URL and a job ID +2. Upload your audio file to the upload URL +3. Poll the job status until all targets are `complete` (or `failed`) +4. Download each result using its `download_url` + +The examples below use `https://api.deepl.com` (API Pro). If you're on API Free, replace that with `https://api-free.deepl.com`. + +## Step 1: Create a job + +Send a POST request with your source file metadata and the list of outputs you want. You must declare the file's `content_length` and `content_type` upfront — these are used to pre-authorize the upload. + +```bash +curl -X POST https://api.deepl.com/v1/jobs/voice/translate \ + -H "Authorization: DeepL-Auth-Key YOUR_AUTH_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "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" } + ] + }' +``` + +A successful response returns a job ID, an 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'll need it to poll for status. The `upload_url` is a pre-signed URL valid for 5 minutes. If you miss the window, you'll need to create a new job. + + + Each entry in `targets` is independent. A single job can produce any combination of output types and languages — one job, multiple results. + + +## Step 2: Upload your audio file + +PUT the file directly to the `upload_url` from step 1. Include the `Content-Type` header matching the `content_type` you declared when creating the job. + +```bash +curl -X PUT "https://assets.deepl.com/collections/a74d88fb-ed2a-4943-a664-a4512398b994/assets/b1c2d3e4-f5a6-7890-abcd-ef1234567890" \ + -H "Content-Type: audio/mpeg" \ + --data-binary @podcast-episode-42.mp3 +``` + +A successful upload returns an HTTP 200 with no body. Once the file is received, processing begins automatically — you don't need to trigger it separately. + + + The `Content-Type` on the upload request must exactly match `content_type` in your job creation request. A mismatch will cause the upload to fail. + + +## Step 3: Poll for status + +Poll `GET /v1/jobs/voice/translate/{job_id}` until all targets reach a terminal status (`complete`, `failed`, or `downloaded`). Each target is processed independently, so some may finish before others. + +```bash +curl https://api.deepl.com/v1/jobs/voice/translate/a74d88fb-ed2a-4943-a664-a4512398b994 \ + -H "Authorization: DeepL-Auth-Key YOUR_AUTH_KEY" +``` + +While processing, targets show `status: processing`: + +```json +{ + "job_id": "a74d88fb-ed2a-4943-a664-a4512398b994", + "results": [ + { "status": "processing" }, + { "status": "processing" } + ] +} +``` + +When a target completes, its 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" } + } + ] +} +``` + +Results appear in the same order as the `targets` array in your create request. A failed target does not affect other targets in the same job — if the German text completes successfully, its `download_url` is available even if the Spanish audio fails. + +A reasonable polling strategy is to start with a 5-second interval and back off to 30 seconds for larger files. Results expire 1 hour after the upload completes, so don't wait too long to download them. + +## Step 4: Download results + +Fetch each completed result using its `download_url`: + +```bash +# Download German plain text +curl -o translation-de.txt \ + "https://assets.deepl.com/collections/a74d88fb/assets/c3d4e5f6" + +# Download Spanish PCM audio +curl -o translation-es.pcm \ + "https://assets.deepl.com/collections/a74d88fb/assets/d4e5f6a7" +``` + +The download URL does not require your API key — authentication is embedded in the pre-signed URL itself. After you download a result, its status transitions to `downloaded` and DeepL marks the asset for deletion. Download each result only once, or save it locally before processing. + +## Putting it all together + +Here's the complete flow as a shell script: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +AUTH_KEY="YOUR_AUTH_KEY" +FILE="podcast-episode-42.mp3" +FILE_SIZE=$(wc -c < "$FILE") + +# Step 1: Create job +echo "Creating job..." +RESPONSE=$(curl -sS -X POST https://api.deepl.com/v1/jobs/voice/translate \ + -H "Authorization: DeepL-Auth-Key $AUTH_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"source_file\": { + \"name\": \"$FILE\", + \"content_type\": \"audio/mpeg\", + \"content_length\": $FILE_SIZE + }, + \"parameters\": { \"source_language\": \"en\" }, + \"targets\": [ + { \"language\": \"de\", \"type\": \"text/plain\" }, + { \"language\": \"es\", \"type\": \"audio/pcm;encoding=s16le;rate=16000\" } + ] + }") + +JOB_ID=$(echo "$RESPONSE" | jq -r '.job_id') +UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.upload_url') +echo "Job ID: $JOB_ID" + +# Step 2: Upload file +echo "Uploading audio file..." +curl -sS -X PUT "$UPLOAD_URL" \ + -H "Content-Type: audio/mpeg" \ + --data-binary @"$FILE" +echo "Upload complete." + +# Step 3: Poll until all targets are terminal +echo "Polling for results..." +while true; do + STATUS=$(curl -sS \ + -H "Authorization: DeepL-Auth-Key $AUTH_KEY" \ + "https://api.deepl.com/v1/jobs/voice/translate/$JOB_ID") + + STATUSES=$(echo "$STATUS" | jq -r '.results[].status') + + if echo "$STATUSES" | grep -qE '^processing$|^pending$|^uploaded$'; then + echo "Still processing, waiting 10s..." + sleep 10 + else + echo "All targets in terminal state." + break + fi +done + +# Step 4: Download results +echo "$STATUS" | jq -c '.results[]' | while IFS= read -r result; do + RESULT_STATUS=$(echo "$result" | jq -r '.status') + if [ "$RESULT_STATUS" = "complete" ]; then + DOWNLOAD_URL=$(echo "$result" | jq -r '.download_url') + FILENAME="result-$(echo "$DOWNLOAD_URL" | rev | cut -d/ -f1 | rev)" + echo "Downloading $FILENAME..." + curl -sS -o "$FILENAME" "$DOWNLOAD_URL" + else + echo "Target failed: $(echo "$result" | jq -r '.error.message // "unknown error"')" + fi +done + +echo "Done." +``` + +## Common issues + +**Upload returns 403 or 400**: Check that the `Content-Type` header on the PUT request matches the `content_type` you declared in the create request. Also verify you're using the full `upload_url` from the response, not a reconstructed URL. + +**Job returns 404**: Either the job ID is wrong, or the job has expired and been deleted. Jobs are deleted after all results are downloaded or the result window closes. + +**A target fails with "processing failed"**: The source audio may be corrupt, silent, or in an unsupported format. Verify the file plays correctly locally, and check the [supported source formats](/api-reference/jobs-voice-translate/reference#supported-source-audio-formats). + +**File upload window expired**: You have 5 minutes from job creation to complete the upload. If you miss it, create a new job. + +## Next steps + +- [Reference: limits, status lifecycle, and output formats](/api-reference/jobs-voice-translate/reference) — check file size and duration limits before submitting large files +- [Supported Voice Languages](/docs/voice/supported-voice-languages) — verify your target languages are available for translation +- [Create Job endpoint reference](/api-reference/jobs-voice-translate/create-voice-translate-job) — full request and response schemas +- [Get Job Status endpoint reference](/api-reference/jobs-voice-translate/get-voice-translate-job-status) — complete status response schema \ No newline at end of file