curl --request POST \
--url https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"languages": [
"cs"
]
}
'import requests
url = "https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate"
payload = { "languages": ["cs"] }
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({languages: ['cs']})
};
fetch('https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'languages' => [
'cs'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate"
payload := strings.NewReader("{\n \"languages\": [\n \"cs\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"languages\": [\n \"cs\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"languages\": [\n \"cs\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": false,
"statusCode": 400,
"message": "Bad Request",
"timestamp": "2025-01-01T00:00:00.000Z"
}{
"success": false,
"statusCode": 401,
"message": "Unauthorized",
"timestamp": "2025-01-01T00:00:00.000Z"
}{
"success": false,
"statusCode": 403,
"message": "Access denied",
"timestamp": "2025-01-01T00:00:00.000Z"
}{
"success": false,
"statusCode": 404,
"message": "Project or recording not found",
"timestamp": "2025-01-01T00:00:00.000Z"
}{
"success": false,
"statusCode": 500,
"message": "Internal server error",
"timestamp": "2025-01-01T00:00:00.000Z"
}Translate Recording
Add one or more target translation languages to a recording that already exists, without recreating the project. Use this instead of POST /v1/projects whenever the project already exists — for example the caller forgot a language at creation time, the user asks for another language later, or the caller only learns which language is needed after seeing the transcript.
Precondition: source transcription must be complete
Translation reads the recording’s transcript, so it can only run after transcription has finished. This is not enforced synchronously by this endpoint — a call made too early is accepted and queued, but produces no usable result. Before calling this endpoint, check GET /v1/recordings/{recordingId}/status?language={sourceLanguage} and confirm transcription is true (or poll GET /v1/recordings/{recordingId}/status?summary=true until status is COMPLETED).
Re-requesting a language
A language already in the recording’s translation languages is not skipped — it is retranslated (logged as a retranslation rather than an initial translation). Only include a language in languages if you actually want it (re-)translated; use ignoredLanguages to record a language as intentionally not translated without omitting it from tracking.
Language support
languages accepts any BCP-47-style code Phrase Studio has translation support for — effectively all locales except the literal auto placeholder. Do not pass the recording’s own source language: the Studio UI excludes it from the language picker, but this endpoint does not reject it — it is accepted and produces a same-language “translation”. An unsupported code is rejected with 400.
After translation completes
Call POST /v1/projects/{projectId}/recordings/{recordingId}/dubbing to dub any of the resulting languages — dubbing requires the language to be translated first.
curl --request POST \
--url https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"languages": [
"cs"
]
}
'import requests
url = "https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate"
payload = { "languages": ["cs"] }
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({languages: ['cs']})
};
fetch('https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'languages' => [
'cs'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate"
payload := strings.NewReader("{\n \"languages\": [\n \"cs\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"languages\": [\n \"cs\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.studio.us.phrase.com/v1/projects/{projectId}/recordings/{recordingId}/translate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"languages\": [\n \"cs\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": false,
"statusCode": 400,
"message": "Bad Request",
"timestamp": "2025-01-01T00:00:00.000Z"
}{
"success": false,
"statusCode": 401,
"message": "Unauthorized",
"timestamp": "2025-01-01T00:00:00.000Z"
}{
"success": false,
"statusCode": 403,
"message": "Access denied",
"timestamp": "2025-01-01T00:00:00.000Z"
}{
"success": false,
"statusCode": 404,
"message": "Project or recording not found",
"timestamp": "2025-01-01T00:00:00.000Z"
}{
"success": false,
"statusCode": 500,
"message": "Internal server error",
"timestamp": "2025-01-01T00:00:00.000Z"
}Authorizations
Body
Target languages to (re-)translate. A language already present on the recording is retranslated, not skipped — only list a language here if you want it (re-)run. Any BCP-47-style code with translation support is accepted except the literal auto placeholder. Do not pass the recording's source language — the Studio UI excludes it from the picker, but this field does not reject it.
Subset of languages to record as intentionally not translated (tracked on the recording so it shows as deliberately skipped, not pending or failed) instead of actually running translation for it
TMS project template to use if translation is routed through a connected TMS
Subtitle profile id per language code. List available ids with GET /v1/subtitle-profiles.
Show child attributes
Show child attributes
Style guide id per language code. Style guides belong to a separate Phrase system (Frame), not Phrase Studio itself — no external Studio API lists them.
Show child attributes
Show child attributes
Term base ids per language code. Term bases belong to a connected TMS, not Phrase Studio itself — no external Studio API lists them.
Show child attributes
Show child attributes
Response
Translation started for the requested languages
Was this page helpful?