VectorClone API
The VectorClone API gives you programmatic access to our full studio — face swap, talking avatars, lip sync, real-time full-body swap, and generative image editing — over a single, clean REST interface. Send an input, get back a rendered result — clean and watermark-free. No GPUs, no models to host, no infrastructure to run.
Every request is authenticated with an API key and billed from your prepaid balance. There are no subscriptions and no minimums — you pay only for what you generate, at the per-unit prices shown in Pricing.
Quickstart
Three steps to your first render — upload an asset, submit a job, poll for the result.
- 1Create an API key in the developer console and fund your balance.
- 2Upload each input asset to get a file_key.
- 3Submit a capability call, then poll the returned status URL until it succeeds.
# 1) Upload a portrait
FILE_KEY=$(curl -s https://api.vectorclone.com/api/v1/uploads \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-F "file=@portrait.png" | jq -r .file_key)
# 2) Submit an Avatar Studio job
JOB=$(curl -s https://api.vectorclone.com/api/v1/avatar \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"image_key\":\"$FILE_KEY\",\"script\":\"Hello from VectorClone!\"}")
JOB_ID=$(echo "$JOB" | jq -r .id)
# 3) Poll until it is done
curl -s https://api.vectorclone.com/api/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $VECTORCLONE_API_KEY"Authentication
Authenticate every request with your secret API key. Send it either as a Bearer token or in the X-API-Key header — both are accepted.
# Bearer token (recommended)
curl https://api.vectorclone.com/api/v1/pricing \
-H "Authorization: Bearer vck_live_..."
# or the X-API-Key header
curl https://api.vectorclone.com/api/v1/pricing \
-H "X-API-Key: vck_live_..."401 unauthorized.Create a separate key per project or environment so usage stays auditable and you can revoke one without disrupting the others.
Base URL & versioning
All endpoints live under a single versioned base URL:
https://api.vectorclone.com/api/v1The current version is v1. We only make additive changes within a version — new fields and new capabilities may appear, but existing fields and behavior stay stable. Breaking changes ship under a new version prefix.
All responses are JSON. Successful submits return 202 with a job you poll; errors return a JSON body with a stable code (see Errors).
Uploading assets
Capability calls reference inputs by file_key, not by URL. Upload each image, audio, or video file first and use the returned key. We host the upload on our own storage — there is no CORS or presign dance, and no external URLs are fetched.
/uploadsmultipart/form-data| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The asset to upload — an image, audio, or video file. The type is detected from the content type. |
curl https://api.vectorclone.com/api/v1/uploads \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-F "file=@face.png"{
"file_key": "api/images/9f8c...-a1.png",
"url": "https://cdn.vectorclone.com/api/images/9f8c...-a1.png"
}See Limits & formats for the maximum size of each asset type.
Async jobs & polling
Rendering is asynchronous. A capability call returns immediately with a queued job; you then poll its status URL (or receive a webhook) until it reaches a terminal state.
/jobs/{job_id}A job moves through these statuses:
| Status | Terminal | Meaning |
|---|---|---|
| queued | No | Accepted and waiting for a render slot. |
| processing | No | Actively rendering. |
| succeeded | Yes | Done — output_url is populated. |
| failed | Yes | Could not be completed; error holds a stable code. Held funds are refunded. |
| canceled | Yes | You canceled it; any held funds are refunded. |
{
"id": "b2f1c0de-...-77",
"status": "succeeded",
"output_url": "https://cdn.vectorclone.com/...signed...",
"credits_charged": 0.42,
"error": null
}output_url is a signed link that stays valid for a window set by us (7 days by default). Fetch the result and store it on your own infrastructure within that window — do not treat the URL as permanent. You are billed (credits_charged) only when a job succeeds; failed jobs are automatically refunded.Poll on a reasonable interval (e.g. every 2–5 seconds). Only your own jobs are visible to your keys.
List jobs
List your jobs, newest first — for bulk reconciliation or an audit. Filter by status, and page through with limit and offset. To fetch a single job's output URL and full details, poll it individually.
/jobs| Query | Type | Description |
|---|---|---|
| status | string | Optional filter: queued, processing, succeeded, failed, or canceled. |
| limit | int | Rows to return, 1–200 (default 50). |
| offset | int | Rows to skip, for paging (default 0). |
curl "https://api.vectorclone.com/api/v1/jobs?status=succeeded&limit=50" \
-H "Authorization: Bearer $VECTORCLONE_API_KEY"{
"jobs": [
{
"id": "b2f1c0de-...-77",
"status": "succeeded",
"feature": "avatar",
"endpoint": "avatar",
"created_at": "2026-08-19T17:55:03Z",
"credits_charged": 0.42
}
],
"limit": 50,
"offset": 0,
"has_more": false
}Cancel a job
Cancel a queued or processing job. Its billing hold is released immediately (refunded) and the render slot is freed. A job that has already finished (succeeded or failed) returns 400 invalid_input.
/jobs/{job_id}/cancelcurl -X POST https://api.vectorclone.com/api/v1/jobs/$JOB_ID/cancel \
-H "Authorization: Bearer $VECTORCLONE_API_KEY"{
"id": "b2f1c0de-...-77",
"status": "canceled",
"output_url": null,
"credits_charged": 0,
"error": null
}Returns the job object with status canceled.
Webhooks
Instead of polling, pass a callback_url on any capability call. When the job reaches a terminal state we POST the same payload the status endpoint returns to your URL.
| Header | Value |
|---|---|
| X-VectorClone-Event | job.completed |
| X-VectorClone-Signature | HMAC-SHA256 (hex) of the raw request body |
Verify authenticity by recomputing the signature over the raw body with your webhook secret and comparing it to X-VectorClone-Signature.
import crypto from "crypto";
app.post("/vc-webhook", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto
.createHmac("sha256", process.env.VC_WEBHOOK_SECRET)
.update(req.body) // the RAW bytes, not parsed JSON
.digest("hex");
if (expected !== req.get("X-VectorClone-Signature")) {
return res.status(400).end();
}
const event = JSON.parse(req.body.toString());
// event = { id, status, output_url, credits_charged, error }
res.status(200).end();
});X-VectorClone-Event is always job.completed, and X-VectorClone-Signature is the HMAC-SHA256 of the raw body. If your endpoint doesn't return a 2xx, we retry with exponential backoff up to 12 attempts, then stop. The payload carries id, status, output_url, credits_charged, and error.
Idempotency
Network retries should never double-charge you or render twice. Send an Idempotency-Key header (any unique string, e.g. a UUID) on a submit. A retry carrying the same key returns the original job — no second charge, no duplicate work.
curl https://api.vectorclone.com/api/v1/face-swap-image \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6b1a9e2c-order-4821" \
-d '{"image_key":"...","face_key":"..."}'Rate limits
Requests are rate limited per key. Exceeding the limit returns 429 with code rate_limited — back off and retry. Separately, the platform enforces a global concurrency ceiling so heavy API traffic can never starve capacity; if it is momentarily saturated you get at_capacity, which is safe to retry shortly.
| Tier | Requests / minute |
|---|---|
| default | 60 |
| pro | 600 |
Need a higher tier? Contact us from the console.
Errors
Errors return a JSON body with a stable, machine-readable code and a human-readable message. Branch on the code, not the message text.
{
"error": {
"code": "insufficient_credits",
"message": "Your API balance is too low for this call. Top up to continue."
}
}| HTTP | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing, invalid, or revoked API key. |
| 403 | forbidden | This key isn't allowed to perform that action. |
| 402 | insufficient_credits | Balance too low — top up to continue. |
| 400 / 422 | invalid_input | A field failed validation. |
| 400 | unsupported_format | The uploaded file type isn't supported. |
| 400 | input_too_large | Asset exceeds the size/duration limit. |
| 400 | capability_unavailable | This capability is not currently enabled. |
| 404 | not_found | The job doesn't exist or isn't yours. |
| 429 | rate_limited | Too many requests — slow down and retry. |
| 429 | at_capacity | Platform momentarily saturated — retry shortly. |
| 500 | internal_error | Something went wrong on our side — safe to retry. |
422 invalid_input. Send only the documented fields.Failed jobs (returned by the status endpoint) use these result codes:
| Code | Meaning |
|---|---|
| face_not_detected | No usable face was found in an input. |
| unsupported_format | The uploaded file type isn't supported. |
| input_too_large | Asset exceeds the size/duration limit. |
| content_rejected | The input violated our content policy. |
| processing_failed | The render failed for another reason (auto-refunded). |
Avatar Studio
Turn a single portrait into a talking avatar video — speaking a typed script (text-to-speech) or your own recorded audio.
/avatar| Field | Type | Required | Description |
|---|---|---|---|
image_key | string | Yes | file_key of the source portrait (from /uploads). |
script | string | No | What the avatar says (text-to-speech). Up to 5,000 characters. Required unless audio_key is supplied. |
audio_key | string | No | file_key of your OWN recorded audio for the avatar to speak. When supplied, script and voice_id are ignored. |
voice_id | string | No | A specific voice to speak the script. |
language | string | No | Language hint for the voice. |
output_resolution_p | int | No | 480, 720, or 1080. |
callback_url | string | No | Webhook POSTed on the terminal status. |
script for text-to-speech (optionally with a voice_id), or supply audio_key — the file_key of your own recorded audio — and the avatar speaks that instead (script and voice_id are then ignored).curl https://api.vectorclone.com/api/v1/avatar \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image_key": "api/images/portrait.png",
"script": "Welcome to the future of video.",
"output_resolution_p": 1080
}'# Speak your OWN recorded audio (script & voice_id are ignored)
curl https://api.vectorclone.com/api/v1/avatar \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image_key": "api/images/portrait.png",
"audio_key": "api/audio/my-voice.wav"
}'{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Face Swap — video
Swap a face into a source video. Billed per second of output.
/face-swap| Field | Type | Required | Description |
|---|---|---|---|
video_key | string | Yes | file_key of the source video. |
face_key | string | Yes | file_key of the face image to swap in. |
max_duration_sec | int | No | Cap the processed duration. |
output_resolution_p | int | No | 480, 720, or 1080. |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/face-swap \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video_key": "api/videos/clip.mp4",
"face_key": "api/images/face.png"
}'{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Face Swap — image
Swap a face into a single still image. This performs a full head swap — face, hair, and silhouette — not just the face oval.
/face-swap-image| Field | Type | Required | Description |
|---|---|---|---|
image_key | string | Yes | file_key of the base image. |
face_key | string | Yes | file_key of the face image to swap in. |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/face-swap-image \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image_key": "api/images/scene.png",
"face_key": "api/images/face.png"
}'{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Lip Sync
Re-sync a video's mouth movements to a new audio track.
/lip-sync| Field | Type | Required | Description |
|---|---|---|---|
video_key | string | Yes | file_key of the source video. |
audio_key | string | Yes | file_key of the audio to sync to. |
output_resolution_p | int | No | 480, 720, or 1080. |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/lip-sync \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video_key": "api/videos/clip.mp4",
"audio_key": "api/audio/voice.wav"
}'{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Reimagine
Edit or restyle up to four images from a text prompt.
/reimagine-image| Field | Type | Required | Description |
|---|---|---|---|
image_keys | string[] | Yes | 1–4 file_keys of the source images. |
prompt | string | Yes | The edit instruction. Up to 2,000 characters. |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/reimagine-image \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image_keys": ["api/images/room.png"],
"prompt": "Make it a cozy cabin at golden hour"
}'{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Reimagine — video
Edit a video from a text instruction. Billed per second of output.
/reimagine-video| Field | Type | Required | Description |
|---|---|---|---|
video_key | string | Yes | file_key of the source video to edit. |
prompt | string | Yes | The edit instruction — what to change in the video. |
negative_prompt | string | No | Optional — elements the edit should avoid. |
max_duration_sec | int | No | Cap the output length, 1–60 seconds. |
output_resolution_p | int | No | 480, 720, or 1080. |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/reimagine-video \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video_key": "api/videos/clip.mp4",
"prompt": "Make it a snowy night scene",
"output_resolution_p": 720
}'{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Identity Swap
Place a character into the scene and motion of a driving video. Supply a driving video for the scene and movement plus a character image, and your character performs that same motion in a brand-new video. Billed per second of output.
/identity-swap| Field | Type | Required | Description |
|---|---|---|---|
video_key | string | Yes | file_key of the driving video — it provides the scene and the motion. |
character_key | string | Yes | file_key of the character image to place into the scene. |
mode | string | No | express (default) runs the whole pipeline in one call and returns the final video. advanced pauses after each stage so you can review a preview and push it on — see Advanced (stepper) mode below. |
prompt | string | No | Instruction for the swap. A sensible default is used if omitted. |
aspect_ratio | string | No | One of 16:9, 9:16, 1:1, 4:3, 4:5. Defaults to the driving video's ratio. |
resolution | string | No | 1k (default) or 2k. |
keep_original_sound | bool | No | Keep the driving video's audio in the result. Default true. |
audio_mode | string | No | How the character's audio is produced: original (default), own_audio, voice_clone, or tts. See Audio options below. |
audio_key | string | No | own_audio only — file_key of an audio track the character is lip-synced to. Required when audio_mode is own_audio. |
voice_sample_key | string | No | voice_clone only — file_key of a voice sample to clone. Required when audio_mode is voice_clone. |
script | string | No | voice_clone / tts only — the text the cloned or catalogue voice speaks. Required when audio_mode is voice_clone or tts. |
voice_id | string | No | tts only — a voice id from GET /voices for the character to speak your script. Required when audio_mode is tts. |
language | string | No | tts only — spoken language hint (defaults to auto-detect). |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/identity-swap \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video_key": "api/videos/dance.mp4",
"character_key": "api/images/hero.png",
"resolution": "1k"
}'keep_original_sound to false.{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Audio options
Set audio_mode to control the character's sound. Every mode other than original lip-syncs the character to the chosen audio.
| audio_mode | Also send | Result |
|---|---|---|
| original | — | Keeps the driving video's own sound (default). |
| own_audio | audio_key | The character is lip-synced to the audio track you upload. |
| voice_clone | voice_sample_key + script | Clones the voice sample, then the character speaks your script in that voice. |
| tts | voice_id + script | The character speaks your typed script in a catalogue voice. |
curl https://api.vectorclone.com/api/v1/identity-swap \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"video_key": "api/videos/dance.mp4",
"character_key": "api/images/hero.png",
"audio_mode": "voice_clone",
"voice_sample_key": "api/audio/my-voice.wav",
"script": "Hey everyone, welcome back to the channel."
}'audio_mode: tts speaks a typed script in a catalogue voice. Pass voice_id — one of the ids from GET /voices — plus script. Use voice_clone instead to speak in a cloned voice, or own_audio to lip-sync to an uploaded track.Advanced (stepper) mode
Submit with mode: advanced to review the pipeline stage by stage. The job pauses after the frame extract and after the swap, so you can check each preview before pushing it on. While it runs, the job status also returns a stage and a signed preview_url of the latest intermediate.
- 1Submit POST /v1/identity-swap with mode: advanced. You get a job id back, same as express.
- 2Poll the job status: it reports stage: extracting, then stage: frame_ready with a preview_url of the extracted frame.
- 3When stage is frame_ready, call advance with stage: swap. The character placement runs and the job moves to stage: swap_ready with a preview_url of the swapped still.
- 4When stage is swap_ready, call advance with stage: motion. The final video renders and the job completes with output_url.
/identity-swap/{id}/advance| Field | Type | Required | Description |
|---|---|---|---|
stage | string | Yes | swap (after frame_ready) or motion (after swap_ready). |
prompt | string | No | swap only — override the swap instruction. |
aspect_ratio | string | No | swap only — override the output aspect ratio. |
resolution | string | No | swap only — override the render resolution (1k or 2k). |
character_key | string | No | swap only — replace the character image. |
keep_original_sound | bool | No | motion only — override keeping the driving video's audio. |
# 1 · submit in advanced mode
curl https://api.vectorclone.com/api/v1/identity-swap \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "video_key": "api/videos/dance.mp4", "character_key": "api/images/hero.png", "mode": "advanced" }'
# 2 · once GET /v1/jobs/{id} reports stage: frame_ready
curl https://api.vectorclone.com/api/v1/identity-swap/$JOB_ID/advance \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "stage": "swap" }'
# 3 · once it reports stage: swap_ready
curl https://api.vectorclone.com/api/v1/identity-swap/$JOB_ID/advance \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "stage": "motion" }'{
"id": "b2f1c0de-...-77",
"status": "processing",
"stage": "frame_ready",
"preview_url": "https://cdn.vectorclone.com/...signed...",
"output_url": null,
"error": null
}stage and preview_url appear only for Identity Swap jobs run in advanced mode. Advancing costs nothing extra — the whole pipeline still settles per second of output when the job completes, exactly like express.Motion Control
Animate a still character image using the motion of a reference video. The output preserves your character's identity while matching the reference's motion. Billed per second of output.
/motion-control| Field | Type | Required | Description |
|---|---|---|---|
image_key | string | Yes | file_key of the still character image to animate. |
video_key | string | Yes | file_key of the motion-reference video (its motion drives the image). |
character_orientation | string | No | Which way the character faces: video (derive from the reference), front, side, or back. Default video. |
max_duration_sec | int | No | Cap the output length, 3–30 seconds. |
prompt | string | No | Optional guidance for the animation. |
negative_prompt | string | No | Optional — elements to avoid. |
keep_original_sound | bool | No | Keep the reference video's audio on the output. Default false. |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/motion-control \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image_key": "api/images/hero.png",
"video_key": "api/videos/dance.mp4",
"character_orientation": "video"
}'{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Real-Time Swap (Full body)
Swap a full-body performance in real time on a live video stream — for calls, avatars, and interactive apps. Unlike the async capabilities, this is a live session: you create a session, connect a stream, and send/receive video with sub-second latency.
/real-time-swap/session| Field | Type | Required | Description |
|---|---|---|---|
duration_minutes | int | Yes | Maximum length of this session (1–240). You are billed PER SECOND actually streamed, up to this cap — the stream hard-stops here so you never overrun. You must have the max cost available to start. |
prompt | string | No | Optional swap instruction. A sensible default is used if omitted. |
curl https://api.vectorclone.com/api/v1/real-time-swap/session \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "duration_minutes": 10 }'{
"session_id": "e7c1...-9a",
"stream_url": "wss://api.vectorclone.com/realtime",
"model": "vectorclone-rt-1",
"session_token": "rt_...",
"prompt": "Replace the person's face ...",
"max_duration_sec": 600,
"expires_at": "2026-07-12T18:40:00Z",
"cost_usd": 24.00
}Connecting to the live stream
After creating a session, open a WebSocket to the returned stream_url with your session_token — the token is all that's needed. You'll get back the details to join a standard LiveKit media room, where you publish your camera and subscribe to the swapped video track. Any standard LiveKit client works — no special SDK.
- 1Open a WebSocket to stream_url?session_token=… (the token is all that's needed).
- 2On open, send {"type":"session_join","passthrough":false}, then a set_image (to swap to a reference face) or a prompt message.
- 3Receive a room_info message with a livekit_url + token.
- 4Join that LiveKit room with any LiveKit client; publish your camera and subscribe to the transformed track (register the listener BEFORE connecting — the swapped track can arrive immediately).
const ws = new WebSocket(
streamUrl + "?session_token=" + sessionToken // streamUrl from the session response
);
ws.onopen = () => {
ws.send(JSON.stringify({ type: "session_join", passthrough: false }));
// Swap to a reference face (base64, no data: prefix)…
ws.send(JSON.stringify({
type: "set_image",
image_data: faceBase64,
prompt: "Keep lighting and background unchanged",
enhance_prompt: true,
}));
// …or drive the swap from a text prompt only:
// ws.send(JSON.stringify({ type: "prompt", prompt: "…", enhance_prompt: true }));
};import { Room, RoomEvent } from "livekit-client";
ws.onmessage = async (e) => {
const msg = JSON.parse(e.data);
if (msg.type !== "room_info") return;
const room = new Room({ adaptiveStream: false, dynacast: false });
// Register BEFORE connect — the swapped track can arrive immediately.
room.on(RoomEvent.TrackSubscribed, (track) => {
if (track.kind === "video") track.attach(outputVideoEl); // the swapped stream
});
await room.connect(msg.livekit_url, msg.token);
// Publish your camera; the swapped video comes back as the remote track.
const cam = await navigator.mediaDevices.getUserMedia({
video: { width: 512, height: 512, frameRate: 25 },
});
await room.localParticipant.publishTrack(cam.getVideoTracks()[0]);
};cost_usd in the response is the maximum (the full block); you must have that much available to start, but you only pay for the seconds you use. The stream hard-stops at max_duration_sec, so you never overrun. Start another session to continue. The livekit_url points at standard LiveKit media infrastructure; your client connects to it directly for low-latency video.Voice Changer (real-time)
Stream microphone audio in and get it back converted to a target voice, in real time. You fund a session by the minute but are billed only for the seconds you actually stream — nothing if it never connects. The session is hard-capped to the funded length.
List target voices
The curated system voices you can convert toward. Each returns a sample_url you can play to preview it (which is also the reference clip when you target that voice). Or skip this and stream your own reference clip in the session (below).
/voice-changer/voicescurl https://api.vectorclone.com/api/v1/voice-changer/voices \
-H "Authorization: Bearer $VECTORCLONE_API_KEY"[
{
"id": "b2d1...-7f",
"name": "Emma",
"description": "Warm female narrator",
"sample_url": "/api/v1/voice-changer/voices/b2d1...-7f/sample"
}
]These are a curated library of generic, ready-to-use voices we maintain and keep expanding — list them at runtime with GET /voice-changer/voices and pass a returned id to your session rather than hardcoding ids, since the catalog grows over time.
voice_id is optional on POST /voice-changer/session — omit it to stream your own reference voice clip over the session instead of picking a catalogue voice. So an empty voices list never blocks Voice Changer.Preview a voice
Fetch a voice's audio sample — play it to preview, or save it to use as the reference clip. It's served through the API, so send your Authorization header.
/voice-changer/voices/{id}/samplecurl https://api.vectorclone.com/api/v1/voice-changer/voices/$VOICE_ID/sample \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
--output voice-sample.wavStart a session
Funds duration_minutes up front (charged per second actually streamed) and returns a scoped token + our stream URL once a voice pod is ready.
/voice-changer/session| Field | Type | Required | Description |
|---|---|---|---|
duration_minutes | int | Yes | Minutes to fund. Charged per second actually streamed; the session ends at this length. |
voice_id | string | No | A curated target voice from GET /voice-changer/voices. Omit to stream your own reference clip over the session instead. |
curl https://api.vectorclone.com/api/v1/voice-changer/session \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "duration_minutes": 5, "voice_id": "b2d1...-7f" }'{
"session_id": "e7c1...-9a",
"status": "ready",
"stream_url": "wss://api.vectorclone.com/voice-relay",
"model": "vectorclone-voice-1",
"session_token": "vc_...",
"max_duration_sec": 300,
"cost_usd": 6.00
}status is starting, a voice pod is coming online — nothing is charged. A FRESH pod takes about 1–2 minutes (sometimes a little more): it provisions a GPU, pulls the engine, then loads the model. Poll this endpoint every few seconds until you get status: ready with the token — it moves through provisioning → warming up the model → ready. Once a pod is warm, the next sessions start almost instantly.Stream audio
Connect to stream_url with your session_token, send the target-voice reference once, then stream int16 mono PCM. The converted audio comes back in the same format.
- 1Connect:
new WebSocket(stream_url + "?session_token=" + session_token). - 2Handshake — send the target reference as
[4-byte big-endian length][WAV bytes]. Use your own clip, or the bytes from a system voice'ssample_url. - 3The server replies once with the audio format to use —
sample_rate(e.g. 16000) andchunk_frames(e.g. 2400), int16 mono PCM. - 4Loop — send your mic as int16 mono PCM chunks at that
sample_rate; the converted audio returns in the same format, in order. Play it as it arrives.
// The target-voice reference (int16 WAV): your OWN clip, or a system voice's
// sample fetched from its sample_url (send your Authorization header).
const ref = new Uint8Array(referenceWavBytes);
const ws = new WebSocket(streamUrl + "?session_token=" + sessionToken); // from the session
ws.binaryType = "arraybuffer";
ws.onopen = () => {
// Handshake: [4-byte BIG-ENDIAN length][WAV bytes]
const header = new ArrayBuffer(4);
new DataView(header).setUint32(0, ref.length, false); // false = big-endian
ws.send(header);
ws.send(ref);
};let rate = 16000, chunkFrames = 2400;
ws.onmessage = (e) => {
if (typeof e.data === "string") { // control frame
const m = JSON.parse(e.data);
if (m.sample_rate) { rate = m.sample_rate; chunkFrames = m.chunk_frames; }
return; // e.g. { "sample_rate": 16000, "chunk_frames": 2400 }
}
playPcm(new Int16Array(e.data)); // converted audio — int16 mono @ rate
};
// Send your mic as int16 mono PCM @ rate, in chunkFrames-sized chunks:
function sendMicChunk(int16Chunk) { ws.send(int16Chunk.buffer); }Voice Clone
Clone the voice in a reference sample and have it speak your script, returned as an audio file — poll the job for the signed output URL. A flat rate per clone.
/voice-clone| Field | Type | Required | Description |
|---|---|---|---|
voice_sample_key | string | Yes | file_key of a clean voice sample to clone — a few seconds of speech. |
script | string | Yes | The text the cloned voice speaks. Up to 1,200 characters. |
language | string | No | Optional language hint for the script. |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/voice-clone \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"voice_sample_key": "api/audio/my-voice.wav",
"script": "Hey everyone, welcome back to the channel."
}'{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Text-to-Speech
Synthesise a typed script in a catalogue voice into a standalone audio file — audio only, no video. It's async: poll the job for the signed audio output URL — for example to feed straight into POST /lip-sync. Billed per second of rendered audio.
/text-to-speech| Field | Type | Required | Description |
|---|---|---|---|
script | string | Yes | The text to speak. Up to 5,000 characters. |
voice_id | string | Yes | A voice id from GET /voices — the catalogue voice that speaks the script. |
language | string | No | Optional spoken-language hint (auto-detected if omitted). |
callback_url | string | No | Webhook POSTed on the terminal status. |
curl https://api.vectorclone.com/api/v1/text-to-speech \
-H "Authorization: Bearer $VECTORCLONE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"script": "Welcome to the show. Today we explore the future of media.",
"voice_id": "Alex"
}'tts audio_mode inside /identity-swap and /avatar, which speak a script in a catalogue voice but return video. /voice-clone is different — it needs a reference recording to clone, whereas text-to-speech uses a ready-made catalogue voice (a voice_id from GET /voices).{
"id": "b2f1c0de-...-77",
"status": "queued",
"status_url": "/v1/jobs/b2f1c0de-...-77"
}Voices
A public reference list of the text-to-speech voices — no authentication required. Pick a voice_id here to use with text-to-speech (Identity Swap audio_mode: tts).
/voicespublic — no auth| Field | Type | Description |
|---|---|---|
| id | string | The voice_id to pass to text-to-speech. |
| name | string | A human-readable label (character and tone). |
| language | string | The voice's language. |
curl https://api.vectorclone.com/api/v1/voices[
{ "id": "Alex", "name": "Alex (warm, masculine)", "language": "English" },
{ "id": "Maya", "name": "Maya (bright, feminine)", "language": "English" }
]Account
Your account's hard limits and current balance in a single call. Use it to validate an input against the caps before you upload, and to check your balance before you submit a job.
/account| Field | Type | Description |
|---|---|---|
| rate_tier | string | Your rate-limit tier name. |
| rate_limit_per_minute | int | Requests allowed per minute (429 rate_limited past this). |
| max_concurrent_jobs | int | Maximum jobs processing at once (429 at_capacity past this). |
| max_image_mb | int | Largest image upload, in MB. |
| max_audio_mb | int | Largest audio upload, in MB. |
| max_video_mb | int | Largest video upload, in MB. |
| allowed_output_resolutions_p | int[] | Output resolutions you may request (in p). |
| max_script_chars | int | Longest avatar/voice script, in characters. |
| max_prompt_chars | int | Longest prompt, in characters. |
| max_realtime_minutes | int | Total Real-Time Swap minutes available. |
| max_voice_minutes | int | Total Voice Changer minutes available. |
| balance_usd | number | Your live spendable balance in USD — already net of any active holds. |
| held_usd | number | Funds reserved on in-flight jobs (refunded if a job fails or is canceled). balance_usd + held_usd = total funded. |
curl https://api.vectorclone.com/api/v1/account \
-H "Authorization: Bearer $VECTORCLONE_API_KEY"{
"rate_tier": "default",
"rate_limit_per_minute": 60,
"max_concurrent_jobs": 3,
"max_image_mb": 45,
"max_audio_mb": 45,
"max_video_mb": 500,
"allowed_output_resolutions_p": [480, 720, 1080],
"max_script_chars": 5000,
"max_prompt_chars": 2000,
"max_realtime_minutes": 240,
"max_voice_minutes": 240,
"balance_usd": 142.50,
"held_usd": 12.00
}max_video_mb (file size), not by length. Real-time and voice sessions are capped by the *_minutes values.Balance
Check your prepaid balance and lifetime spend programmatically — handy for dashboards, low-balance alerts, or gating your own usage.
/balancecurl https://api.vectorclone.com/api/v1/balance \
-H "Authorization: Bearer $VECTORCLONE_API_KEY"{
"balance_usd": 142.50,
"held_usd": 12.00,
"total_spent_usd": 57.50,
"total_topped_up_usd": 200.00,
"spent_this_week_usd": 8.20,
"spent_this_month_usd": 31.40,
"spent_this_year_usd": 57.50
}balance_usd is your live spendable balance — already net of active holds — and reads identically on GET /balance and GET /account at the same instant. held_usd is funds reserved on in-flight jobs: a job's hold is debited up front and refunded if it fails or is canceled, or trued-up to the real cost on completion. balance_usd + held_usd is your total funded amount. Submitting a job temporarily moves balance_usd into the hold, so two calls a moment apart can differ — held_usd explains the difference.spent_this_week_usd, spent_this_month_usd and spent_this_year_usd are convenience windows (calendar week starting Monday, month, and year, all UTC). total_spent_usd is lifetime. For any custom range, use GET /usage/summary below.
Usage
Your recent API calls and what each was charged, newest first.
/usage| Query | Type | Description |
|---|---|---|
| limit | int | How many rows to return (default 100, max 500). |
| from | ISO 8601 | Optional. Only calls at or after this time (e.g. 2026-07-01T00:00:00Z). |
| to | ISO 8601 | Optional. Only calls before this time. |
curl "https://api.vectorclone.com/api/v1/usage?limit=50" \
-H "Authorization: Bearer $VECTORCLONE_API_KEY"[
{
"id": "b2f1c0de-...-77",
"endpoint": "avatar",
"feature": "avatar",
"status": "succeeded",
"charged_usd": 0.42,
"created_at": "2026-07-12T17:55:03Z"
}
]Usage summary
Accurate call counts and spend for your whole account — or a date range — computed from your full history, not a sum of the recent /usage log. Totals stay correct no matter how many calls you've made. Use this for spend dashboards and budgets.
/usage/summary| Query | Type | Description |
|---|---|---|
| from | ISO 8601 | Optional. Start of the range (inclusive). |
| to | ISO 8601 | Optional. End of the range (exclusive). Omit both for all-time. |
curl "https://api.vectorclone.com/api/v1/usage/summary?from=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer $VECTORCLONE_API_KEY"{
"total_calls": 945,
"succeeded": 916,
"failed": 29,
"total_spent_usd": 1362.38,
"per_feature": [
{ "feature": "real_time_swap_full", "calls": 200, "spend_usd": 327.04 },
{ "feature": "avatar", "calls": 512, "spend_usd": 803.11 }
]
}Pricing
Prices are pay-as-you-go and charged from your prepaid balance. The table below is live — it reads directly from GET /v1/pricing (public, no auth), so it always reflects current pricing. A per-unit price and a per-call minimum apply.
| Capability | Unit | Price / unit | Min charge |
|---|---|---|---|
| Loading live pricing… | |||
per_second capabilities bill by output duration; per_generation bill a flat rate per successful call. You are only ever charged when a job succeeds.
Limits & formats
| Input | Limit |
|---|---|
| Image upload | Up to 45 MB |
| Audio upload | Up to 45 MB |
| Video upload | Up to 500 MB |
| Output resolution | 480p, 720p, or 1080p |
| Avatar script | Up to 5,000 characters |
| Reimagine prompt | Up to 2,000 characters |
| Reimagine images | 1–4 per call |
Ready to build?
Create a key, fund your balance, and ship your first render today.
Changelog
What's changed in the API & docs. Last updated 2026-08-20.
- New GET /v1/voices (public, no auth) — the text-to-speech voice catalogue; pick a voice_id for Identity Swap audio_mode: tts.
- New GET /v1/account — your hard limits (rate tier, concurrency, upload sizes, output resolutions, script/prompt caps, realtime/voice minutes) plus balance, so you can validate before uploading. Note: video is capped by size (max_video_mb), not by a fixed duration.
- New GET /v1/jobs — list your jobs newest-first for bulk reconcile/audit (?status=&limit=&offset=), and POST /v1/jobs/{id}/cancel — cancel a queued or processing job; its billing hold is released (refunded) and the slot freed.
- New POST /v1/motion-control — animate a still character image with the motion of a reference video (identity preserved). Billed per output second.
- New POST /v1/reimagine-video — edit a video from a text instruction. Billed per output second.
- New POST /v1/voice-clone — clone the voice in a reference sample speaking your script (up to 1,200 characters) into an audio file. Flat rate per clone.
- POST /v1/avatar now accepts audio_key — supply your own recorded audio and the avatar speaks it (script/voice_id are ignored); script is now optional (required only for text-to-speech).
- Identity Swap text-to-speech is now available: audio_mode: tts with a voice_id (from GET /v1/voices) + script (previously returned capability_unavailable).
- Jobs can now be canceled — the job status set is queued | processing | succeeded | failed | canceled.
- Stricter request validation: unknown request fields are now rejected with 422 invalid_input — send only documented fields.
- Webhook deliveries are now retried with exponential backoff up to 12 attempts before giving up (previously retried indefinitely).
- Added POST /text-to-speech — synthesise a script in a catalogue voice into a standalone audio file (audio only, no video), billed per output second. The standalone counterpart to Identity Swap / Avatar audio_mode: tts, which return video.
- Added held_usd to GET /account and GET /balance — funds reserved on in-flight jobs. balance_usd is your spendable balance, net of active holds (balance_usd + held_usd = total funded).
- GET /v1/voice-changer/voices now serves a dedicated, curated library of generic, ready-to-use target voices maintained by our team (previously drawn from an internal set). The catalog grows over time.
- No contract change: still list voices at runtime via GET /v1/voice-changer/voices (each has an id + sample_url) and pass a voice id to the session — don't hardcode ids.
- Custom audio on POST /v1/identity-swap via audio_mode: keep the driving clip's own sound (original, the default), lip-sync the character to your own uploaded track (own_audio + audio_key), or clone a voice sample and have the character speak a typed script in it (voice_clone + voice_sample_key + script). Text-to-speech (tts) is coming soon — it returns capability_unavailable until the voice library ships.
- New advanced (stepper) mode: submit with mode: advanced to review the pipeline stage by stage. GET /v1/jobs/{id} then also returns stage (extracting → frame_ready → swap_ready) and a signed preview_url of the latest intermediate; push each stage on with POST /v1/identity-swap/{id}/advance (stage: swap, then motion). No extra charge — the whole pipeline still settles per output second on completion.
- New capability — Identity Swap: give a driving video (its scene + motion) and a character image, and get back a brand-new video with your character performing that same motion in the scene. Billed per second of output; async, so you're only charged on success.
- POST /v1/identity-swap — video_key (the driving video) + character_key (the character image), plus optional prompt, aspect_ratio (16:9, 9:16, 1:1, 4:3, 3:4 — defaults to the driving video's ratio), resolution (1k or 2k), and keep_original_sound (default true). Poll GET /v1/jobs/{id} for the output_url.
- Tip: use a SHORT driving clip — the motion is re-applied to your character, so a longer input only makes the output longer (more per-second cost, slower render). A clear, front-facing character portrait gives the cleanest result.
- New capability — Voice Changer: stream microphone audio and get it back converted to a target voice, in real time. Billed per second actually streamed; the session is hard-capped to the funded duration.
- GET /v1/voice-changer/voices — list the curated system target voices (each with a sample_url).
- GET /v1/voice-changer/voices/{id}/sample — play a voice to preview it, or use it as the reference clip.
- POST /v1/voice-changer/session — fund a session (per-minute) and get a scoped token + our branded stream URL; you're charged only for the seconds you stream. If status is "starting", a pod is warming up (nothing charged) — retry in ~30s.
- Streaming: connect the returned stream_url with your session_token, send the target reference as [4-byte big-endian length][WAV bytes], then exchange int16 mono PCM chunks (the server replies with the sample_rate + chunk_frames to use). Stream your own reference clip instead of a voice_id to convert toward your own voice — it's never stored on our side.
- New GET /v1/usage/summary — accurate total calls, succeeded/failed, total spend, and a per-capability breakdown for any date range (?from=&to=). Use this instead of summing the capped /usage log, which only returns the most recent rows.
- GET /v1/balance now also returns spent_this_week_usd, spent_this_month_usd and spent_this_year_usd (alongside the lifetime total_spent_usd) so you can monitor spend per period in one call.
- GET /v1/usage now accepts ?from= and ?to= to scope the returned call log to a date range.
