# Abort multipart upload
Source: https://docs.vmeg.ai/api-reference/assets--materials/abort-multipart-upload
/api-reference/openapi.json post /openapi/v1/assets/material/upload/multipart/abort
Cancel an in-progress multipart upload. Requires [`X-Idempotency-Key`](/guides/idempotency). See [Material upload](/guides/assets/material-upload).
# Complete single-file upload
Source: https://docs.vmeg.ai/api-reference/assets/materials/complete-upload
/api-reference/openapi.json post /openapi/v1/assets/material/upload/complete
Single-file upload **step 3**: register the file after CDN PUT. Requires [`X-Idempotency-Key`](/guides/idempotency). Flow: [Material upload](/guides/assets/material-upload).
# Delete material
Source: https://docs.vmeg.ai/api-reference/assets/materials/delete-material
/api-reference/openapi.json post /openapi/v1/assets/material/delete
Logical delete of a material. Requires [`X-Idempotency-Key`](/guides/idempotency). See [Materials](/guides/assets/materials).
# Generate presigned upload URL
Source: https://docs.vmeg.ai/api-reference/assets/materials/gen-upload-url
/api-reference/openapi.json post /openapi/v1/assets/material/upload/gen-upload-url
Single-file upload **step 1**: get a presigned PUT URL and `materialId`. Upload the file to the URL, then call **Complete single-file upload**. Required before **Create translate and dub job**. See [Material upload](/guides/assets/material-upload).
# List materials
Source: https://docs.vmeg.ai/api-reference/assets/materials/list-materials
/api-reference/openapi.json get /openapi/v1/assets/material/list
Paginated library of video/audio files you uploaded for translate-and-dub jobs. Pass `materialId` to **Create translate and dub job**. See [Materials](/guides/assets/materials).
# Complete multipart upload
Source: https://docs.vmeg.ai/api-reference/assets/materials/multipart-complete
/api-reference/openapi.json post /openapi/v1/assets/material/upload/multipart/complete
Multipart upload **final step**: commit parts and register the material. Requires [`X-Idempotency-Key`](/guides/idempotency). Flow: [Material upload](/guides/assets/material-upload).
# Initiate multipart upload
Source: https://docs.vmeg.ai/api-reference/assets/materials/multipart-initiate
/api-reference/openapi.json post /openapi/v1/assets/material/upload/multipart/initiate
Multipart upload **step 1**: start upload and get `materialId` / `uploadId`. Requires [`X-Idempotency-Key`](/guides/idempotency). Flow: [Material upload](/guides/assets/material-upload).
# Presign multipart parts
Source: https://docs.vmeg.ai/api-reference/assets/materials/multipart-presign-parts
/api-reference/openapi.json post /openapi/v1/assets/material/upload/multipart/presign-parts
Multipart upload **step 2**: presigned URL per part. Requires [`X-Idempotency-Key`](/guides/idempotency). Flow: [Material upload](/guides/assets/material-upload).
# Delete cloned voice
Source: https://docs.vmeg.ai/api-reference/assets/voices/delete-cloned-voice
/api-reference/openapi.json post /openapi/v1/assets/voice/clone/delete
Logical delete of a cloned voice. Requires [`X-Idempotency-Key`](/guides/idempotency). See [Voices](/guides/assets/voices).
# List cloned voices
Source: https://docs.vmeg.ai/api-reference/assets/voices/list-cloned-voices
/api-reference/openapi.json post /openapi/v1/assets/voice/clone/list
List cloned voices created by [Voice clone](/guides/products/voice-clone). See [Voices](/guides/assets/voices).
# List system voices
Source: https://docs.vmeg.ai/api-reference/assets/voices/list-system-voices
/api-reference/openapi.json post /openapi/v1/assets/voice/basic/list
List read-only preset voices for **Synthesize speech** requests. Pick a `voiceId` from the response. See [Voices](/guides/assets/voices).
# Rename cloned voice
Source: https://docs.vmeg.ai/api-reference/assets/voices/rename-cloned-voice
/api-reference/openapi.json post /openapi/v1/assets/voice/clone/update
Rename a cloned voice display name. Requires [`X-Idempotency-Key`](/guides/idempotency). See [Voices](/guides/assets/voices).
# API overview
Source: https://docs.vmeg.ai/api-reference/introduction
What VMEG Open API does and how endpoints are organized
VMEG Open API lets you automate what users do on [vmeg.ai](https://www.vmeg.ai/home) from your own backend: **translate and dub** video or audio, **synthesize speech**, **clone voices**, and **translate plain text**.
**Video/audio translation = translate + dub.** The API transcribes speech, translates the script, generates new AI-dubbed audio (and video when applicable), and returns dubbed media files. It is **not** the same as the text-only translation API.
## URLs
| Purpose | URL |
| ----------------------------------------- | -------------------------------------------- |
| Documentation site | [https://docs.vmeg.ai](https://docs.vmeg.ai) |
| API base URL (all `/openapi/**` requests) | `https://api.vmeg.ai` |
The interactive Playground sends requests to `https://api.vmeg.ai`.
## Authentication
All `/openapi/**` endpoints require your API Key in the **`Authorization` request header**:
```http theme={null}
Authorization: Bearer
```
Enter the key in the Playground **Authorization** field to try requests.
## Scope
List, detail, and delete operations for **tasks** and **materials**, and queries for **cloned voices**, return only data tied to the API Key you authenticate with. You do not pass a project identifier in query parameters or request bodies.
## Typical translate-and-dub flow
Most integrators start here when localizing video or audio:
Finish the full **Upload files (materials)** flow to get a registered `materialId` for your video or audio. See [Material upload](/guides/assets/material-upload). Do not create a job with a mid-upload `materialId`.
Set your webhook URL on [API Configuration](https://www.vmeg.ai/open-api-setting) so dubbed results can be delivered when the job finishes.
`POST /openapi/v1/task/media-translation/create-async` — VMEG transcribes, translates, dubs, and renders output. See [Create translate and dub job](/api-reference/media-translation/create-media-translation-async).
Your webhook receives CDN URLs for the dubbed video or audio. Poll **Query job status** if you need a fallback.
## Endpoint groups
| Group | What it does | When to use |
| ----------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| **Video & audio translate and dub** | Full localization pipeline: ASR → translation → AI dubbing → rendered media | You have a video or audio file and want a dubbed version in another language |
| **Text to speech (TTS)** | Turn text into speech with preset or cloned voices | You already have a script and need audio only |
| **Voice cloning** | Clone a speaker from a short sample | You need a custom `voiceId` for TTS or dubbing |
| **Text translation (text only)** | Translate plain text segments | You have strings only — **not** spoken content in media |
| **Task status and history** | List, query, or delete jobs | Track progress, recover from missed webhooks |
| **Upload files (materials)** | Upload and manage video/audio inputs | Required before translate-and-dub jobs |
| **Voices (presets and clones)** | Browse voices available for TTS | Pick a `voiceId` before synthesizing speech |
Async **`create-async`** endpoints document the **Callbacks** webhook request body (task-specific `data` schema) on the same page.
Concept guides (idempotency, webhooks, errors, pricing) are under the **Guides** tab. Product walkthroughs: [Media translation](/guides/products/media-translation), [Products overview](/guides/products/overview).
# Create translate and dub job
Source: https://docs.vmeg.ai/api-reference/media-translation/create-media-translation-async
/api-reference/openapi.json post /openapi/v1/task/media-translation/create-async
Start an async **translate and dub** pipeline for uploaded video or audio: transcribe speech → translate script → generate AI dubbing (voice clone or presets) → render dubbed output. Optional subtitles and lip sync via `options`. The HTTP response only confirms acceptance (`taskId`). Dubbed video/audio URLs arrive in your [Webhook](/guides/webhooks) callback (`event`: `openapi-media-translation`).
If you request audio translation (`taskType`: `at`) but the material is video, the server may run video translation (`vt`) instead — check `taskType` in the acceptance response.
# Delete an API job
Source: https://docs.vmeg.ai/api-reference/tasks/delete-task
/api-reference/openapi.json post /openapi/v1/tasks/delete
Delete a task record. Requires [`X-Idempotency-Key`](/guides/idempotency). See [Products overview](/guides/products/overview).
# Query job status and results
Source: https://docs.vmeg.ai/api-reference/tasks/get-task-detail
/api-reference/openapi.json get /openapi/v1/tasks/detail
Poll status and `result` for one `taskId` — use after any `create-async`, or when webhooks are unavailable. For translate-and-dub jobs, `result` includes dubbed media CDN URLs when `status` is complete. Scoped to your **API Key**.
# List API jobs
Source: https://docs.vmeg.ai/api-reference/tasks/list-tasks
/api-reference/openapi.json get /openapi/v1/tasks/list
Paginated list of jobs you submitted (translate-and-dub, TTS, text translation, voice clone). Filter by `taskType` and `status`. Scoped to your **API Key**. Async deliverables still arrive via [Webhooks](/guides/webhooks) — use this endpoint to browse history or reconcile missed callbacks.
# Translate text segments (async)
Source: https://docs.vmeg.ai/api-reference/text-translation/create-text-translation-async
/api-reference/openapi.json post /openapi/v1/task/translate/create-async
Submit a batch text translation job. The HTTP response only confirms acceptance (`taskId`). Translated segments arrive in your [Webhook](/guides/webhooks) callback (`event`: `openapi-translate`).
# Translate text segments (sync)
Source: https://docs.vmeg.ai/api-reference/text-translation/create-text-translation-sync
/api-reference/openapi.json post /openapi/v1/task/translate/create
Translate plain text segments in one request. Translated strings are returned immediately in the HTTP `data` payload. Does **not** process video or audio — use **Create translate and dub job** for media.
# Synthesize speech (async)
Source: https://docs.vmeg.ai/api-reference/tts/synthesize-speech-async
/api-reference/openapi.json post /openapi/v1/task/tts/create-async
Submit a long or batch TTS job. The HTTP response only confirms acceptance (`taskId`). Finished audio arrives in your [Webhook](/guides/webhooks) callback (`event`: `openapi-tts`).
# Synthesize speech (sync)
Source: https://docs.vmeg.ai/api-reference/tts/synthesize-speech-sync
/api-reference/openapi.json post /openapi/v1/task/tts/create
Convert text to speech in one request. Audio URLs are returned immediately in the HTTP `data` payload — no webhook required.
# Clone voice from sample audio (async)
Source: https://docs.vmeg.ai/api-reference/voice-clone/clone-voice-from-sample-audio-async
/api-reference/openapi.json post /openapi/v1/task/clone-voice/create-async
Submit a voice cloning job. The HTTP response only confirms acceptance (`taskId`). The new `voiceId` arrives in your [Webhook](/guides/webhooks) callback (`event`: `openapi-clone-voice`).
# Clone voice from sample audio (sync)
Source: https://docs.vmeg.ai/api-reference/voice-clone/clone-voice-from-sample-audio-sync
/api-reference/openapi.json post /openapi/v1/task/clone-voice/create
Clone a speaker from a short reference recording in one request. On success, returns `voiceId` — reuse it in TTS or translate-and-dub jobs.
# Material upload
Source: https://docs.vmeg.ai/guides/assets/material-upload
Presigned and multipart CDN upload
Materials are uploaded **directly to CDN/object storage** via presigned URLs, then registered with the VMEG API. Bytes do not pass through the API gateway.
Every upload-related **POST** in this flow requires [`X-Idempotency-Key`](/guides/idempotency).
Early steps (gen-upload-url or multipart initiate) may already return a `materialId`. That ID is only for continuing the upload. Do **not** pass it to create-async or other product APIs until the full upload finishes — single-file **complete**, multipart **complete**, or an instant-upload / dedup response that already includes a registered `material`.
## Choose an upload mode
| Mode | When to use |
| ------------------------------- | ---------------------------- |
| **Single-file (presigned URL)** | Smaller files, one PUT |
| **Multipart** | Large files, resumable parts |
Request and response fields: [Generate presigned upload URL](/api-reference/assets/materials/gen-upload-url), [Complete single-file upload](/api-reference/assets/materials/complete-upload), and other **Assets - Materials → upload** operations in [API reference](/api-reference/introduction).
## Single-file upload
`POST /openapi/v1/assets/material/upload/gen-upload-url` with `fileHash` (MD5) and `fileName`.
If the response includes `data.material`, the file was deduplicated — skip PUT. Otherwise upload bytes to `data.uploadUrl`.
`POST /openapi/v1/assets/material/upload/complete` with `s3Uri`, `materialId`, `fileHash`, and `fileName` from the gen-upload-url step.
### Example
```javascript Node.js theme={null}
import crypto from "node:crypto";
import fs from "node:fs";
const API_BASE = "https://api.vmeg.ai";
const API_KEY = process.env.VMEG_API_KEY;
const FILE_PATH = "./demo.mp4";
function md5File(path) {
const hash = crypto.createHash("md5");
hash.update(fs.readFileSync(path));
return hash.digest("hex");
}
function idemKey(step) {
return `upload-${step}-${crypto.randomUUID()}`;
}
async function apiPost(path, body) {
const res = await fetch(`${API_BASE}${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"X-Idempotency-Key": idemKey(path),
},
body: JSON.stringify(body),
});
const json = await res.json();
if (json.code !== 200) throw new Error(json.message || "API error");
return json.data;
}
async function uploadSingleFile() {
const fileName = FILE_PATH.split("/").pop();
const fileHash = md5File(FILE_PATH);
const fileBytes = fs.readFileSync(FILE_PATH);
const gen = await apiPost("/openapi/v1/assets/material/upload/gen-upload-url", {
fileHash,
fileName,
});
if (gen.material?.materialId) {
console.log("Instant upload (dedup):", gen.material.materialId);
return gen.material.materialId;
}
const putRes = await fetch(gen.uploadUrl, { method: "PUT", body: fileBytes });
if (!putRes.ok) throw new Error(`CDN PUT failed: ${putRes.status}`);
const material = await apiPost("/openapi/v1/assets/material/upload/complete", {
s3Uri: gen.s3Uri,
materialId: gen.materialId,
fileHash,
fileName,
});
console.log("materialId:", material.materialId);
return material.materialId;
}
uploadSingleFile().catch(console.error);
```
```python Python theme={null}
import hashlib
import os
import uuid
import requests
API_BASE = "https://api.vmeg.ai"
API_KEY = os.environ["VMEG_API_KEY"]
FILE_PATH = "./demo.mp4"
def md5_file(path: str) -> str:
h = hashlib.md5()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def idem_key(step: str) -> str:
return f"upload-{step}-{uuid.uuid4()}"
def api_post(path: str, body: dict) -> dict:
res = requests.post(
f"{API_BASE}{path}",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"X-Idempotency-Key": idem_key(path),
},
json=body,
timeout=120,
)
res.raise_for_status()
payload = res.json()
if payload.get("code") != 200:
raise RuntimeError(payload.get("message") or "API error")
return payload["data"]
def upload_single_file() -> str:
file_name = os.path.basename(FILE_PATH)
file_hash = md5_file(FILE_PATH)
gen = api_post(
"/openapi/v1/assets/material/upload/gen-upload-url",
{"fileHash": file_hash, "fileName": file_name},
)
if gen.get("material", {}).get("materialId"):
material_id = gen["material"]["materialId"]
print("Instant upload (dedup):", material_id)
return material_id
with open(FILE_PATH, "rb") as f:
put = requests.put(gen["uploadUrl"], data=f, timeout=600)
put.raise_for_status()
material = api_post(
"/openapi/v1/assets/material/upload/complete",
{
"s3Uri": gen["s3Uri"],
"materialId": gen["materialId"],
"fileHash": file_hash,
"fileName": file_name,
},
)
material_id = material["materialId"]
print("materialId:", material_id)
return material_id
if __name__ == "__main__":
upload_single_file()
```
```java Java theme={null}
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public final class SingleMaterialUpload {
private static final String API_BASE = "https://api.vmeg.ai";
private static final String API_KEY = System.getenv("VMEG_API_KEY");
private static final Path FILE_PATH = Path.of("demo.mp4");
private static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
private static final ObjectMapper JSON = new ObjectMapper();
public static void main(String[] args) throws Exception {
String materialId = uploadSingleFile();
System.out.println("materialId: " + materialId);
}
static String uploadSingleFile() throws Exception {
String fileName = FILE_PATH.getFileName().toString();
String fileHash = md5File(FILE_PATH);
byte[] fileBytes = Files.readAllBytes(FILE_PATH);
JsonNode gen = apiPost(
"/openapi/v1/assets/material/upload/gen-upload-url",
Map.of("fileHash", fileHash, "fileName", fileName));
if (gen.hasNonNull("material") && gen.get("material").hasNonNull("materialId")) {
return gen.get("material").get("materialId").asText();
}
HttpRequest put = HttpRequest.newBuilder()
.uri(URI.create(gen.get("uploadUrl").asText()))
.PUT(HttpRequest.BodyPublishers.ofByteArray(fileBytes))
.timeout(Duration.ofMinutes(10))
.build();
HttpResponse putRes = HTTP.send(put, HttpResponse.BodyHandlers.discarding());
if (putRes.statusCode() < 200 || putRes.statusCode() >= 300) {
throw new IOException("CDN PUT failed: " + putRes.statusCode());
}
JsonNode material = apiPost(
"/openapi/v1/assets/material/upload/complete",
Map.of(
"s3Uri", gen.get("s3Uri").asText(),
"materialId", gen.get("materialId").asText(),
"fileHash", fileHash,
"fileName", fileName));
return material.get("materialId").asText();
}
static JsonNode apiPost(String path, Map body) throws Exception {
String bodyJson = JSON.writeValueAsString(body);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + path))
.timeout(Duration.ofSeconds(120))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.header("X-Idempotency-Key", "upload-" + path + "-" + UUID.randomUUID())
.POST(HttpRequest.BodyPublishers.ofString(bodyJson))
.build();
HttpResponse res = HTTP.send(req, HttpResponse.BodyHandlers.ofString());
JsonNode root = JSON.readTree(res.body());
if (root.path("code").asInt() != 200) {
throw new IOException(root.path("message").asText("API error"));
}
return root.get("data");
}
static String md5File(Path path) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(Files.readAllBytes(path));
StringBuilder sb = new StringBuilder();
for (byte b : md5.digest()) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
```
The Java example uses Jackson (`ObjectMapper`) for JSON. Add `com.fasterxml.jackson.core:jackson-databind` to your project, or parse responses with your preferred JSON library.
## Multipart upload
For large files:
1. `POST .../multipart/initiate`
2. `POST .../multipart/presign-parts` — presigned URL per part
3. PUT each part to CDN and record each part `ETag`
4. `POST .../multipart/complete` — finalize and get `materialId`
5. On failure: `POST .../multipart/abort`
Use `recommendedPartSize` from the initiate response (S3 minimum part size is typically 5 MB except the last part).
### Example
```javascript Node.js theme={null}
import crypto from "node:crypto";
import fs from "node:fs";
const API_BASE = "https://api.vmeg.ai";
const API_KEY = process.env.VMEG_API_KEY;
const FILE_PATH = "./large.mp4";
function md5File(path) {
const hash = crypto.createHash("md5");
hash.update(fs.readFileSync(path));
return hash.digest("hex");
}
function idemKey(step) {
return `multipart-${step}-${crypto.randomUUID()}`;
}
async function apiPost(path, body) {
const res = await fetch(`${API_BASE}${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"X-Idempotency-Key": idemKey(path),
},
body: JSON.stringify(body),
});
const json = await res.json();
if (json.code !== 200) throw new Error(json.message || "API error");
return json.data;
}
function readPart(buffer, offset, size) {
return buffer.subarray(offset, Math.min(offset + size, buffer.length));
}
async function uploadMultipart() {
const fileName = FILE_PATH.split("/").pop();
const fileHash = md5File(FILE_PATH);
const fileBytes = fs.readFileSync(FILE_PATH);
const init = await apiPost("/openapi/v1/assets/material/upload/multipart/initiate", {
fileHash,
fileName,
});
const partSize = init.recommendedPartSize || 8 * 1024 * 1024;
const partNumbers = [];
for (let offset = 0, n = 1; offset < fileBytes.length; offset += partSize, n++) {
partNumbers.push(n);
}
const presign = await apiPost(
"/openapi/v1/assets/material/upload/multipart/presign-parts",
{
materialId: init.materialId,
uploadId: init.uploadId,
fileHash,
fileName,
partNumbers,
}
);
const parts = [];
for (const { partNumber, url } of presign.urls) {
const start = (partNumber - 1) * partSize;
const chunk = readPart(fileBytes, start, partSize);
const putRes = await fetch(url, { method: "PUT", body: chunk });
if (!putRes.ok) throw new Error(`Part ${partNumber} PUT failed: ${putRes.status}`);
const eTag = (putRes.headers.get("etag") || "").replace(/"/g, "");
parts.push({ partNumber, eTag });
}
const material = await apiPost(
"/openapi/v1/assets/material/upload/multipart/complete",
{
materialId: init.materialId,
uploadId: init.uploadId,
fileHash,
fileName,
parts,
}
);
console.log("materialId:", material.materialId);
return material.materialId;
}
uploadMultipart().catch(console.error);
```
```python Python theme={null}
import hashlib
import math
import os
import uuid
import requests
API_BASE = "https://api.vmeg.ai"
API_KEY = os.environ["VMEG_API_KEY"]
FILE_PATH = "./large.mp4"
def md5_file(path: str) -> str:
h = hashlib.md5()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def idem_key(step: str) -> str:
return f"multipart-{step}-{uuid.uuid4()}"
def api_post(path: str, body: dict) -> dict:
res = requests.post(
f"{API_BASE}{path}",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"X-Idempotency-Key": idem_key(path),
},
json=body,
timeout=120,
)
res.raise_for_status()
payload = res.json()
if payload.get("code") != 200:
raise RuntimeError(payload.get("message") or "API error")
return payload["data"]
def upload_multipart() -> str:
file_name = os.path.basename(FILE_PATH)
file_hash = md5_file(FILE_PATH)
file_size = os.path.getsize(FILE_PATH)
init = api_post(
"/openapi/v1/assets/material/upload/multipart/initiate",
{"fileHash": file_hash, "fileName": file_name},
)
part_size = init.get("recommendedPartSize") or 8 * 1024 * 1024
part_count = max(1, math.ceil(file_size / part_size))
part_numbers = list(range(1, part_count + 1))
presign = api_post(
"/openapi/v1/assets/material/upload/multipart/presign-parts",
{
"materialId": init["materialId"],
"uploadId": init["uploadId"],
"fileHash": file_hash,
"fileName": file_name,
"partNumbers": part_numbers,
},
)
parts = []
with open(FILE_PATH, "rb") as f:
for item in presign["urls"]:
part_number = item["partNumber"]
f.seek((part_number - 1) * part_size)
chunk = f.read(part_size)
put = requests.put(item["url"], data=chunk, timeout=600)
put.raise_for_status()
e_tag = put.headers.get("ETag", "").strip('"')
parts.append({"partNumber": part_number, "eTag": e_tag})
material = api_post(
"/openapi/v1/assets/material/upload/multipart/complete",
{
"materialId": init["materialId"],
"uploadId": init["uploadId"],
"fileHash": file_hash,
"fileName": file_name,
"parts": parts,
},
)
material_id = material["materialId"]
print("materialId:", material_id)
return material_id
if __name__ == "__main__":
upload_multipart()
```
```java Java theme={null}
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public final class MultipartMaterialUpload {
private static final String API_BASE = "https://api.vmeg.ai";
private static final String API_KEY = System.getenv("VMEG_API_KEY");
private static final Path FILE_PATH = Path.of("large.mp4");
private static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
private static final ObjectMapper JSON = new ObjectMapper();
public static void main(String[] args) throws Exception {
System.out.println("materialId: " + uploadMultipart());
}
static String uploadMultipart() throws Exception {
String fileName = FILE_PATH.getFileName().toString();
String fileHash = md5File(FILE_PATH);
long fileSize = java.nio.file.Files.size(FILE_PATH);
JsonNode init = apiPost(
"/openapi/v1/assets/material/upload/multipart/initiate",
Map.of("fileHash", fileHash, "fileName", fileName));
long partSize = init.has("recommendedPartSize")
? init.get("recommendedPartSize").asLong()
: 8L * 1024 * 1024;
int partCount = (int) Math.max(1, (fileSize + partSize - 1) / partSize);
List partNumbers = new ArrayList<>();
for (int i = 1; i <= partCount; i++) {
partNumbers.add(i);
}
ObjectNode presignBody = JSON.createObjectNode();
presignBody.put("materialId", init.get("materialId").asText());
presignBody.put("uploadId", init.get("uploadId").asText());
presignBody.put("fileHash", fileHash);
presignBody.put("fileName", fileName);
ArrayNode nums = presignBody.putArray("partNumbers");
partNumbers.forEach(nums::add);
JsonNode presign = apiPost(
"/openapi/v1/assets/material/upload/multipart/presign-parts", presignBody);
ArrayNode completedParts = JSON.createArrayNode();
try (RandomAccessFile raf = new RandomAccessFile(FILE_PATH.toFile(), "r")) {
for (JsonNode item : presign.get("urls")) {
int partNumber = item.get("partNumber").asInt();
long offset = (long) (partNumber - 1) * partSize;
int len = (int) Math.min(partSize, fileSize - offset);
byte[] chunk = new byte[len];
raf.seek(offset);
raf.readFully(chunk);
HttpRequest put = HttpRequest.newBuilder()
.uri(URI.create(item.get("url").asText()))
.PUT(HttpRequest.BodyPublishers.ofByteArray(chunk))
.timeout(Duration.ofMinutes(10))
.build();
HttpResponse putRes = HTTP.send(put, HttpResponse.BodyHandlers.ofString());
if (putRes.statusCode() < 200 || putRes.statusCode() >= 300) {
throw new IOException("Part PUT failed: " + putRes.statusCode());
}
String eTag = putRes.headers().firstValue("etag")
.orElse("").replace("\"", "");
ObjectNode part = JSON.createObjectNode();
part.put("partNumber", partNumber);
part.put("eTag", eTag);
completedParts.add(part);
}
}
ObjectNode completeBody = JSON.createObjectNode();
completeBody.put("materialId", init.get("materialId").asText());
completeBody.put("uploadId", init.get("uploadId").asText());
completeBody.put("fileHash", fileHash);
completeBody.put("fileName", fileName);
completeBody.set("parts", completedParts);
JsonNode material = apiPost(
"/openapi/v1/assets/material/upload/multipart/complete", completeBody);
return material.get("materialId").asText();
}
static JsonNode apiPost(String path, Object body) throws Exception {
String bodyJson = body instanceof String ? (String) body : JSON.writeValueAsString(body);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + path))
.timeout(Duration.ofSeconds(120))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.header("X-Idempotency-Key", "multipart-" + path + "-" + UUID.randomUUID())
.POST(HttpRequest.BodyPublishers.ofString(bodyJson))
.build();
HttpResponse res = HTTP.send(req, HttpResponse.BodyHandlers.ofString());
JsonNode root = JSON.readTree(res.body());
if (root.path("code").asInt() != 200) {
throw new IOException(root.path("message").asText("API error"));
}
return root.get("data");
}
static JsonNode apiPost(String path, Map body) throws Exception {
return apiPost(path, (Object) JSON.valueToTree(body));
}
static String md5File(Path path) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(java.nio.file.Files.readAllBytes(path));
StringBuilder sb = new StringBuilder();
for (byte b : md5.digest()) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
```
## After upload
Only after the full upload flow succeeds, use the registered `materialId` in `source.materialId` for [media translation](/guides/products/media-translation). Manage assets via [Materials](/guides/assets/materials).
# Materials
Source: https://docs.vmeg.ai/guides/assets/materials
Video and audio assets for Open API tasks
## What a material is
A **material** is a registered video or audio file stored for Open API use. After the full [material upload](/guides/assets/material-upload) flow completes, you get a usable **`materialId`** to pass to tasks such as [media translation](/guides/products/media-translation).
Do not use a `materialId` from an unfinished upload (for example, only gen-upload-url or multipart initiate). Materials are API-only — not shared with uploads on vmeg.ai. See [Assets overview](/guides/assets/overview).
## Material type
The `category` field on a material describes the asset type, for example:
| `category` | Typical use |
| ---------- | ---------------------------------- |
| `video` | Video translation (`taskType: vt`) |
| `audio` | Audio translation (`taskType: at`) |
Other fields on a material include `name`, CDN URLs (`addressUrl`, `thumbnailUrl`, `audioUrl`), `mimeType`, `duration`, and `filesize`. For every field and type, see the response body in [List materials](/api-reference/assets/materials/list-materials).
## Lifecycle
1. Finish the full [upload](/guides/assets/material-upload) flow → registered `materialId`
2. Reference that `materialId` in a product request
3. **List** or **delete** when no longer needed
## Operations
| Action | Endpoint |
| ------ | ----------------------------------------- |
| List | `GET /openapi/v1/assets/material/list` |
| Delete | `POST /openapi/v1/assets/material/delete` |
Upload flows are on [Material upload](/guides/assets/material-upload).
Request and response fields: [List materials](/api-reference/assets/materials/list-materials), [Delete material](/api-reference/assets/materials/delete-material), and other **Assets - Materials** endpoints under the [API reference](/api-reference/introduction) tab.
# Assets overview
Source: https://docs.vmeg.ai/guides/assets/overview
API-scoped materials and voices
Open API **assets** are resources you create and reference by ID in task requests. There are two types:
| Asset | ID field | Purpose |
| ------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **Material** | `materialId` | Video or audio used as input (mainly media translation). Usable only after the full [upload](/guides/assets/material-upload) flow completes |
| **Voice** | `voiceId` | System preset or cloned voice used in TTS |
## API-only storage
Materials and voices created through the API are not shared with files or voices you manage on [vmeg.ai](https://www.vmeg.ai/home). Use `/openapi/v1/assets/**` only. API tasks are separate too; see [Products overview](/guides/products/overview).
List and delete operations return only assets tied to your **API Key**. Voice list endpoints use the same scope.
## Typical flow
```text theme={null}
Complete full material upload → registered materialId → media translation task
Voice clone task → voiceId → TTS task
List basic voices → voiceId → TTS task
```
## Guides
Material types, list, and delete
Presigned and multipart upload
System and cloned voices
Request and response fields: [Assets - Materials](/api-reference/assets/materials/list-materials) and [Assets - Voices](/api-reference/assets/voices/list-system-voices) in API reference.
# Voices
Source: https://docs.vmeg.ai/guides/assets/voices
System and cloned voices for TTS
Voices are API-only — referenced by `voiceId` in TTS and media translation; not shared with voices on vmeg.ai. See [Assets overview](/guides/assets/overview).
## Two voice types
| Type | Source | API access |
| ------------------ | -------------------------------------------------------- | -------------------------------------------------------------- |
| **System (basic)** | Platform presets | `POST /openapi/v1/assets/voice/basic/list` |
| **Cloned** | [Voice clone](/guides/products/voice-clone) task success | `POST /openapi/v1/assets/voice/clone/list`, `update`, `delete` |
## `voiceId` and `provider`
Each voice in list or task responses includes:
| Field | System presets | Cloned voices |
| -------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| **`voiceId`** | Stable ID from [List system voices](/api-reference/assets/voices/list-system-voices) (e.g. `sv_…`) | Stable ID from clone list or clone task (e.g. `cv_…`) |
| **`provider`** | Preset tier code `S1`–`S7` | Clone engine `V1`–`V5` |
**Use the values exactly as the API returns them** — do not build or guess IDs.
* For [Text to speech](/guides/products/text-to-speech), **`voiceId` is enough**; do not send `provider` on TTS requests.
* For [Media translation](/guides/products/media-translation) webhooks, translated lines may include both fields. **Keep `voiceId` and `provider` together** when you store or edit a line for later updates.
Preset tiers use codes **`S1`–`S7`**; clone engines use **`V1`–`V5`**. Copy values from API responses — do not guess or construct them.
## System voices
List available preset voices and pick a `voiceId` for [Text to speech](/guides/products/text-to-speech).
Filter options and response fields: [List system voices](/api-reference/assets/voices/list-system-voices).
Use **`displayName`** and **`localName`** for UI labels.
**`cover`** and **`audio`** are HTTPS preview URLs you can fetch directly. Use each URL exactly as returned.
## Cloned voices
When a voice clone task succeeds (sync response or async webhook), VMEG persists a clone with a new **`voiceId`**. Use it in subsequent TTS calls.
You can rename or delete clones via the clone asset endpoints. Request and response fields: [List cloned voices](/api-reference/assets/voices/list-cloned-voices), [Rename cloned voice](/api-reference/assets/voices/rename-cloned-voice), and [Delete cloned voice](/api-reference/assets/voices/delete-cloned-voice).
## Typical flow
```text theme={null}
List basic voices → copy voiceId → TTS
or
Voice clone → voiceId in webhook/sync response → TTS
```
Supported languages for synthesis: [Supported languages](/guides/supported-languages).
# API Key
Source: https://docs.vmeg.ai/guides/authentication
Authenticate Open API requests with an API Key
## API Key
All endpoints under `/openapi/v1/**` require an **API Key**.
## Request header
Send your key on **every** request in the HTTP **Authorization** header (not in the query string or request body):
```http theme={null}
Authorization: Bearer
```
Example with `curl`:
```bash theme={null}
curl "https://api.vmeg.ai/openapi/v1/tasks/list" \
-H "Authorization: Bearer YOUR_API_KEY"
```
Replace `YOUR_API_KEY` with the key from API Configuration.
## Scope
Your API Key determines which **tasks**, **materials**, and **cloned voices** you can list, query, and delete. Send only the fields documented for each endpoint.
Do not expose API Keys in client apps or public repositories. Store keys in environment variables or a secrets manager.
## Create and rotate keys
Create and rotate keys on API Configuration on vmeg.ai (sidebar **API** after you sign in). Sign in if needed.
The same page is where you set your **Webhook URL** and view the **Webhook Secret** VMEG generates for async callbacks.
API Configuration is available on paid plans. See [Pricing](/guides/pricing).
## Authentication failures
HTTP status `401` with a JSON body such as:
```json theme={null}
{
"code": 401,
"message": "Invalid or missing API Key",
"data": null
}
```
See [Error codes](/guides/errors) for the full code list.
# Error codes
Source: https://docs.vmeg.ai/guides/errors
Business codes in Open API responses
The Open API uses the JSON **`code`** field for business outcomes. HTTP status handles transport (for example `401` for auth failures), but success and failure semantics are defined by `code`.
## Code reference
| code | Name | Description |
| ---- | --------------------- | ------------------------------------ |
| 200 | Success | Request succeeded |
| 400 | Bad Request | Invalid or failed validation |
| 401 | Unauthorized | API Key missing or invalid |
| 402 | Payment Required | Insufficient credits or quota |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Idempotency key already used |
| 413 | Payload Too Large | Upload or body exceeds limit |
| 422 | Unprocessable Entity | Valid format but cannot be processed |
| 500 | Internal Server Error | Server error |
| 502 | Bad Gateway | Upstream processing failure |
## Response shape
```json theme={null}
{
"code": 402,
"message": "Insufficient quota",
"data": null
}
```
* Success: `code === 200`, payload in `data`
* Failure: human-readable `message` for logs and support
## Common cases
Returned when the same `X-Idempotency-Key` is reused on a mutating POST endpoint within the idempotency TTL. The response may include `Retry-After: 2`. See [Idempotency](/guides/idempotency).
Returned when a task cannot deduct project credits (TTS, translation, voice clone, media translation). See [Pricing](/guides/pricing) or add credits on vmeg.ai.
An upstream service failed or timed out. Poll `GET /openapi/v1/tasks/detail` with the `taskId` (your task list on vmeg.ai does not include Open API jobs). If a webhook is configured, wait for retries.
Returned when `data[].voiceId` fails batch rules. Common `message` patterns:
* **Unknown voiceId** — `voiceId not found in system or clone voice library: data[0] ... Use POST /openapi/v1/assets/voice/basic/list for system voices or POST /openapi/v1/assets/voice/clone/list for clone voices.`
* **Mixed voices in one batch** — `all voiceId in one batch must share the same provider, but found multiple: S1, S2. Please use voices from the same provider, or split into separate requests.`
* **`timbreRefAudio` with system preset** — `timbreRefAudio requires a clone provider (V1-V5), but voiceId in this batch resolved to a system provider (S1). ...`
See [Text to speech](/guides/products/text-to-speech) and [Supported clone methods](/guides/supported-clone-methods#text-to-speech-voiceid-and-provider).
# Idempotency key
Source: https://docs.vmeg.ai/guides/idempotency
Prevent duplicate writes with X-Idempotency-Key on POST requests
## Why use it
Retries after a timeout or ambiguous `5xx` are common. Without an idempotency key, the server may treat each retry as a **new** request — duplicate tasks, extra credits, or duplicate webhooks.
`X-Idempotency-Key` marks one logical submission. Under your API Key, the same path + key is accepted only once within the TTL (default **24 hours**). **Retry the same intent with the same key**; start a new job with a **new key**.
Idempotency prevents **duplicate submissions**, not two different jobs.
## What to send
Add the header on **every `POST` request** to the Open API (`/openapi/v1/**`):
```http theme={null}
X-Idempotency-Key:
```
Rules:
* Non-empty after trim, max length **64**
* Generate the key **before** the first attempt and keep it for safe retries
* Do not reuse a key for a different payload — you will get `409 Conflict`
## If the key was already used
* HTTP `409 Conflict`
* Header `Retry-After: 2`
* Body: `code: 409`, `message` contains `Idempotency key already used`
## Safe retry vs new work
| Situation | Key |
| --------------------------------- | -------------- |
| First submit | New unique key |
| Retry after timeout (same intent) | **Same key** |
| New task or new write | **New key** |
## Example
```bash theme={null}
export IDEM_KEY="job-$(date +%s)-$RANDOM"
curl -X POST "https://api.vmeg.ai/openapi/v1/task/tts/create" \
-H "Authorization: Bearer $API_KEY" \
-H "X-Idempotency-Key: $IDEM_KEY" \
-H "Content-Type: application/json" \
-d @payload.json
```
If the result is unclear, retry with the **same key**. Do not generate a new key on every retry, or you risk duplicate writes.
# VMEG Open API
Source: https://docs.vmeg.ai/guides/introduction
Integrate TTS, translation, media localization, voice cloning, and asset management
Use VMEG Open API to run speech and translation workloads from your own backend: submit jobs, work with API-side files and voices, and get long-running results through webhooks when needed.
## What you can do
Translate and dub video or audio you upload.
Turn text into speech.
Clone a voice from a sample and reuse it.
Translate text in batches.
Store video and audio files for translation jobs.
Pick preset or cloned voices for speech synthesis.
Field-level request and response shapes live in [API reference](/api-reference/introduction).
## Where to start
Your first API call (synchronous TTS).
Paid access and credits.
Create a key and call the API.
Manually connect an MCP client with your API Key.
Tasks, sync vs async, and how results are delivered.
# Overview
Source: https://docs.vmeg.ai/guides/mcp/overview
Connect MCP-compatible AI agents to VMEG media translation
Connect any **MCP-compatible** AI agent to VMEG by **manually** adding the remote MCP endpoint and your API Key. Your agent can list materials, create translation tasks, edit drafts, and poll job status — using the same account credits as the website.
**Marketplace / app-store connectors are not supported yet.** Use [manual setup](/guides/mcp/setup) with an API Key for now.
## Remote MCP endpoint
Point your MCP client at:
```text theme={null}
{mcpUrl}
```
The server uses **Streamable HTTP** (Spring AI MCP Server, sync tools).
## Authentication
**Recommended today:** if your client supports a Bearer token, send your API Key on every MCP request (same key as the [Open API](/guides/authentication)):
```http theme={null}
Authorization: Bearer YOUR_API_KEY
```
Create and rotate keys on API Configuration.
| Mode | Status | Notes |
| -------------------- | ------------------- | --------------------------------------------------------------------------------------------------------- |
| **API Key (Bearer)** | Supported | Manual MCP config in Cursor or any compatible client. Key from API Configuration. |
| **OAuth 2.1 (PKCE)** | Available on server | For future connector flows; not needed for manual setup. |
You do **not** pass `projId` in MCP tool arguments. The authenticated project comes from your API Key (or OAuth token when using connectors later).
## What you can do
Once connected, your agent can call tools for:
* **Materials** — list, upload (`POST /api/mcp/material/upload`), delete
* **Tasks** — create video/audio/subtitle translation, list and poll status, delete
* **Voices** — list system and cloned voices for manual voice selection
* **Editor workflows** — query scripts/subtitles/ASR/speakers/tracks, save draft edits, re-translate, trigger TTS, compose final render
See the full [MCP tools reference](/guides/mcp/tools) for every tool name and description.
## Typical flow
Add `{mcpUrl}` to your MCP client and set `Authorization: Bearer` plus your API Key. See [Setup](/guides/mcp/setup).
Upload a file (`POST /api/mcp/material/upload`), pick from `vmeg_list_materials`, or pass a YouTube URL.
Call `vmeg_create_video_translation_task` or `vmeg_create_subtitle_translation_task` with `targetLanguage` and a media source.
Use `vmeg_list_tasks` and editor tools to query content, save draft changes, re-dub, or compose the final output.
Tasks are **asynchronous**. Creating a job returns a `taskId` and `editorUrl`; poll until the task finishes.
## Remote MCP vs Open API
| | Remote MCP | [Open API](/guides/introduction) |
| -------- | ------------------------------------------------ | ------------------------------------ |
| Setup | Manual MCP URL + API Key | API Key only |
| Host | `www.vmeg.ai` (`/api/mcp`) | `api.vmeg.ai` (`/openapi/v1/**`) |
| Best for | AI agents (conversational workflows) | Your backend (REST integration) |
| Billing | Account credits (same plan) | Account credits (same plan) |
Both access the same VMEG project, materials, and tasks.
## FAQ
**Do I need a separate MCP API Key?**\
No. Reuse the same API Key as the Open API — send it as the Bearer token.
**Can I install VMEG from the Cursor marketplace or ChatGPT app store?**\
Not yet. Use [manual setup](/guides/mcp/setup) until marketplace connectors are published.
**Does MCP cost extra?**\
No. Tool calls consume the same credits as tasks created on vmeg.ai. See [Pricing](/guides/pricing).
**Can I use MCP and the Open API together?**\
Yes. They operate on the same project scope; choose MCP for agents and REST for programmatic backends.
**Why does the agent ask me to confirm before delete or compose?**\
Delete tools (`vmeg_delete_material`, `vmeg_delete_task`) and destructive editor actions require explicit user confirmation by design.
# Setup
Source: https://docs.vmeg.ai/guides/mcp/setup
Manually connect VMEG Remote MCP with an API Key and endpoint URL
Today, connect VMEG Remote MCP by **manually** adding the server URL and your API Key. One-click marketplace or app-store connectors are **not supported yet**.
## Manual setup (recommended)
Create a key on API Configuration (sign in if needed). You need a paid plan with credits — see [Pricing](/guides/pricing).
In your MCP client, add a **custom / remote** MCP server (not a marketplace listing):
* **URL:** `{mcpUrl}`
* **Auth:** `Authorization: Bearer YOUR_API_KEY` (use the key from API Configuration)
Restart or reload your editor so the new MCP server is picked up.
Ask the agent to list materials (for example: "List my VMEG materials"). It should call `vmeg_list_materials`.
### Cursor example
Add to your project or user MCP config (`.cursor/mcp.json`):
```json theme={null}
{
"mcpServers": {
"VMEG": {
"url": "https://www.vmeg.ai/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
Reload Cursor (**Cmd+Shift+P** → **Developer: Reload Window**). In chat, confirm the **VMEG** MCP server is connected before calling tools.
Do not commit API Keys to git. Use environment-specific config or a local-only `mcp.json` entry.
### Other MCP clients
Any client that supports **Streamable HTTP** remote MCP can use the same values:
| Field | Value |
| -------------------- | --------------------- |
| Server URL | `{mcpUrl}` |
| Authorization header | `Bearer YOUR_API_KEY` |
The API Key binds to one VMEG project — the same scope as [Open API](/guides/authentication). You do **not** pass `projId` in tool arguments.
## Material upload (HTTP)
Create-task tools accept `materialId`, `sourceUrl`, or `youtubeUrl`. To upload a file before creating a task:
```bash theme={null}
curl -X POST "{mcpMaterialUploadUrl}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@/path/to/video.mp4"
```
The response `data.id` is the `materialId` for `vmeg_create_video_translation_task` or `vmeg_create_subtitle_translation_task`.
## OAuth (connectors, advanced)
VMEG also exposes **OAuth 2.1 (PKCE)** for clients that cannot embed an API Key (for example, future ChatGPT Apps / Connector flows). This path is **not** required for manual setup above.
| Endpoint | Purpose |
| ------------------------------------------------------ | ----------------------------- |
| `{mcpOAuthUrl}/.well-known/oauth-authorization-server` | Authorization server metadata |
| `{mcpUrl}/.well-known/oauth-protected-resource` | Protected resource metadata |
Resolved issuer for tokens: `{mcpOAuthUrl}`.
## Marketplace and app-store connectors
**Not supported yet.** Do not rely on Cursor marketplace listings or ChatGPT Connector one-click install until VMEG publishes them. Use [manual setup](#manual-setup-recommended) with your API Key instead.
When marketplace support ships, this page will add product-specific steps. Until then, only custom / remote MCP configuration is documented.
## Troubleshooting
| Symptom | What to check |
| ----------------------------------- | --------------------------------------------------------------------------------------------- |
| MCP server not listed | Reload the client after editing `mcp.json`. |
| Tool returns auth error | API Key is valid and sent as `Bearer` on every MCP request. |
| `vmeg_create_*` fails with no media | Upload, list materials, pass `sourceUrl`, or `youtubeUrl` first. |
| Wrong project data | Create a new API Key under the intended project on API Configuration. |
See [Error codes](/guides/errors) for Open API-style failures surfaced through tools.
# Tools reference
Source: https://docs.vmeg.ai/guides/mcp/tools
All VMEG Remote MCP tools exposed at /api/mcp
VMEG registers **sync MCP tools** on the Remote MCP server. Tool names are stable (`vmeg_*`). Unless noted, **do not pass `projId`** — the authenticated OAuth session or API Key supplies the project.
Field-level JSON schemas are returned by the MCP `tools/list` response. This page summarizes names and behavior. REST equivalents (where they exist) live in [API reference](/api-reference/introduction).
## Materials
| Tool | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vmeg_list_materials` | List uploaded video/audio materials in your project. Returns `materialId`, name, category, URLs, and metadata. Supports filters (`category`, `keyword`, `materialId`, `mimeType`) and pagination. |
| `vmeg_delete_material` | Delete an uploaded material by `materialId`. **Requires explicit user confirmation** before the agent calls this tool. |
**HTTP upload (not an MCP tool):** `POST {mcpMaterialUploadUrl}` with `multipart/form-data` file field. Returns `materialId` for create-task tools.
## Voices
| Tool | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------- |
| `vmeg_list_basic_voices` | List system (library) voices. Filter by locale (BCP-47, e.g. `en-US`). Use with manual voice selection. |
| `vmeg_list_clone_voices` | List your cloned voices (`voiceId`, provider, preview URLs). Use with `voiceGenerationMode=manual`. |
## Tasks — create and manage
| Tool | Description |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vmeg_create_video_translation_task` | Create a **video or audio translation** task. Requires `targetLanguage` and one media source: `materialId`, `sourceUrl` (HTTPS direct link, e.g. ChatGPT attachment), or `youtubeUrl` (video only). Product is auto-detected from material category. Ask the user for `voiceGenerationMode` (`clone`, `clone-accent` default, `smart`, `manual`). Returns `taskId`, `status`, `product`, `editorUrl`. |
| `vmeg_create_subtitle_translation_task` | Create a **subtitle translation** task. Modes: preview with media + `targetLanguage`; merge with `subtitleType`; render/export with `taskId` + `translatedScripts`. YouTube imports may return `pending` while download runs asynchronously. |
| `vmeg_list_tasks` | List or poll tasks (lite mode, no heavy config JSON). |
| `vmeg_delete_task` | Delete a task by `taskId`. **Requires explicit user confirmation** before calling. |
## Tasks — editor queries
All query tools take `taskId` and optional filters. Times are on the **post-translation** timeline unless noted.
| Tool | Description |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `vmeg_query_task_scripts` | Query translated script lines (`content.translated_scripts`). Filter by keywords, time range, index range, `speakerId`. |
| `vmeg_query_task_subtitles` | Query subtitle clips (`content.subtitle_clips`). |
| `vmeg_query_task_asr` | Query ASR lines for transcription tasks (`content.asr_scripts`). |
| `vmeg_query_task_speakers` | Query speakers (`content.voice_match`, or distinct script speakers). |
| `vmeg_query_task_tracks` | Query video/audio track blocks (`content.build_tracks`). Filter by `kind` (`audio` / `video`). |
## Tasks — editor mutations
| Tool | Description |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `vmeg_save_task_draft` | Apply draft edits in one transactional call. Pass `ops`: an array of typed operations (`entity`, `action`, `value`, …). All-or-nothing. |
| `vmeg_retranslate_task_scripts` | Re-translate all scripts for an existing task. **Confirm with the user** before calling. Poll with `vmeg_get_retranslate_status`. |
| `vmeg_trigger_video_translation_all_tts` | Re-dub all (or selected) translated scripts. **Confirm with the user** before calling. Poll with `vmeg_get_tts_status`. |
| `vmeg_compose_task_draft` | Render (compose) the saved draft into a paid export job. **Confirm with the user** before calling. Returns a new render `taskId`. |
| `vmeg_get_retranslate_status` | Poll whole-task re-translation status. |
| `vmeg_get_tts_status` | Poll per-script TTS dubbing status. |
## Agent behavior (server instructions)
The MCP server injects instructions for connected clients. Highlights:
* Never pass `projId` in tool arguments.
* Do not call create-task tools without a media source — ask how the user wants to provide media first.
* Confirm before delete, compose, re-translate, or bulk TTS.
* Tasks are asynchronous; poll `vmeg_list_tasks` and editor query tools until work completes.
## OAuth challenge
If a tool is called without a valid Bearer token, the server returns an MCP error with `_meta` OAuth challenge headers so clients (e.g. ChatGPT Apps SDK) can start login. After authorization, retry the tool call.
# Pricing
Source: https://docs.vmeg.ai/guides/pricing
Credits, paid access, and API billing
## Who can use the API
The Open API is available to **paid users**. Free accounts are prompted to upgrade before accessing API Configuration.
**Plans, prices, and credit packs** — how much money buys how many credits, subscription tiers, and top-up options are on the product Pricing page. This guide only explains **how Open API tasks consume credits**.
## Open API credit consumption
Credits are deducted when a billable task is **accepted and processed** (sync `create` or async `create-async`). Rates below apply to Open API products; they follow the same product billing rules as the VMEG app unless noted otherwise.
| Open API product | Endpoints | Charging unit | Credits |
| ------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------- |
| [Media translation](/guides/products/media-translation) | `POST /openapi/v1/task/media-translation/create-async` | Per minute of source media duration | **60 credits / min** |
| [Text to speech](/guides/products/text-to-speech) | `POST /openapi/v1/task/tts/create`, `.../create-async` | Per character length of input text | **\~2 credits per 200 characters** |
| [Voice clone](/guides/products/voice-clone) | `POST /openapi/v1/task/clone-voice/create`, `.../create-async` | Per successful clone | **10 credits per voice** |
| [Text translation](/guides/products/text-translation) | `POST /openapi/v1/task/translate/create`, `.../create-async` | Per 100 characters of source text (sum of all `segments[].text`) | **1 credit / 100 characters** |
### Per-minute products
For **media translation**, duration **under one minute is billed as one full minute**, consistent with other VMEG per-minute features.
**Media translation** (`vt` / `at`) charges from the uploaded material length. Optional pipeline features (for example **lip sync**) can add credits on top of the base translation rate — for example, lip sync is commonly **+60 credits / min** in addition to translation. The exact total depends on your `options` in the create request; insufficient balance returns `402` before or at acceptance.
**Text translation** totals scale with character count: each billing unit is **100 characters** of source text (all segments combined), rounded **up** to the next unit. For example, 1–100 characters cost **1 credit**, 101–200 cost **2 credits**. The per-unit rate defaults to **1 credit** and may be adjusted in product configuration.
**Text to speech** totals scale with character count (roughly 2 credits per 200 characters; partial blocks follow product rounding).
**Voice clone** charges once per successful clone job (sync or async), not per subsequent TTS use of that `voiceId`.
### What does not consume task credits here
Open API **asset** calls (material upload URLs, material list/delete, voice list/update/delete) are supporting operations for tasks above. They are not listed as separate per-call credit lines in this table.
## Insufficient credits
When credits are insufficient, the API returns `402 Payment Required`. See [Error codes](/guides/errors).
## Website and API billing
Your account shares one credit balance. Usage from the website and from the Open API may appear on separate lines in history. Track API tasks only via [task APIs](/guides/products/overview) — they are not listed in your task history on vmeg.ai.
## Buy credits and view prices
To see **subscription prices**, **how many credits you get per plan**, and **how to purchase more credits**, go to Pricing on the product site. For enterprise plans, contact us.
More examples (lip sync totals, subtitle-style rates, and feature notes) are in the help center — How credits work.
## Related
* [Usage limits](/guides/usage-limits) — technical quotas (payload size, batch limits)
* [Products overview](/guides/products/overview) — which operations create billable tasks
# Media translation
Source: https://docs.vmeg.ai/guides/products/media-translation
Async video and audio localization
Localize uploaded video or audio with a full **translate and dub** pipeline: speech recognition, script translation, AI dubbing (cloned or preset voices), and rendered output. Optional subtitles and lip sync. **Async only** — dubbed files arrive via [Webhook](/guides/webhooks).
This API **re-dubs** your media in the target language. It is not plain text translation and does not return only subtitles — you get new dubbed video or audio files.
## Task types
| Request `taskType` | Meaning |
| ------------------ | ----------------- |
| `vt` | Video translation |
| `at` | Audio translation |
If you request `at` but the material is video, the server may run **video translation (`vt`)** instead. Check `taskType` in the acceptance response and webhook.
## Workflow
Finish the full [material upload](/guides/assets/material-upload) flow (including complete / instant upload), then use the registered `materialId`
Set your webhook URL on API Configuration and copy the generated secret for verification
`POST /openapi/v1/task/media-translation/create-async` with `X-Idempotency-Key`
[Webhook request body](/guides/webhook-request) when `event` is `openapi-media-translation`
## Request outline
Required fields include `taskType`, `source.materialId`, `language` (source/target locales), and `options` (dubbing, ASR, translation, etc.).
`source.materialId` must come from a **fully completed** [material upload](/guides/assets/material-upload) (or from [list materials](/guides/assets/materials) after a successful upload). Do not use a `materialId` from an in-progress gen-upload-url / multipart initiate response.
Use [Supported languages](/guides/supported-languages) for locale codes. For the full `options` tree, see the request body in [Create media translation (async)](/api-reference/media-translation/create-media-translation-async).
## Responses
* **HTTP Response** (immediate): acceptance — `taskId`, `status`, `createdAt`, effective `taskType`
* **Webhook** (on completion): deliverables in `data.result` (CDN URLs)
Do not expect final video/audio URLs in the `create-async` HTTP response.
## Voice fields in webhook results
Translated script lines and per-line TTS results may include **`voiceId`** and **`provider`**. Copy both exactly as returned — do not build or guess values.
| Field | Meaning |
| ---------- | --------------------------------------------------------------------- |
| `voiceId` | Stable voice ID (`sv_…` for presets, `cv_…` for clones) |
| `provider` | Preset tier (`S1`, `S2`, …) or clone engine (`V1`–`V5`) for that line |
If you let users edit translated text and resubmit lines, **store and resend `voiceId` and `provider` together**. See [Voices](/guides/assets/voices).
## Task management
Poll `GET /openapi/v1/tasks/detail` as a fallback. See [Products overview](/guides/products/overview).
# Products overview
Source: https://docs.vmeg.ai/guides/products/overview
Tasks, sync vs async, and unified task APIs
Every Open API **product** creates a **task** with a `taskId`. You can list, inspect, and delete tasks through shared task endpoints regardless of product type.
## Separate from website tasks
Open API tasks are not the same as jobs you start on [vmeg.ai](https://www.vmeg.ai/home) — same rule as [API assets](/guides/assets/overview).
* Use `/openapi/v1/tasks/**` and webhooks to track API work only.
* Website task history does not list API jobs, and website jobs cannot be queried through the Open API.
Your API Key scopes **Open API tasks and assets** only. Work done on the website uses a separate store.
## Task APIs
| Action | Endpoint |
| ------ | ------------------------------- |
| List | `GET /openapi/v1/tasks/list` |
| Detail | `GET /openapi/v1/tasks/detail` |
| Delete | `POST /openapi/v1/tasks/delete` |
Request and response fields: [List tasks](/api-reference/tasks/list-tasks), [Get task detail](/api-reference/tasks/get-task-detail), and [Delete task](/api-reference/tasks/delete-task).
Task list, detail, and delete, plus asset list and cloned-voice queries, are scoped to your **API Key**. Open API requests do not include a separate project identifier.
## Sync vs async
| Product | Sync create | Async create | How you get results |
| ------------------------------------------------------- | ----------------------------- | ----------------------------------------- | ------------------------------------------------------------------------- |
| [Media translation](/guides/products/media-translation) | — | `POST .../media-translation/create-async` | Async only: Webhook |
| [Text to speech](/guides/products/text-to-speech) | `POST .../tts/create` | `POST .../tts/create-async` | Sync: `data.results` in HTTP response. Async: [Webhook](/guides/webhooks) |
| [Voice clone](/guides/products/voice-clone) | `POST .../clone-voice/create` | `POST .../clone-voice/create-async` | Sync: `voiceId` in response. Async: Webhook |
| [Text translation](/guides/products/text-translation) | `POST .../translate/create` | `POST .../translate/create-async` | Sync: `data.items` in response. Async: Webhook |
### Async integration checklist
1. [API Key](/guides/authentication)
2. [Idempotency](/guides/idempotency) — `X-Idempotency-Key` on every `POST`
3. [Webhooks](/guides/webhooks) — webhook URL on API Configuration; copy the generated secret; return 2xx quickly
4. Submit `create-async` → save `taskId`
5. Handle the [Webhook request body](/guides/webhook-request) — check `event` (`openapi-tts`, `openapi-translate`, etc.) then parse `data`
### Sync integration
Call `create` (non-async) with `X-Idempotency-Key` and read results from the HTTP `data` payload.
## Task types
`taskType` uses short codes:
| `taskType` | Product |
| ----------------- | ----------------- |
| `vt` / `at` | Media translation |
| `tts` | Text to speech |
| `cloneVoice` | Voice clone |
| `textTranslation` | Text translation |
## Product guides
Video/audio localization
Speech synthesis
Clone from sample audio
Segment translation
# Text to speech
Source: https://docs.vmeg.ai/guides/products/text-to-speech
Synthesize speech from text
Convert text to speech using a system or cloned **voiceId**. Copy IDs from [Voices](/guides/assets/voices) list or clone responses.
## Sync vs async
| Mode | Endpoint | Results |
| ----- | ---------------------------------------- | --------------------------------------------------------------------------------- |
| Sync | `POST /openapi/v1/task/tts/create` | `data.results[].uri` (CDN audio) in HTTP response |
| Async | `POST /openapi/v1/task/tts/create-async` | [Webhook](/guides/webhook-request) when `event` is `openapi-tts` (`data.results`) |
Every `create` and `create-async` call requires [`X-Idempotency-Key`](/guides/idempotency). Async also needs a configured [Webhook](/guides/webhooks).
## Workflow (sync)
See [Quickstart](/guides/quickstart) for a complete curl example.
Key request fields:
* `language.target` — locale from [Supported languages](/guides/supported-languages)
* `data[]` — up to 50 lines; each needs `text` and `voiceId` (or `timbreRefAudio`)
* `outputFormat` — `wav`, `mp3`, or `m4a`
Do **not** send `provider`. Pass the **`voiceId` from the voice list or clone result**; the service picks the matching engine automatically. All lines in one request must use voices from the **same tier or clone engine** (see [Supported clone methods](/guides/supported-clone-methods#text-to-speech-voiceid-and-provider) and [Error codes](/guides/errors)).
## Workflow (async)
1. Configure [Webhooks](/guides/webhooks)
2. POST to `create-async` with idempotency key
3. Handle audio URLs in webhook `data.results`
## Voices
* **System voices**: `POST /openapi/v1/assets/voice/basic/list` → copy `voiceId`
* **Cloned voices**: [Voice clone](/guides/products/voice-clone) → copy returned `voiceId`
Full request schema: **API reference → TTS** operations.
## Related
* [Products overview](/guides/products/overview)
* [Usage limits](/guides/usage-limits) — batch size limits
# Text translation
Source: https://docs.vmeg.ai/guides/products/text-translation
Dubbing-oriented segment translation with precise context, scenes, and speaker metadata
VMEG text translation is built for **localization pipelines** — especially **dubbing, subtitles, and voiceover** — not as a generic “paste a paragraph” machine translation endpoint. You send an ordered list of **segments** (lines or cues), and the engine uses **context, scene, speaker, and locale hints** so translations read naturally when spoken or timed, and stay consistent across a script.
Locales must appear in [Supported languages](/guides/supported-languages). For full audio/video translation (ASR → translate → TTS in one job), use [Media translation](/guides/products/media-translation) instead.
## How this differs from generic translation APIs
| Capability | Typical translation API | VMEG text translation |
| ---------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Unit of work | Long documents or single strings | Up to **50 segments** per request, index-aligned `data.items` |
| Context | Document-level or none | **Per-segment** `context`, global `sourceContext` modes, optional **timeline** (`start` / `end`) |
| Dubbing fit | Neutral written style | **`sceneCustomized.scene`**: `video`, `audio`, `subtitle`, `voiceover` |
| Speakers | Rarely modeled | **`speaker`**, **`gender`** (`male` / `female`), per-segment **`locale`** |
| Terminology | Optional glossary elsewhere | Built-in **`options.glossary`** map |
| Downstream TTS / media | Separate products | Same locale and phrasing assumptions as [TTS](/guides/products/text-to-speech) and [Media translation](/guides/products/media-translation) |
Billing is by **source character count** (all `segments[].text`), not by duration. See [Pricing](/guides/pricing).
## Request shape
| Field | Role |
| ------------------------------------- | ------------------------------------------------------------------- |
| `language.source` / `language.target` | BCP-47 pair for the batch (required `target`; `source` recommended) |
| `segments[]` | Lines to translate; response `data.items[i]` matches input index |
| `options` | Context strategy, scene, glossary, custom `prompt` |
Each segment supports:
| Segment field | Purpose |
| --------------- | ----------------------------------------------------------------------------------------- |
| `text` | Source line (required) |
| `speaker` | Speaker id or label — keeps pronouns and voice assignment consistent across segments |
| `gender` | `male` or `female` — disambiguates languages where gender affects grammar or delivery |
| `locale` | Overrides `language.source` for that line (mixed-language scripts) |
| `context` | Neighbor source lines for **this** segment only (highest priority for context) |
| `start` / `end` | Source timeline in seconds — use with ordered segments when context follows playback time |
Field-level OpenAPI detail: [Create text translation (sync)](/api-reference/text-translation/create-text-translation-sync).
## Context management
Ambiguous lines (“that”, “he”, homophones) need **controlled** context — not the whole document dumped in every time.
### Global strategy: `options.sourceContext`
| `mode` | Behavior |
| --------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `independent` | Default. Each segment translated without neighbors |
| `provided_only` | Use only `segments[i].context` you supply — best when segments are **not contiguous** in time or order |
| `neighbor` | Auto-include nearby segments from the **same request**; window size via `contextWindow` (1–20 segments before and after) |
**Priority:** segment-level `context` **overrides** global `sourceContext` for that line.
### When to use which mode
* **Subtitle / dubbing script in order** — `neighbor` with a small `contextWindow` (e.g. 3–5) for pronoun and tone continuity without leaking unrelated lines.
* **Cherry-picked cues or A/B takes** — `provided_only` + explicit `context` per segment.
* **Isolated UI strings or glossary checks** — `independent`.
Pair `start` / `end` with ordered `segments` when your pipeline already has timecodes; the API uses segment order and timing together with `neighbor` mode.
## Scene and delivery: `options.sceneCustomized`
| Field | Values / default | Effect |
| -------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `scene` | `video` (default), `audio`, `subtitle`, `voiceover` | Tells the model how the line will be **used** — pacing, brevity, and oral vs on-screen style differ by scene |
| `allowStutter` | `true` (default) | When `true`, disfluencies in the source may be preserved where appropriate for natural speech |
Use `subtitle` for burn-in or caption constraints; `voiceover` or `audio` when the line is spoken without lip sync; `video` for general dubbed video dialogue.
## Terminology and instructions
* **`options.glossary`** — map of source term → preferred translation (brand names, character names, fixed phrases).
* **`options.prompt`** — extra instruction appended to the translation task (tone, audience, “keep informal”, etc.).
* **`options.skipMissingTranslationCheck`** — when `true`, skips validation that every segment returned a translation (advanced; default `false`).
## Sync vs async
| Mode | Endpoint | Results |
| ----- | ---------------------------------------------- | ------------------------------------------------------------------------------------- |
| Sync | `POST /openapi/v1/task/translate/create` | `data.items` in HTTP response |
| Async | `POST /openapi/v1/task/translate/create-async` | [Webhook](/guides/webhook-request) when `event` is `openapi-translate` (`data.items`) |
Every `create` and `create-async` call requires [`X-Idempotency-Key`](/guides/idempotency). Async also needs a configured [Webhook](/guides/webhooks). Async-only `extraData` is echoed in webhooks — see [Create text translation (async)](/api-reference/text-translation/create-text-translation-async).
## Example: multi-speaker dubbing batch
```bash theme={null}
curl -X POST "https://api.vmeg.ai/openapi/v1/task/translate/create" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: translate-demo-001" \
-d '{
"language": { "source": "ja-JP", "target": "en-US" },
"segments": [
{
"text": "彼はもう知っている。",
"speaker": "A",
"gender": "male",
"start": 12.4,
"end": 14.1
},
{
"text": "本当に?",
"speaker": "B",
"gender": "female",
"start": 14.2,
"end": 15.0
}
],
"options": {
"sourceContext": { "mode": "neighbor", "contextWindow": 3 },
"sceneCustomized": { "scene": "video", "allowStutter": false },
"glossary": { "VMEG": "VMEG" },
"prompt": "Natural dubbing dialogue; keep lines short for lip sync."
}
}'
```
Translated lines appear in `data.items` in the same order as `segments`.
## Workflow (async)
1. Configure [Webhooks](/guides/webhooks)
2. POST to `create-async` with idempotency key
3. Save `taskId` from the acceptance response
4. Read translated segments from webhook `data.items`
## Related
* [Products overview](/guides/products/overview)
* [Pricing](/guides/pricing) — credits per 100 characters of source text
* [Usage limits](/guides/usage-limits) — max 50 segments per request
* [Media translation](/guides/products/media-translation) — end-to-end audio/video localization
# Voice clone
Source: https://docs.vmeg.ai/guides/products/voice-clone
Clone a voice from sample audio
Create a reusable **cloned voice** from sample audio. On success, VMEG assigns a **`voiceId`** and stores the clone as an [API voice asset](/guides/assets/voices).
## Sync vs async
| Mode | Endpoint | Results |
| ----- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| Sync | `POST /openapi/v1/task/clone-voice/create` | `voiceId` and sample metadata in HTTP response |
| Async | `POST /openapi/v1/task/clone-voice/create-async` | [Webhook](/guides/webhook-request) when `event` is `openapi-clone-voice` (`data.voiceId`) |
Every `create` and `create-async` call requires [`X-Idempotency-Key`](/guides/idempotency). Async also needs a configured [Webhook](/guides/webhooks).
## Workflow
Provide reference audio per request schema (URL or uploaded asset fields in API reference).
Sync or async create endpoint with `provider` from [Supported clone methods](/guides/supported-clone-methods) and `sampleLanguage` from [Supported languages](/guides/supported-languages).
Pass `voiceId` to [Text to speech](/guides/products/text-to-speech) requests.
## After cloning
* List clones: `POST /openapi/v1/assets/voice/clone/list`
* Rename: `POST /openapi/v1/assets/voice/clone/update`
* Delete: `POST /openapi/v1/assets/voice/clone/delete`
Clones are only available through API asset endpoints, not among voices you pick on vmeg.ai. See [Assets overview](/guides/assets/overview).
Request and callback schemas: **API reference → Voice clone** operations.
## Related
* [Voices](/guides/assets/voices)
* [Products overview](/guides/products/overview)
# Quickstart
Source: https://docs.vmeg.ai/guides/quickstart
Make your first synchronous TTS request
## Prerequisites
* A paid account with credits — see [Pricing](/guides/pricing)
* An **API Key** — see [API Key](/guides/authentication) and API Configuration (sign in if needed)
## Synchronous TTS example
Submit one line of text and receive a synthesized audio URL in the HTTP response.
```bash theme={null}
curl -X POST "https://api.vmeg.ai/openapi/v1/task/tts/create" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"language": { "target": "en-US" },
"outputFormat": "mp3",
"data": [
{
"text": "Hello from VMEG Open API.",
"voiceId": "YOUR_VOICE_ID"
}
]
}'
```
On success, `code` is `200` and `data.results` contains CDN audio URLs. Field-level detail: [Create TTS (sync)](/api-reference/introduction) in API reference.
Replace `YOUR_VOICE_ID` with a `voiceId` from [List system voices](/api-reference/assets/voices/list-system-voices) (e.g. `sv_…`).
## Async tasks
For async tasks:
1. Send [`X-Idempotency-Key`](/guides/idempotency) on every `POST` (required, max 64 characters).
2. Configure a [Webhook](/guides/webhooks) URL on API Configuration.
3. The HTTP response confirms acceptance only (`taskId`, etc.) — deliverables arrive via [Webhook request body](/guides/webhook-request).
See [Products overview](/guides/products/overview) for sync vs async by product.
## Next steps
Bearer header and configuration
TTS workflows and voice selection
# Request ID
Source: https://docs.vmeg.ai/guides/request-id
Correlate Open API calls with req_id for logging and support
## Why use it
When a call fails or you need help from VMEG support, a **request ID** ties together server logs, idempotency records, and task creation for that HTTP request.
You can pass your own ID on **any** Open API request (`/openapi/v1/**`). If you omit it, the server generates one (32-character hex, no hyphens).
`req_id` marks **this HTTP request** for logging and support. [`X-Idempotency-Key`](/guides/idempotency) prevents duplicate writes, `taskId` tracks an async job, and [`pipelineKey`](/guides/webhook-request#deduplicate-by-pipelinekey) deduplicates webhook deliveries — use the field that matches your goal; they are often used together.
## What to send
Add a query parameter:
| Item | Value |
| -------- | ------------ |
| Name | `req_id` |
| Location | Query string |
| Required | No |
```http theme={null}
POST /openapi/v1/task/tts/create?req_id=YOUR_REQUEST_ID
```
Rules:
* Use a value you can store in your own logs (for example a UUID or `order-12345-attempt-1`)
* Works on `GET` and `POST` under `/openapi/v1/**`
* The ID is **not** returned in the standard success JSON body; keep the value you sent (or log the generated ID from your client if you rely on server generation only indirectly via support lookup)
## Example
```bash theme={null}
export REQ_ID="support-$(date +%s)-$RANDOM"
curl -X POST "https://api.vmeg.ai/openapi/v1/task/tts/create?req_id=${REQ_ID}" \
-H "Authorization: Bearer $API_KEY" \
-H "X-Idempotency-Key: $IDEM_KEY" \
-H "Content-Type: application/json" \
-d @payload.json
```
If the URL already has query parameters, append with `&req_id=...`.
## When contacting support
Provide:
* The `req_id` you used (or the approximate time and which **API Key** you used if you did not set one)
* `taskId` for async jobs, if applicable
* Product and endpoint path
Set `req_id` on the **first** attempt of a flow and reuse it only when retrying the **same** HTTP call for debugging. Start a **new** `req_id` for a logically new submission (and use a new idempotency key for new writes).
# Supported clone methods
Source: https://docs.vmeg.ai/guides/supported-clone-methods
Voice provider codes and clone engines V1–V5
Open API uses short **provider codes** in voice lists, translation results, and clone requests — **`S1`–`S7`** for system presets and **`V1`–`V5`** for clones.
## Provider codes at a glance
| Code pattern | Used for | Where you see it |
| ------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------- |
| **`S1`–`S7`** | System preset voices | [List system voices](/api-reference/assets/voices/list-system-voices), media translation line results |
| **`V1`–`V5`** | Cloned voices and clone jobs | [Voice clone](/guides/products/voice-clone), clone list, media translation when cloning is enabled |
System preset tiers are **`S1` through `S7`**, including Cantonese presets on **`S7`**.
Copy **`voiceId` and `provider` together** from API responses when you need to resubmit or edit content (especially media translation lines). See [Voices](/guides/assets/voices).
## Where you send `provider`
| Product | Field |
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| [Text to speech](/guides/products/text-to-speech) | **Do not send** — use `voiceId` only |
| [Voice clone](/guides/products/voice-clone) | `provider` (`V1`–`V5`) when creating a clone |
| [Media translation](/guides/products/media-translation) | `options.voiceClone.provider` (`V1`–`V5`) when voice cloning is enabled; optional on `selectedVoicesList` if you already know the preset tier |
## Clone engines (`V1`–`V5`)
Pass **`V1` through `V5`** when creating a cloned voice or when media translation uses voice cloning. These match the clone options on [vmeg.ai](https://www.vmeg.ai/home).
| `provider` | Best for |
| ---------- | ---------------------------------------------------------------------- |
| `V1` | Simple cloning — short sentences, everyday dialogue |
| `V2` | Films, dramas, storytelling — emotional shifts and tone detail |
| `V3` | General use with more accurate, natural cloning for **English** voices |
| `V4` | Authentic emotion and subtle mood changes; strongest nuance |
| `V5` | **Non-English** and multilingual voices |
### Choosing a clone engine
| Your content | Suggested `provider` |
| ---------------------------------- | -------------------- |
| Short lines, light usage | `V1` |
| Drama, film, strong performance | `V2` or `V4` |
| Mostly English source/target | `V3` |
| Mostly non-English or multilingual | `V5` |
## Text to speech: `voiceId` and batch rules
[Text to speech](/guides/products/text-to-speech) accepts up to 50 lines in `data[]` per request.
**Use `voiceId` from [Voices](/guides/assets/voices).** Do not send `provider` on TTS requests.
Batch rules:
**Every `voiceId` must exist.** If an ID is not in your system or clone library, the API returns `400` and points you to `POST /openapi/v1/assets/voice/basic/list` or `POST /openapi/v1/assets/voice/clone/list`.
**All lines in one request must use compatible voices** — same preset tier (`S*`) for system voices, or the same clone engine (`V1`–`V5`) for clones. For example, you cannot mix `S1` and `S2` presets, or `V1` and `V2` clones, in one request.
**`timbreRefAudio` is clone-only.** If any line uses `timbreRefAudio`, every voice in that batch must be a clone engine (`V1`–`V5`). Do not mix `timbreRefAudio` with system preset `voiceId`s in the same batch.
See [Error codes](/guides/errors) → **TTS voice validation** for example messages.
Language locale codes are listed in [Supported languages](/guides/supported-languages). `provider` selects the **engine family**, not the language.
# Supported languages
Source: https://docs.vmeg.ai/guides/supported-languages
Source and target locale codes for Open API products
Use **BCP-47-style locale codes** in request bodies, for example `en-US`, `zh-CN`, `ja-JP`.
## Source languages
Use as `language.source` in media translation.
| Locale code | Language |
| ---------------- | ------------------------------------------- |
| `en-GB` | English (United Kingdom) |
| `en-US` | English (United States) |
| `es-ES` | Spanish (Spain) |
| `fr-FR` | French (France) |
| `de-DE` | German (Germany) |
| `ja-JP` | Japanese (Japan) |
| `ko-KR` | Korean (Korea) |
| `pt-PT` | Portuguese (Portugal) |
| `zh-CN` | Chinese (Mandarin, Simplified) |
| `af-ZA` | Afrikaans (South Africa) |
| `sq-AL` | Albanian (Albania) |
| `am-ET` | Amharic (Ethiopia) |
| `ar-DZ` | Arabic (Algeria) |
| `ar-BH` | Arabic (Bahrain) |
| `ar-EG` | Arabic (Egypt) |
| `ar-IQ` | Arabic (Iraq) |
| `ar-IL` | Arabic (Israel) |
| `ar-JO` | Arabic (Jordan) |
| `ar-KW` | Arabic (Kuwait) |
| `ar-LB` | Arabic (Lebanon) |
| `ar-LY` | Arabic (Libya) |
| `ar-MA` | Arabic (Morocco) |
| `ar-OM` | Arabic (Oman) |
| `ar-PS` | Arabic (Palestinian Authority) |
| `ar-QA` | Arabic (Qatar) |
| `ar-SA` | Arabic (Saudi Arabia) |
| `ar-SY` | Arabic (Syria) |
| `ar-TN` | Arabic (Tunisia) |
| `ar-AE` | Arabic (United Arab Emirates) |
| `ar-XA` | Arabic (Modern Standard) |
| `ar-YE` | Arabic (Yemen) |
| `hy-AM` | Armenian (Armenia) |
| `az-AZ` | Azerbaijani (Latin, Azerbaijan) |
| `eu-ES` | Basque(Spain) |
| `bn-IN` | Bengali (India) |
| `bn-BD` | Bangla (Bangladesh) |
| `bs-BA` | Bosnian (Bosnia and Herzegovina) |
| `be-BY` | Belarusian (Belarus) |
| `bho-IN` | Bhojpuri (India) |
| `bg-BG` | Bulgarian (Bulgaria) |
| `my-MM` | Burmese (Myanmar) |
| `ca-ES` | Catalan(Spain) |
| `yue-CN` | Chinese (Cantonese, Simplified) |
| `zh-HK` | Chinese (Cantonese, Traditional) |
| `zh-CN-shandong` | Chinese (Jilu Mandarin, Simplified) |
| `zh-CN-sichuan` | Chinese (Southwestern Mandarin, Simplified) |
| `zh-TW` | Chinese (Taiwanese Mandarin, Traditional) |
| `wuu-CN` | Chinese (Wu, Simplified) |
| `ug-CN` | Uyghur (China) |
| `yue-CN` | Chinese (Cantonese, Simplified) |
| `nan-CN` | Chinese (Southern Min, Simplified) |
| `hr-HR` | Croatian (Croatia) |
| `cs-CZ` | Czech (Czechia) |
| `da-DK` | Danish (Denmark) |
| `nl-BE` | Dutch (Belgium) |
| `nl-NL` | Dutch (Netherlands) |
| `en-AU` | English (Australia) |
| `en-CA` | English (Canada) |
| `en-GH` | English (Ghana) |
| `en-HK` | English (Hong Kong SAR) |
| `en-IN` | English (India) |
| `en-IE` | English (Ireland) |
| `en-KE` | English (Kenya) |
| `en-NZ` | English (New Zealand) |
| `en-NG` | English (Nigeria) |
| `en-PH` | English (Philippines) |
| `en-SG` | English (Singapore) |
| `en-ZA` | English (South Africa) |
| `en-TZ` | English (Tanzania) |
| `et-EE` | Estonian (Estonia) |
| `fil-PH` | Filipino (Philippines) |
| `fi-FI` | Finnish (Finland) |
| `fr-BE` | French (Belgium) |
| `fr-CA` | French (Canada) |
| `fr-CH` | French (Switzerland) |
| `gl-ES` | Galician(Spain) |
| `ka-GE` | Georgian (Georgia) |
| `de-AT` | German (Austria) |
| `de-CH` | German (Switzerland) |
| `el-GR` | Greek (Greece) |
| `gu-IN` | Gujarati (India) |
| `he-IL` | Hebrew (Israel) |
| `hi-IN` | Hindi (India) |
| `ht-HT` | Haitian Creole (Haiti) |
| `hu-HU` | Hungarian (Hungary) |
| `is-IS` | Icelandic (Iceland) |
| `id-ID` | Indonesian (Indonesia) |
| `ga-IE` | Irish (Ireland) |
| `it-IT` | Italian (Italy) |
| `it-CH` | Italian (Switzerland) |
| `jv-ID` | Javanese (Latin, Indonesia) |
| `kn-IN` | Kannada (India) |
| `kk-KZ` | Kazakh (Kazakhstan) |
| `km-KH` | Khmer (Cambodia) |
| `kmr-Arab` | Northern Kurdish - Kurmanji (Arabic Script) |
| `kmr-Latn` | Northern Kurdish - Kurmanji (Latin Script) |
| `sw-KE` | Kiswahili (Kenya) |
| `sw-TZ` | Kiswahili (Tanzania) |
| `lo-LA` | Lao (Laos) |
| `lv-LV` | Latvian (Latvia) |
| `lt-LT` | Lithuanian (Lithuania) |
| `mk-MK` | Macedonian (North Macedonia) |
| `ms-MY` | Malay (Malaysia) |
| `ml-IN` | Malayalam (India) |
| `mt-MT` | Maltese (Malta) |
| `mr-IN` | Marathi (India) |
| `mn-MN` | Mongolian (Mongolia) |
| `ne-NP` | Nepali (Nepal) |
| `nb-NO` | Norwegian Bokmål (Norway) |
| `or-IN` | Odia (India) |
| `ps-AF` | Pashto (Afghanistan) |
| `fa-IR` | Persian (Iran) |
| `pl-PL` | Polish (Poland) |
| `pt-BR` | Portuguese (Brazil) |
| `pa-IN` | Punjabi (India) |
| `ro-RO` | Romanian (Romania) |
| `ru-RU` | Russian (Russia) |
| `sr-Latn-RS` | Serbian (Serbia) |
| `si-LK` | Sinhala (Sri Lanka) |
| `sk-SK` | Slovak (Slovakia) |
| `sl-SI` | Slovenian (Slovenia) |
| `so-SO` | Somali (Somalia) |
| `es-AR` | Spanish (Argentina) |
| `es-BO` | Spanish (Bolivia) |
| `es-CL` | Spanish (Chile) |
| `es-CO` | Spanish (Colombia) |
| `es-CR` | Spanish (Costa Rica) |
| `es-CU` | Spanish (Cuba) |
| `es-DO` | Spanish (Dominican Republic) |
| `es-EC` | Spanish (Ecuador) |
| `es-SV` | Spanish (El Salvador) |
| `es-GQ` | Spanish (Equatorial Guinea) |
| `es-GT` | Spanish (Guatemala) |
| `es-HN` | Spanish (Honduras) |
| `es-MX` | Spanish (Mexico) |
| `es-NI` | Spanish (Nicaragua) |
| `es-PA` | Spanish (Panama) |
| `es-PY` | Spanish (Paraguay) |
| `es-PE` | Spanish (Peru) |
| `es-PR` | Spanish (Puerto Rico) |
| `es-US` | Spanish (United States) |
| `es-UY` | Spanish (Uruguay) |
| `es-VE` | Spanish (Venezuela) |
| `sv-SE` | Swedish (Sweden) |
| `ta-IN` | Tamil (India) |
| `te-IN` | Telugu (India) |
| `th-TH` | Thai (Thailand) |
| `tr-TR` | Turkish (Türkiye) |
| `uk-UA` | Ukrainian (Ukraine) |
| `ur-IN` | Urdu (India) |
| `ur-PK` | Urdu (Pakistan) |
| `uz-UZ` | Uzbek (Latin, Uzbekistan) |
| `vi-VN` | Vietnamese (Vietnam) |
| `cy-GB` | Welsh (United Kingdom) |
| `zu-ZA` | Zulu (South Africa) |
## Target languages
Use as `language.target` for TTS, text translation, and media translation.
| Locale code | Language |
| ---------------- | ------------------------------------------------ |
| `en-GB` | English (United Kingdom) |
| `en-US` | English (United States) |
| `es-ES` | Spanish (Spain) |
| `fr-FR` | French (France) |
| `de-DE` | German (Germany) |
| `ja-JP` | Japanese (Japan) |
| `ko-KR` | Korean (Korea) |
| `pt-PT` | Portuguese (Portugal) |
| `zh-CN` | Chinese (Mandarin, Simplified) |
| `af-ZA` | Afrikaans (South Africa) |
| `sq-AL` | Albanian (Albania) |
| `am-ET` | Amharic (Ethiopia) |
| `ar-AE` | Arabic (United Arab Emirates) |
| `ar-BH` | Arabic (Bahrain) |
| `ar-DZ` | Arabic (Algeria) |
| `ar-EG` | Arabic (Egypt) |
| `ar-IQ` | Arabic (Iraq) |
| `ar-JO` | Arabic (Jordan) |
| `ar-KW` | Arabic (Kuwait) |
| `ar-LB` | Arabic (Lebanon) |
| `ar-LY` | Arabic (Libya) |
| `ar-MA` | Arabic (Morocco) |
| `ar-OM` | Arabic (Oman) |
| `ar-QA` | Arabic (Qatar) |
| `ar-SA` | Arabic (Saudi Arabia) |
| `ar-SY` | Arabic (Syria) |
| `ar-TN` | Arabic (Tunisia) |
| `ar-YE` | Arabic (Yemen) |
| `ar-XA` | Arabic (Modern Standard) |
| `az-AZ` | Azerbaijani (Latin, Azerbaijan) |
| `bg-BG` | Bulgarian (Bulgaria) |
| `bn-BD` | Bangla (Bangladesh) |
| `bn-IN` | Bengali (India) |
| `bs-BA` | Bosnian (Bosnia and Herzegovina) |
| `ca-ES` | Catalan(Spain) |
| `cs-CZ` | Czech (Czechia) |
| `cy-GB` | Welsh (United Kingdom) |
| `da-DK` | Danish (Denmark) |
| `de-AT` | German (Austria) |
| `de-CH` | German (Switzerland) |
| `el-GR` | Greek (Greece) |
| `en-AU` | English (Australia) |
| `en-CA` | English (Canada) |
| `en-HK` | English (Hong Kong SAR) |
| `en-IE` | English (Ireland) |
| `en-IN` | English (India) |
| `en-KE` | English (Kenya) |
| `en-NG` | English (Nigeria) |
| `en-NZ` | English (New Zealand) |
| `en-PH` | English (Philippines) |
| `en-SG` | English (Singapore) |
| `en-TZ` | English (Tanzania) |
| `en-ZA` | English (South Africa) |
| `es-AR` | Spanish (Argentina) |
| `es-BO` | Spanish (Bolivia) |
| `es-CL` | Spanish (Chile) |
| `es-CO` | Spanish (Colombia) |
| `es-CR` | Spanish (Costa Rica) |
| `es-CU` | Spanish (Cuba) |
| `es-DO` | Spanish (Dominican Republic) |
| `es-EC` | Spanish (Ecuador) |
| `es-GQ` | Spanish (Equatorial Guinea) |
| `es-GT` | Spanish (Guatemala) |
| `es-HN` | Spanish (Honduras) |
| `es-MX` | Spanish (Mexico) |
| `es-NI` | Spanish (Nicaragua) |
| `es-PA` | Spanish (Panama) |
| `es-PE` | Spanish (Peru) |
| `es-PR` | Spanish (Puerto Rico) |
| `es-PY` | Spanish (Paraguay) |
| `es-SV` | Spanish (El Salvador) |
| `es-US` | Spanish (United States) |
| `es-UY` | Spanish (Uruguay) |
| `es-VE` | Spanish (Venezuela) |
| `et-EE` | Estonian (Estonia) |
| `eu-ES` | Basque(Spain) |
| `fa-IR` | Persian (Iran) |
| `fi-FI` | Finnish (Finland) |
| `fil-PH` | Filipino (Philippines) |
| `fr-BE` | French (Belgium) |
| `fr-CA` | French (Canada) |
| `fr-CH` | French (Switzerland) |
| `ga-IE` | Irish (Ireland) |
| `gl-ES` | Galician(Spain) |
| `gu-IN` | Gujarati (India) |
| `he-IL` | Hebrew (Israel) |
| `ht-HT` | Haitian Creole (Haiti) |
| `hi-IN` | Hindi (India) |
| `hr-HR` | Croatian (Croatia) |
| `hu-HU` | Hungarian (Hungary) |
| `hy-AM` | Armenian (Armenia) |
| `id-ID` | Indonesian (Indonesia) |
| `is-IS` | Icelandic (Iceland) |
| `it-IT` | Italian (Italy) |
| `jv-ID` | Javanese (Latin, Indonesia) |
| `ka-GE` | Georgian (Georgia) |
| `kk-KZ` | Kazakh (Kazakhstan) |
| `kmr-Arab` | Northern Kurdish - Kurmanji (Arabic Script) |
| `kmr-Latn` | Northern Kurdish - Kurmanji (Latin Script) |
| `kmr-Cyrl` | Northern Kurdish - Kurmanji (Cyrillic Script) |
| `km-KH` | Khmer (Cambodia) |
| `kn-IN` | Kannada (India) |
| `lo-LA` | Lao (Laos) |
| `lt-LT` | Lithuanian (Lithuania) |
| `lv-LV` | Latvian (Latvia) |
| `mk-MK` | Macedonian (North Macedonia) |
| `ml-IN` | Malayalam (India) |
| `mn-MN` | Mongolian (Mongolia) |
| `mr-IN` | Marathi (India) |
| `ms-MY` | Malay (Malaysia) |
| `mt-MT` | Maltese (Malta) |
| `my-MM` | Burmese (Myanmar) |
| `nb-NO` | Norwegian Bokmål (Norway) |
| `ne-NP` | Nepali (Nepal) |
| `nl-BE` | Dutch (Belgium) |
| `nl-NL` | Dutch (Netherlands) |
| `pa-IN` | Punjabi (India) |
| `pl-PL` | Polish (Poland) |
| `ps-AF` | Pashto (Afghanistan) |
| `pt-BR` | Portuguese (Brazil) |
| `ro-RO` | Romanian (Romania) |
| `ru-RU` | Russian (Russia) |
| `si-LK` | Sinhala (Sri Lanka) |
| `sk-SK` | Slovak (Slovakia) |
| `sl-SI` | Slovenian (Slovenia) |
| `so-SO` | Somali (Somalia) |
| `sr-Latn-RS` | Serbian (Serbia) |
| `sv-SE` | Swedish (Sweden) |
| `sw-KE` | Kiswahili (Kenya) |
| `sw-TZ` | Kiswahili (Tanzania) |
| `ta-IN` | Tamil (India) |
| `te-IN` | Telugu (India) |
| `bho-IN` | Bhojpuri (India) |
| `be-BY` | Belarusian (Belarus) |
| `th-TH` | Thai (Thailand) |
| `tr-TR` | Turkish (Türkiye) |
| `uk-UA` | Ukrainian (Ukraine) |
| `ur-IN` | Urdu (India) |
| `uz-UZ` | Uzbek (Latin, Uzbekistan) |
| `vi-VN` | Vietnamese (Vietnam) |
| `wuu-CN` | Chinese (Wu, Simplified) |
| `yue-CN` | Chinese (Cantonese, Simplified) |
| `nan-CN` | Chinese (Southern Min, Simplified) |
| `zh-CN-guangxi` | Chinese (Guangxi Accent Mandarin, Simplified) |
| `zh-CN-henan` | Chinese (Zhongyuan Mandarin Henan, Simplified) |
| `zh-CN-liaoning` | Chinese (Northeastern Mandarin, Simplified) |
| `zh-CN-shaanxi` | Chinese (Zhongyuan Mandarin Shaanxi, Simplified) |
| `zh-CN-shandong` | Chinese (Jilu Mandarin, Simplified) |
| `zh-CN-sichuan` | Chinese (Southwestern Mandarin, Simplified) |
| `zh-HK` | Chinese (Cantonese, Traditional) |
| `zh-TW` | Chinese (Taiwanese Mandarin, Traditional) |
| `ug-CN` | Uyghur (China) |
| `zu-ZA` | Zulu (South Africa) |
## In API requests
| Field | Used by |
| ----------------- | ------------------------------------------------------------- |
| `language.source` | Media translation |
| `language.target` | TTS, text translation, media translation |
| `sampleLanguage` | Voice clone (see [Voice clone](/guides/products/voice-clone)) |
Field types and examples: [API reference](/api-reference/introduction).
# Usage limits
Source: https://docs.vmeg.ai/guides/usage-limits
Technical quotas and payload limits
Technical limits below are enforced by the API. **Credit balance** and paid access are covered in [Pricing](/guides/pricing).
## Idempotency
| Limit | Value |
| ------------------------------ | -------------------------------------------------------------- |
| `X-Idempotency-Key` max length | 64 characters |
| Required on | Every Open API `POST` — see [Idempotency](/guides/idempotency) |
See [Idempotency](/guides/idempotency).
## TTS and text translation
| Limit | Value |
| --------------------------------------------------- | ------------ |
| Segments / lines per request (`data` or `segments`) | 50 items max |
| `emoWeight` (TTS) | 0–1 |
## Material upload
| Topic | Detail |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Upload path | Direct to CDN via presigned URLs — not through API gateway body size |
| Multipart | Use multipart flow for large files; part size guidance in initiate response (S3 minimum typically 5 MB per part except the last) |
| HTTP 413 | Returned when a request body or upload exceeds allowed size |
Exact upload request fields: **API reference → Assets - Materials**.
## Rate and concurrency
Per-account rate limits and concurrency caps may apply in production. If you hit throttling, retry with backoff and contact support for higher quotas.
## Related
* [Error codes](/guides/errors) — including `413 Payload Too Large`
* [Pricing](/guides/pricing) — credits and `402`
# Webhook request body
Source: https://docs.vmeg.ai/guides/webhook-request
Callback envelope, event field, and per-product payloads
## What it is
When an **async** task finishes, VMEG sends an **HTTP POST** with a JSON body to your configured webhook URL. Verify `X-Timestamp` and `X-Signature` as described in [Webhook verification](/guides/webhook-verification).
The `create-async` HTTP response only confirms acceptance (`taskId`, etc.). **Deliverables arrive in this webhook body**, not in that response.
## Common envelope
All async completion callbacks share the same top-level shape. Product-specific fields live in `data`:
```json theme={null}
{
"code": 200,
"version": "v1",
"event": "openapi-tts",
"pipelineKey": "a1b2c3d4e5f6789012345678901234ab",
"message": "",
"extraData": { "your": "memo" },
"data": { }
}
```
| Field | Meaning |
| ------------- | ------------------------------------------------------------------------------------ |
| `code` | Outcome of this delivery (`200` = task succeeded) |
| `version` | API version of the task (e.g. `v1`, `v2`) |
| `event` | **Which product finished** — see table below |
| `pipelineKey` | **Stable ID for this async completion** — use for receiver deduplication (see below) |
| `message` | Detail when `code` is not success |
| `extraData` | Echo of `extraData` from your async submit request (if you sent it) |
| `data` | Task results; schema depends on `event` |
## Deduplicate by `pipelineKey`
VMEG assigns one **`pipelineKey` per accepted async run**. It appears in every webhook POST for that completion.
| Situation | What to do |
| ---------------------------------- | ------------------------------------------------------------------- |
| First time you see a `pipelineKey` | Verify signature, process `data`, persist the key, return 2xx |
| Same `pipelineKey` again (retry) | Return 2xx quickly; **do not** repeat billable or irreversible work |
`pipelineKey` is for **webhook delivery** deduplication. It is not the same as [`X-Idempotency-Key`](/guides/idempotency) on `create-async` (which prevents duplicate task submission). One accepted async job has one `pipelineKey`; `data.taskId` identifies the task but retries are keyed by `pipelineKey`.
## The `event` field
`event` is a **string in the JSON body** that tells your server which Open API product completed. Use it to route parsing logic (switch on `event`, then read `data`).
In [API reference](/api-reference/introduction), the **Callbacks** block on each `create-async` page is named for OpenAPI tooling. The value you must implement against is the **`event` field inside the POST JSON**, not that section title.
### Event values by product
| Product | `event` value | Async create endpoint | What to read in `data` |
| ------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| [Text to speech](/guides/products/text-to-speech) | `openapi-tts` | [Create TTS (async)](/api-reference/tts/create-tts-async) | Synthesis `results`, `taskId` — see **Callbacks** on that page |
| [Text translation](/guides/products/text-translation) | `openapi-translate` | [Create text translation (async)](/api-reference/text-translation/create-text-translation-async) | `items` (translated segments), `taskId` |
| [Voice clone](/guides/products/voice-clone) | `openapi-clone-voice` | [Create voice clone (async)](/api-reference/clone-voice/create-voice-clone-async) | `voiceId` and related clone fields when successful |
| [Media translation](/guides/products/media-translation) | `openapi-media-translation` | [Create media translation (async)](/api-reference/media-translation/create-media-translation-async) | `result` with CDN URLs for deliverables |
Today, async task completion uses **only** these four `event` values. If we add more callback types later, they will be documented here and in the matching **Callbacks** schema.
### Example handler routing
```text theme={null}
POST /your-webhook
→ verify signature
→ parse JSON
→ if pipelineKey already processed → return 200
→ switch (event):
openapi-tts → parse TTS data
openapi-translate → parse translation data
openapi-clone-voice → parse clone data
openapi-media-translation → parse media result
→ store pipelineKey → return 200
```
## Per-endpoint request schema
Field-level types and nested properties are defined under **Callbacks** on each `create-async` operation in API reference (linked in the table above).
# Webhook verification
Source: https://docs.vmeg.ai/guides/webhook-verification
Verify callbacks with HMAC-SHA256
## Why verify
Your webhook URL is public. Without checks, a forged completion POST could be mistaken for a real task result. Verify every callback before you parse the JSON.
## Signing scheme
VMEG signs each webhook POST with **HMAC-SHA256**:
1. You configure a **Webhook URL** on API Configuration. VMEG generates a **Webhook Secret** on that page — copy it to your server; you never send it in the callback.
2. When a task completes, VMEG POSTs the JSON body to your URL and attaches:
* **`X-Timestamp`** — send time in Unix **milliseconds** (string)
* **`X-Signature`** — hex (base16) string of `HMAC-SHA256(secret, X-Timestamp + rawBody)`
3. Your endpoint recomputes the same HMAC with your copy of the secret. If it matches `X-Signature`, the sender is VMEG and the body was not tampered with. Reject callbacks whose timestamp is more than **300 seconds** from your server clock (replay protection).
## How to verify
1. Read `X-Timestamp` and `X-Signature` from the request headers.
2. Reject if `X-Timestamp` is outside the 300-second window.
3. Hash the **raw request body bytes** — do not re-serialize JSON, pretty-print, sort keys, or change whitespace.
4. Compare your computed hex to `X-Signature` (exact match; do not transform case).
Return `401` or `403` on failure. Still respond promptly.
## Examples
```javascript Node.js theme={null}
import crypto from "node:crypto";
function verify(secret, timestamp, rawBody, signature) {
if (Math.abs(Number(timestamp) - Date.now()) > 300_000) return false;
const message = Buffer.concat([Buffer.from(timestamp), rawBody]);
const expected = crypto.createHmac("sha256", secret).update(message).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
```
```python Python theme={null}
import hmac
import hashlib
import time
def verify(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool:
if abs(int(timestamp) - int(time.time() * 1000)) > 300_000:
return False
message = timestamp.encode() + raw_body
expected = hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
```
```java Java theme={null}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public final class WebhookVerifier {
private static final long MAX_SKEW_MS = 300_000L;
public static boolean verify(String secret, String timestamp, byte[] rawBody, String signature)
throws Exception {
long ts = Long.parseLong(timestamp);
if (Math.abs(ts - System.currentTimeMillis()) > MAX_SKEW_MS) {
return false;
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] tsBytes = timestamp.getBytes(StandardCharsets.UTF_8);
byte[] message = new byte[tsBytes.length + rawBody.length];
System.arraycopy(tsBytes, 0, message, 0, tsBytes.length);
System.arraycopy(rawBody, 0, message, tsBytes.length, rawBody.length);
String expected = toHex(mac.doFinal(message));
return MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
}
private static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
```
## After verification
1. Parse JSON and route on `event` (`openapi-tts`, `openapi-translate`, `openapi-clone-voice`, or `openapi-media-translation`) — see [Webhook request body](/guides/webhook-request).
2. Process `data` using the **Callbacks** schema on the matching `create-async` endpoint in [API reference](/api-reference/introduction).
3. Return **2xx** immediately; queue long-running work.
See [Webhooks](/guides/webhooks) for URL configuration and receiver requirements.
# Webhooks
Source: https://docs.vmeg.ai/guides/webhooks
Configure async task callbacks
## What webhooks are
When an **async** task finishes (TTS, text translation, voice clone, or media translation), VMEG sends an **HTTP POST** to a URL you configure. This is how you receive **task results** — the `create-async` HTTP response only confirms acceptance (`taskId`, etc.).
Webhooks are the **delivery mechanism** for async outcomes, not a separate product.
## Why you need them
| Without webhook | With webhook |
| -------------------------------------------- | ---------------------------------------------- |
| You only get `taskId` from `create-async` | You receive full deliverables in the POST body |
| You must poll `GET /openapi/v1/tasks/detail` | Your server is notified when work completes |
For production async integrations, configure a webhook before calling `*-create-async` endpoints.
## Configuration
On API Configuration on vmeg.ai (sign in if needed):
1. Set your **Webhook URL** (HTTPS recommended).
2. After you save the URL, VMEG **generates a Webhook Secret** and displays it on the same page. Copy it for [signature verification](/guides/webhook-verification) — you do not set the secret yourself.
Test connectivity from the same page when available.
## Receiver requirements
Your endpoint must:
1. **Return 2xx quickly** (within a few seconds). Offload heavy work to a queue or background worker.
2. **Verify signatures** on the raw request body — see [Webhook verification](/guides/webhook-verification).
3. **Deduplicate** by **`pipelineKey`** in the JSON body. Retries for the same async completion reuse the same `pipelineKey` — see [Webhook request body](/guides/webhook-request#deduplicate-by-pipelinekey). Return 2xx without repeating side effects if you already processed that key.
If you return a non-2xx status or time out, VMEG retries with backoff.
## Webhook payload
When a task finishes, the POST JSON includes an **`event`** field (for example `openapi-tts`) so you know which product completed. See the event table and envelope in [Webhook request body](/guides/webhook-request).
Per-product field definitions are under **Callbacks** on each `create-async` page in [API reference](/api-reference/introduction).
## Related
* [Idempotency](/guides/idempotency) — required on all mutating POST requests (including `create-async`)
* [Products overview](/guides/products/overview) — sync vs async by product
# Claude Code
Source: https://docs.vmeg.ai/mcp/claude-code
Connect VMEG Remote MCP in Claude Code from the terminal
Wire VMEG into Claude Code to translate media from your terminal workflow.
Manual setup
### Prerequisites
* [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed
* A VMEG account — see [Pricing](/guides/pricing) for credits
* An [API Key](https://www.vmeg.ai/open-api-setting) from API Configuration
### Add the MCP server
Run in your system terminal (not inside the Claude Code REPL):
```bash theme={null}
claude mcp add --transport http vmeg https://www.vmeg.ai/api/mcp \
--header "Authorization: Bearer YOUR_API_KEY"
```
For all projects, add user scope:
```bash theme={null}
claude mcp add --transport http -s user vmeg https://www.vmeg.ai/api/mcp \
--header "Authorization: Bearer YOUR_API_KEY"
```
**Alternative:** edit `~/.claude.json` or project `.mcp.json`:
```json theme={null}
{
"mcpServers": {
"vmeg": {
"type": "http",
"url": "https://www.vmeg.ai/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
Replace `YOUR_API_KEY` with a key from [API Configuration](https://www.vmeg.ai/open-api-setting). Restart Claude Code after editing.
### Verify
```bash theme={null}
claude mcp list
```
Or inside Claude Code, run `/mcp` — `vmeg` should appear as connected.
### Usage
```text theme={null}
Translate /path/to/demo.mp4 to French with VMEG.
```
Claude Code uploads the file via `POST /api/mcp/material/upload`, creates a translation task, and polls until complete.
## Scoping
| Scope | Flag | Config location | Availability |
| --------------- | ------------ | --------------------------------------- | ------------------------------------------- |
| Local (default) | none | `~/.claude.json`, keyed by project path | Current project, private to you |
| Project | `-s project` | `.mcp.json` in project root | Current project, shared via version control |
| User | `-s user` | `~/.claude.json` | All projects for current user |
App marketplace
One-click Claude Code integration is **coming soon**. Use [manual setup](#manual-setup) above for now.
# Claude
Source: https://docs.vmeg.ai/mcp/claude-web
Connect VMEG Remote MCP in Claude via custom connector
Use VMEG inside Claude (web or desktop) to translate media through conversation.
Manual setup
### Prerequisites
* A Claude account — custom connectors work on all plans (Free is limited to one connector; Pro, Max, Team, and Enterprise allow more). On Team/Enterprise, an owner must add the connector at the organization level first.
* A VMEG account — see [Pricing](/guides/pricing) for credits
### Register the connector
Claude supports **manual custom connectors** (paste a Remote MCP URL). The connector UI does **not** expose custom `Authorization` headers — authenticate with **OAuth** when you connect.
In Claude, go to **Settings** → **Connectors** → **Add custom connector** (or **Customize** → **Connectors** → **+**).
* **Name:** `VMEG`
* **Remote MCP server URL:**
```text theme={null}
https://www.vmeg.ai/api/mcp
```
Leave OAuth Client ID / Secret blank unless you run a custom OAuth client.
Click **Connect**. Complete the VMEG OAuth flow in your browser, approve access, and **select the project** Claude should use.
To avoid repeated prompts, set the VMEG connector to **Always allow**.
Need [API Key](https://www.vmeg.ai/open-api-setting) + Bearer auth instead? Use [Claude Code](/mcp/claude-code) or [Cursor](/mcp/cursor).
### Usage
Open a new Claude chat and request a translation:
```text theme={null}
Translate this video to German with VMEG.
```
Claude calls VMEG tools to create tasks, poll status, and return an editor link when finished.
## Limitations
| Constraint | Detail |
| ------------ | ---------------------------------------------------------------------------------- |
| Claude plan | Custom connectors work on all plans; Free is limited to one, paid plans allow more |
| VMEG account | Free and paid accounts can use MCP; tasks deduct credits as usual |
App marketplace
One-click Claude connector directory integration is **coming soon**. Use [manual setup](#manual-setup) above for now.
# Cursor
Source: https://docs.vmeg.ai/mcp/cursor
Add VMEG Remote MCP to Cursor manually
Use VMEG from Cursor's Agent chat to translate media without leaving your editor.
Manual setup
### Prerequisites
* [Cursor](https://cursor.com) installed (latest version recommended)
* A VMEG account — see [Pricing](/guides/pricing) for credits
* An [API Key](https://www.vmeg.ai/open-api-setting) from API Configuration
### Add the MCP server
In **Cursor Settings** → **Tools & MCP** → **New MCP Server**, or edit `~/.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"vmeg": {
"url": "https://www.vmeg.ai/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
Replace `YOUR_API_KEY` with the key from [API Configuration](https://www.vmeg.ai/open-api-setting). If you already have other MCP servers, add `vmeg` alongside them inside the existing `mcpServers` block.
Cursor auto-detects changes to `mcp.json`. If `vmeg` doesn't appear, toggle it off/on or click refresh in **Cursor Settings** → **Tools & MCP** (reloading the window via **Cmd+Shift+P** → **Developer: Reload Window** also works).
### Verify
Open **Cursor Settings** → **MCP** and confirm `vmeg` is listed with tools enabled. In chat, ask *「What MCP tools do you have?」* to confirm VMEG tools appear.
### Usage
In Cursor Agent chat, attach or reference a local file and request translation:
```text theme={null}
Upload this video and translate it to Spanish with VMEG.
```
For local files, the agent should upload via `POST /api/mcp/material/upload`, then call create-task tools. See [Quick start](/mcp/quickstart#media-sources).
## Limitations
| Constraint | Detail |
| ------------- | -------------------------------------------------------------- |
| Manual config | Add the Remote MCP URL and Bearer token yourself |
| Local files | Use multipart upload; local paths are not valid as `sourceUrl` |
App marketplace
A one-click Cursor marketplace listing is **coming soon**. Use [manual setup](#manual-setup) above for now.
# Gemini CLI
Source: https://docs.vmeg.ai/mcp/gemini-cli
Connect VMEG Remote MCP in Gemini CLI
Add VMEG to Gemini CLI and translate media from natural-language prompts in your terminal.
Manual setup
### Prerequisites
* Gemini CLI installed (`npm install -g @google/gemini-cli@latest`)
* A VMEG account — see [Pricing](/guides/pricing) for credits
* An [API Key](https://www.vmeg.ai/open-api-setting) from API Configuration
### Add the MCP server
Edit your Gemini CLI settings and add VMEG under `mcpServers`:
* **Global (all projects):** `~/.gemini/settings.json`
* **Project-scoped:** `.gemini/settings.json` in your project root
```json theme={null}
{
"mcpServers": {
"vmeg": {
"httpUrl": "https://www.vmeg.ai/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
Replace `YOUR_API_KEY` with your key from [API Configuration](https://www.vmeg.ai/open-api-setting). If you already have other MCP servers, add `vmeg` alongside them. Restart Gemini CLI after saving.
### Verify
Launch Gemini CLI and run:
```text theme={null}
/mcp
```
`vmeg` should appear with its available tools.
### Usage
```text theme={null}
Translate demo.mp4 to Korean with VMEG.
```
## Configuration scoping
| Scope | File location | Availability |
| ------- | -------------------------------------- | -------------------- |
| Global | `~/.gemini/settings.json` | All projects |
| Project | `.gemini/settings.json` (project root) | Current project only |
App marketplace
One-click Gemini CLI integration is **coming soon**. Use [manual setup](#manual-setup) above for now.
# Manus
Source: https://docs.vmeg.ai/mcp/manus
Connect VMEG Remote MCP in Manus agents
Connect VMEG to Manus so agents can translate media as part of automated workflows.
Manual setup
### Prerequisites
* A Manus account with access to Manus Computer (Agents)
* A VMEG account — see [Pricing](/guides/pricing) for credits
* An [API Key](https://www.vmeg.ai/open-api-setting) from API Configuration
### Connect VMEG
Manus supports **custom Remote MCP** connectors (Settings → Connectors → **Add custom MCP**). Authenticate with a Bearer [API Key](https://www.vmeg.ai/open-api-setting) set as a custom `Authorization` header.
In [Manus](https://manus.im/app), go to **Settings** → **Connectors** → **Add custom MCP** (or **Direct configuration**).
* **Name:** `VMEG`
* **Transport:** `HTTP`
* **Server URL:**
```text theme={null}
https://www.vmeg.ai/api/mcp
```
Click **Add custom header** and set:
* **Name:** `Authorization`
* **Value:** `Bearer YOUR_API_KEY`
Include the space after `Bearer`. Use a key from [API Configuration](https://www.vmeg.ai/open-api-setting).
Save the connector. Manus should list VMEG tools after a successful connection test.
### Usage in Manus Computer
Open [Manus Agents](https://manus.im/app/agents), enable VMEG tools for your agent, and describe the translation job:
```text theme={null}
Every morning, check for new video files in my folder, translate each to English
with VMEG, and report task IDs when submission completes.
```
## Limitations
| Constraint | Detail |
| -------------- | ----------------------------------------------------------------------------------------------- |
| Authentication | Use a Bearer [API Key](https://www.vmeg.ai/open-api-setting) in a custom `Authorization` header |
| Public URL | Manus connects from the cloud; the MCP endpoint must be publicly reachable over HTTPS |
| VMEG account | Free and paid accounts can use MCP; tasks deduct credits as usual |
| Async tasks | Translation jobs are async — agents should poll `vmeg_list_tasks` |
App marketplace
One-click Manus tool-store integration is **coming soon**. Use [manual setup](#manual-setup) above for now. If only pre-built integrations are shown, use another agent from [supported products](/mcp/quickstart#supported-products) until then.
# ChatGPT
Source: https://docs.vmeg.ai/mcp/open-ai
VMEG on ChatGPT — coming soon
**Coming soon.** VMEG support for ChatGPT is on the way. In the meantime, use one of the [supported agents](/mcp/quickstart#supported-products).
## What to use instead
Connect VMEG through an agent that supports manual Remote MCP configuration:
* [Cursor](/mcp/cursor)
* [Claude](/mcp/claude-web)
* [Claude Code](/mcp/claude-code)
* [Gemini CLI](/mcp/gemini-cli)
* [Manus](/mcp/manus)
See [Quick start](/mcp/quickstart) for the endpoint, tools, and [API Key](https://www.vmeg.ai/open-api-setting) setup.
# Quick start
Source: https://docs.vmeg.ai/mcp/quickstart
Connect AI agents to VMEG media translation via Remote MCP
Connect VMEG to an MCP-compatible AI agent and translate video, audio, or subtitles through conversation — no custom integration code required. Usage draws on your existing VMEG plan credits.
**App marketplace / one-click connectors are not supported yet.** Use [manual setup](#add-vmeg-to-your-agent-manual) below with an [API Key](https://www.vmeg.ai/open-api-setting).
Add VMEG to your agent (manual)
Create a key on [API Configuration](https://www.vmeg.ai/open-api-setting). You need a paid plan with credits — see [Pricing](/guides/pricing).
In your [MCP-compatible agent](/mcp/quickstart#supported-products), open **Connectors** or **MCP servers** and choose **Add custom connector** (not a marketplace listing).
* **Name:** `VMEG`
* **URL:** `https://www.vmeg.ai/api/mcp`
* **Authorization:** `Bearer YOUR_API_KEY`
Ask your agent to translate media — for example, "Translate this video to Spanish with VMEG." The agent calls VMEG tools and bills against your plan.
Per-agent steps: [Cursor](/mcp/cursor), [Claude](/mcp/claude-web), and others in [Supported products](#supported-products). **ChatGPT** is [coming soon](/mcp/open-ai).
## What you can do
Once connected, your AI agent has access to the following tools:
### Materials
| Tool | Description |
| ---------------------- | ----------------------------------------------------------------- |
| `vmeg_list_materials` | List uploaded video/audio materials in your project. |
| `vmeg_delete_material` | Delete an uploaded material. Requires explicit user confirmation. |
### Voices
| Tool | Description |
| ------------------------ | ------------------------------------------------------------- |
| `vmeg_list_basic_voices` | List system voices. Filter by locale (e.g. `en-US`, `zh-CN`). |
| `vmeg_list_clone_voices` | List cloned voices in your project. |
### Create tasks
| Tool | Description |
| --------------------------------------- | ----------------------------------------------------------------------------------------- |
| `vmeg_create_video_translation_task` | Create a video or audio translation task. Requires `targetLanguage` and one media source. |
| `vmeg_create_subtitle_translation_task` | Create a subtitle translation task. Requires `targetLanguage` and one media source. |
### Tasks
| Tool | Description |
| ------------------ | ---------------------------------------------------- |
| `vmeg_list_tasks` | List recent tasks or poll a single task by `taskId`. |
| `vmeg_delete_task` | Delete a task. Requires explicit user confirmation. |
### Task draft (editor)
| Tool | Description |
| --------------------------- | -------------------------------------------------------- |
| `vmeg_query_task_scripts` | Query translated script lines. |
| `vmeg_query_task_subtitles` | Query subtitle clips. |
| `vmeg_query_task_asr` | Query ASR / original recognition lines. |
| `vmeg_query_task_speakers` | Query speakers and voice assignments. |
| `vmeg_query_task_tracks` | Query audio/video track blocks. |
| `vmeg_save_task_draft` | Apply draft edits. Does not auto re-translate or export. |
| `vmeg_compose_task_draft` | Render/export a new video or audio from the saved draft. |
### Async follow-up
| Tool | Description |
| ---------------------------------------- | ---------------------------------------------- |
| `vmeg_retranslate_task_scripts` | Re-translate all scripts for an existing task. |
| `vmeg_get_retranslate_status` | Poll re-translation status. |
| `vmeg_trigger_video_translation_all_tts` | Re-dub all or selected scripts. |
| `vmeg_get_tts_status` | Poll per-script TTS dubbing status. |
### Upload (HTTP)
| Endpoint | Description |
| ------------------------------- | ----------------------------------------------------------------------------- |
| `POST /api/mcp/material/upload` | Multipart upload for local files. Returns `materialId` for create-task tools. |
Media sources
Each create-task call needs **exactly one** source:
| Source | When to use |
| ------------ | ----------------------------------------------------- |
| `materialId` | File already in your project (from upload or library) |
| `sourceUrl` | HTTPS direct link — e.g. an agent attachment URL |
| `youtubeUrl` | YouTube video URL only |
For `sourceUrl`, pass a public HTTPS file URL. For local files, upload via `POST /api/mcp/material/upload` first.
Supported products
| Agent | Manual custom MCP | VMEG auth | Status |
| ------------------------------- | ---------------------------- | ------------------------------------------------------------- | --------------- |
| [Cursor](/mcp/cursor) | Yes — `mcp.json` or Settings | [API Key](https://www.vmeg.ai/open-api-setting) (Bearer) | Available |
| [Claude](/mcp/claude-web) | Yes — Add custom connector | OAuth on Connect | Available |
| [Claude Code](/mcp/claude-code) | Yes — `claude mcp add` | [API Key](https://www.vmeg.ai/open-api-setting) or OAuth | Available |
| [Gemini CLI](/mcp/gemini-cli) | Yes — `settings.json` | [API Key](https://www.vmeg.ai/open-api-setting) (Bearer) | Available |
| [Manus](/mcp/manus) | Yes — Custom MCP connector | [API Key](https://www.vmeg.ai/open-api-setting) (Bearer only) | Available |
| [ChatGPT](/mcp/open-ai) | **No** | — | **Coming soon** |
Setup guides for each manual-capable agent are linked above. **App marketplace** one-click install is listed at the bottom of each guide as **coming soon**.
Any other MCP-compatible client can use the same endpoint:
```text theme={null}
https://www.vmeg.ai/api/mcp
```
## Remote MCP
| | Remote MCP |
| -------------- | ---------------------------------------------------------------- |
| Setup | Manual MCP URL + [API Key](https://www.vmeg.ai/open-api-setting) |
| Runs on | VMEG hosted infrastructure |
| Authentication | OAuth or [API Key](https://www.vmeg.ai/open-api-setting) |
| Billing | Your existing VMEG plan credits |
| Best for | Any MCP-compatible agent |
App marketplace
One-click installs from marketplaces or app stores are **not supported yet**. **ChatGPT support is [coming soon](/mcp/open-ai).** For agents that support manual MCP, use [manual setup](#add-vmeg-to-your-agent-manual) with your [API Key](https://www.vmeg.ai/open-api-setting).
## FAQ
For manual setup, yes — create an [API Key](https://www.vmeg.ai/open-api-setting) and send it as `Authorization: Bearer` on MCP requests. Some future marketplace connectors may use OAuth instead.
No. Translation uses credits included in your VMEG plan. See [Pricing](/guides/pricing).
Free and paid VMEG accounts can connect MCP. Submitting translation tasks deducts credits from your account as usual — see [Pricing](/guides/pricing).
Yes. MCP binds to your **VMEG project** (via [API Key](https://www.vmeg.ai/open-api-setting) or OAuth) and uses the same materials, tasks, and project resources as the website.
No. Materials, tasks, and `voiceId` values created through the [Open API](/guides/introduction) are separate from MCP. Do not mix IDs across the two channels. See [API assets](/guides/assets/overview).
MCP exposes translation tools for AI agents (OAuth, VMEG project scope, shared with the web app). The Open API exposes REST endpoints for programmatic backends (API Key, separate asset store).