Get started

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.

One integration
Every studio capability behind a single REST surface and one key.
Pay as you go
Prepaid balance, per-unit pricing, no subscription or minimums.
Built to scale
An isolated render queue keeps your jobs fast and independent.
Own your output
Signed, long-lived URLs — fetch results and store them yourself.
Get started

Quickstart

Three steps to your first render — upload an asset, submit a job, poll for the result.

  1. 1Create an API key in the developer console and fund your balance.
  2. 2Upload each input asset to get a file_key.
  3. 3Submit a capability call, then poll the returned status URL until it succeeds.
Your first call — a talking avatar
# 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"
Get started

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.

Two accepted forms
# 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_..."
Keep your key secret
Your secret key can create charges against your balance. Never embed it in client-side code, mobile apps, or public repositories. Call the API only from your own server. Rotate a key any time from the console — a revoked key immediately returns 401 unauthorized.

Create a separate key per project or environment so usage stays auditable and you can revoke one without disrupting the others.

Get started

Base URL & versioning

All endpoints live under a single versioned base URL:

text
https://api.vectorclone.com/api/v1

The 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).

Core concepts

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.

POST/uploadsmultipart/form-data
FieldTypeRequiredDescription
filefileYesThe asset to upload — an image, audio, or video file. The type is detected from the content type.
Request
curl https://api.vectorclone.com/api/v1/uploads \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY" \
  -F "file=@face.png"
Response
{
  "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.

Core concepts

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.

GET/jobs/{job_id}

A job moves through these statuses:

StatusTerminalMeaning
queuedNoAccepted and waiting for a render slot.
processingNoActively rendering.
succeededYesDone — output_url is populated.
failedYesCould not be completed; error holds a stable code. Held funds are refunded.
canceledYesYou canceled it; any held funds are refunded.
Successful job
{
  "id": "b2f1c0de-...-77",
  "status": "succeeded",
  "output_url": "https://cdn.vectorclone.com/...signed...",
  "credits_charged": 0.42,
  "error": null
}
Signed, expiring output URLs
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.

Core concepts

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.

GET/jobs
QueryTypeDescription
statusstringOptional filter: queued, processing, succeeded, failed, or canceled.
limitintRows to return, 1–200 (default 50).
offsetintRows to skip, for paging (default 0).
Request
curl "https://api.vectorclone.com/api/v1/jobs?status=succeeded&limit=50" \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY"
Response
{
  "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
}
Core concepts

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.

POST/jobs/{job_id}/cancel
Request
curl -X POST https://api.vectorclone.com/api/v1/jobs/$JOB_ID/cancel \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY"
Response
{
  "id": "b2f1c0de-...-77",
  "status": "canceled",
  "output_url": null,
  "credits_charged": 0,
  "error": null
}

Returns the job object with status canceled.

Core concepts

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.

HeaderValue
X-VectorClone-Eventjob.completed
X-VectorClone-SignatureHMAC-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.

Verifying a webhook (Node.js)
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.

Core concepts

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.

Safe to retry
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":"..."}'
Core concepts

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.

TierRequests / minute
default60
pro600

Need a higher tier? Contact us from the console.

Core concepts

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 shape
{
  "error": {
    "code": "insufficient_credits",
    "message": "Your API balance is too low for this call. Top up to continue."
  }
}
HTTPCodeMeaning
401unauthorizedMissing, invalid, or revoked API key.
403forbiddenThis key isn't allowed to perform that action.
402insufficient_creditsBalance too low — top up to continue.
400 / 422invalid_inputA field failed validation.
400unsupported_formatThe uploaded file type isn't supported.
400input_too_largeAsset exceeds the size/duration limit.
400capability_unavailableThis capability is not currently enabled.
404not_foundThe job doesn't exist or isn't yours.
429rate_limitedToo many requests — slow down and retry.
429at_capacityPlatform momentarily saturated — retry shortly.
500internal_errorSomething went wrong on our side — safe to retry.
Unknown fields are rejected
Requests are strictly validated: any field not documented here is rejected with 422 invalid_input. Send only the documented fields.

Failed jobs (returned by the status endpoint) use these result codes:

CodeMeaning
face_not_detectedNo usable face was found in an input.
unsupported_formatThe uploaded file type isn't supported.
input_too_largeAsset exceeds the size/duration limit.
content_rejectedThe input violated our content policy.
processing_failedThe render failed for another reason (auto-refunded).
Capabilities

Avatar Studio

Turn a single portrait into a talking avatar video — speaking a typed script (text-to-speech) or your own recorded audio.

POST/avatar
FieldTypeRequiredDescription
image_keystringYesfile_key of the source portrait (from /uploads).
scriptstringNoWhat the avatar says (text-to-speech). Up to 5,000 characters. Required unless audio_key is supplied.
audio_keystringNofile_key of your OWN recorded audio for the avatar to speak. When supplied, script and voice_id are ignored.
voice_idstringNoA specific voice to speak the script.
languagestringNoLanguage hint for the voice.
output_resolution_pintNo480, 720, or 1080.
callback_urlstringNoWebhook POSTed on the terminal status.
Two ways to give the avatar a voice
Supply 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).
Text-to-speech (a typed script)
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 audio
# 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"
  }'
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

Face Swap — video

Swap a face into a source video. Billed per second of output.

POST/face-swap
FieldTypeRequiredDescription
video_keystringYesfile_key of the source video.
face_keystringYesfile_key of the face image to swap in.
max_duration_secintNoCap the processed duration.
output_resolution_pintNo480, 720, or 1080.
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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"
  }'
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

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.

POST/face-swap-image
FieldTypeRequiredDescription
image_keystringYesfile_key of the base image.
face_keystringYesfile_key of the face image to swap in.
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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"
  }'
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

Lip Sync

Re-sync a video's mouth movements to a new audio track.

POST/lip-sync
FieldTypeRequiredDescription
video_keystringYesfile_key of the source video.
audio_keystringYesfile_key of the audio to sync to.
output_resolution_pintNo480, 720, or 1080.
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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"
  }'
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

Reimagine

Edit or restyle up to four images from a text prompt.

POST/reimagine-image
FieldTypeRequiredDescription
image_keysstring[]Yes1–4 file_keys of the source images.
promptstringYesThe edit instruction. Up to 2,000 characters.
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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"
  }'
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

Reimagine — video

Edit a video from a text instruction. Billed per second of output.

POST/reimagine-video
FieldTypeRequiredDescription
video_keystringYesfile_key of the source video to edit.
promptstringYesThe edit instruction — what to change in the video.
negative_promptstringNoOptional — elements the edit should avoid.
max_duration_secintNoCap the output length, 1–60 seconds.
output_resolution_pintNo480, 720, or 1080.
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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
  }'
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

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.

POST/identity-swap
FieldTypeRequiredDescription
video_keystringYesfile_key of the driving video — it provides the scene and the motion.
character_keystringYesfile_key of the character image to place into the scene.
modestringNoexpress (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.
promptstringNoInstruction for the swap. A sensible default is used if omitted.
aspect_ratiostringNoOne of 16:9, 9:16, 1:1, 4:3, 4:5. Defaults to the driving video's ratio.
resolutionstringNo1k (default) or 2k.
keep_original_soundboolNoKeep the driving video's audio in the result. Default true.
audio_modestringNoHow the character's audio is produced: original (default), own_audio, voice_clone, or tts. See Audio options below.
audio_keystringNoown_audio only — file_key of an audio track the character is lip-synced to. Required when audio_mode is own_audio.
voice_sample_keystringNovoice_clone only — file_key of a voice sample to clone. Required when audio_mode is voice_clone.
scriptstringNovoice_clone / tts only — the text the cloned or catalogue voice speaks. Required when audio_mode is voice_clone or tts.
voice_idstringNotts only — a voice id from GET /voices for the character to speak your script. Required when audio_mode is tts.
languagestringNotts only — spoken language hint (defaults to auto-detect).
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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"
  }'
Getting the best result
Use a short driving clip — its motion is re-applied to your character, so a longer video just makes the output longer (more per-second cost and a slower render). A clear, front-facing character portrait gives the cleanest placement. The result keeps the driving video's audio unless you set keep_original_sound to false.
202 Accepted
{
  "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_modeAlso sendResult
originalKeeps the driving video's own sound (default).
own_audioaudio_keyThe character is lip-synced to the audio track you upload.
voice_clonevoice_sample_key + scriptClones the voice sample, then the character speaks your script in that voice.
ttsvoice_id + scriptThe character speaks your typed script in a catalogue voice.
Clone a 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."
  }'
Text-to-speech (catalogue voice)
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.

  1. 1Submit POST /v1/identity-swap with mode: advanced. You get a job id back, same as express.
  2. 2Poll the job status: it reports stage: extracting, then stage: frame_ready with a preview_url of the extracted frame.
  3. 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.
  4. 4When stage is swap_ready, call advance with stage: motion. The final video renders and the job completes with output_url.
POST/identity-swap/{id}/advance
FieldTypeRequiredDescription
stagestringYesswap (after frame_ready) or motion (after swap_ready).
promptstringNoswap only — override the swap instruction.
aspect_ratiostringNoswap only — override the output aspect ratio.
resolutionstringNoswap only — override the render resolution (1k or 2k).
character_keystringNoswap only — replace the character image.
keep_original_soundboolNomotion only — override keeping the driving video's audio.
Advance the job, one stage at a time
# 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" }'
Polling an advanced job
{
  "id": "b2f1c0de-...-77",
  "status": "processing",
  "stage": "frame_ready",
  "preview_url": "https://cdn.vectorclone.com/...signed...",
  "output_url": null,
  "error": null
}
No extra charge
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.
Capabilities

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.

POST/motion-control
FieldTypeRequiredDescription
image_keystringYesfile_key of the still character image to animate.
video_keystringYesfile_key of the motion-reference video (its motion drives the image).
character_orientationstringNoWhich way the character faces: video (derive from the reference), front, side, or back. Default video.
max_duration_secintNoCap the output length, 3–30 seconds.
promptstringNoOptional guidance for the animation.
negative_promptstringNoOptional — elements to avoid.
keep_original_soundboolNoKeep the reference video's audio on the output. Default false.
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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"
  }'
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

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.

POST/real-time-swap/session
FieldTypeRequiredDescription
duration_minutesintYesMaximum 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.
promptstringNoOptional swap instruction. A sensible default is used if omitted.
Create a session
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 }'
Response
{
  "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.

  1. 1Open a WebSocket to stream_url?session_token=… (the token is all that's needed).
  2. 2On open, send {"type":"session_join","passthrough":false}, then a set_image (to swap to a reference face) or a prompt message.
  3. 3Receive a room_info message with a livekit_url + token.
  4. 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).
1 · open the signaling socket + start
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 }));
};
2 · room_info → join the media room
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]);
};
Billing, duration & the media hop
You're billed per second actually streamed — nothing on create, and nothing if the stream never connects. 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.
Capabilities

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).

GET/voice-changer/voices
Request
curl https://api.vectorclone.com/api/v1/voice-changer/voices \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY"
Response
[
  {
    "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
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.

GET/voice-changer/voices/{id}/sample
Download a voice sample
curl https://api.vectorclone.com/api/v1/voice-changer/voices/$VOICE_ID/sample \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY" \
  --output voice-sample.wav

Start 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.

POST/voice-changer/session
FieldTypeRequiredDescription
duration_minutesintYesMinutes to fund. Charged per second actually streamed; the session ends at this length.
voice_idstringNoA curated target voice from GET /voice-changer/voices. Omit to stream your own reference clip over the session instead.
Create a session
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" }'
Response
{
  "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
}
Pod warming up?
If the response 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.

  1. 1Connect: new WebSocket(stream_url + "?session_token=" + session_token).
  2. 2Handshake — send the target reference as [4-byte big-endian length][WAV bytes]. Use your own clip, or the bytes from a system voice's sample_url.
  3. 3The server replies once with the audio format to use — sample_rate (e.g. 16000) and chunk_frames (e.g. 2400), int16 mono PCM.
  4. 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.
Connect + send the target voice
// 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);
};
Stream your microphone
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); }
Good to know
Billed per second streamed, capped to the funded duration. A custom voice you upload is never stored on our side — your client streams the reference clip per session and it's discarded when the session ends.
Capabilities

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.

POST/voice-clone
FieldTypeRequiredDescription
voice_sample_keystringYesfile_key of a clean voice sample to clone — a few seconds of speech.
scriptstringYesThe text the cloned voice speaks. Up to 1,200 characters.
languagestringNoOptional language hint for the script.
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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."
  }'
Keep scripts short
Scripts are capped at 1,200 characters. For longer speech, use text-to-speech.
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

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.

POST/text-to-speech
FieldTypeRequiredDescription
scriptstringYesThe text to speak. Up to 5,000 characters.
voice_idstringYesA voice id from GET /voices — the catalogue voice that speaks the script.
languagestringNoOptional spoken-language hint (auto-detected if omitted).
callback_urlstringNoWebhook POSTed on the terminal status.
bash
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"
  }'
Standalone audio vs. the tts audio mode
This endpoint returns an audio file. It's the standalone counterpart to the 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).
202 Accepted
{
  "id": "b2f1c0de-...-77",
  "status": "queued",
  "status_url": "/v1/jobs/b2f1c0de-...-77"
}
Capabilities

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).

GET/voicespublic — no auth
FieldTypeDescription
idstringThe voice_id to pass to text-to-speech.
namestringA human-readable label (character and tone).
languagestringThe voice's language.
Request
curl https://api.vectorclone.com/api/v1/voices
Response
[
  { "id": "Alex", "name": "Alex (warm, masculine)", "language": "English" },
  { "id": "Maya", "name": "Maya (bright, feminine)", "language": "English" }
]
Account

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.

GET/account
FieldTypeDescription
rate_tierstringYour rate-limit tier name.
rate_limit_per_minuteintRequests allowed per minute (429 rate_limited past this).
max_concurrent_jobsintMaximum jobs processing at once (429 at_capacity past this).
max_image_mbintLargest image upload, in MB.
max_audio_mbintLargest audio upload, in MB.
max_video_mbintLargest video upload, in MB.
allowed_output_resolutions_pint[]Output resolutions you may request (in p).
max_script_charsintLongest avatar/voice script, in characters.
max_prompt_charsintLongest prompt, in characters.
max_realtime_minutesintTotal Real-Time Swap minutes available.
max_voice_minutesintTotal Voice Changer minutes available.
balance_usdnumberYour live spendable balance in USD — already net of any active holds.
held_usdnumberFunds reserved on in-flight jobs (refunded if a job fails or is canceled). balance_usd + held_usd = total funded.
Request
curl https://api.vectorclone.com/api/v1/account \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY"
Response
{
  "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
}
No fixed video duration cap
There is no fixed maximum video DURATION — uploaded video is capped by max_video_mb (file size), not by length. Real-time and voice sessions are capped by the *_minutes values.
Account

Balance

Check your prepaid balance and lifetime spend programmatically — handy for dashboards, low-balance alerts, or gating your own usage.

GET/balance
Request
curl https://api.vectorclone.com/api/v1/balance \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY"
Response
{
  "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
}
Spendable balance vs. held funds
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.

Account

Usage

Your recent API calls and what each was charged, newest first.

GET/usage
QueryTypeDescription
limitintHow many rows to return (default 100, max 500).
fromISO 8601Optional. Only calls at or after this time (e.g. 2026-07-01T00:00:00Z).
toISO 8601Optional. Only calls before this time.
Request
curl "https://api.vectorclone.com/api/v1/usage?limit=50" \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY"
Response
[
  {
    "id": "b2f1c0de-...-77",
    "endpoint": "avatar",
    "feature": "avatar",
    "status": "succeeded",
    "charged_usd": 0.42,
    "created_at": "2026-07-12T17:55:03Z"
  }
]
Account

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.

GET/usage/summary
QueryTypeDescription
fromISO 8601Optional. Start of the range (inclusive).
toISO 8601Optional. End of the range (exclusive). Omit both for all-time.
Request — spend so far this month
curl "https://api.vectorclone.com/api/v1/usage/summary?from=2026-07-01T00:00:00Z" \
  -H "Authorization: Bearer $VECTORCLONE_API_KEY"
Response
{
  "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 }
  ]
}
Reference

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.

CapabilityUnitPrice / unitMin 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.

Reference

Limits & formats

InputLimit
Image uploadUp to 45 MB
Audio uploadUp to 45 MB
Video uploadUp to 500 MB
Output resolution480p, 720p, or 1080p
Avatar scriptUp to 5,000 characters
Reimagine promptUp to 2,000 characters
Reimagine images1–4 per call

Ready to build?

Create a key, fund your balance, and ship your first render today.

Get your API key
Reference

Changelog

What's changed in the API & docs. Last updated 2026-08-20.

2026-08-20New endpoints, your own avatar audio, text-to-speech & stricter validation
  • 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).
2026-08-17Voice Changer — curated generic voice library
  • 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.
2026-08-17Identity Swap — audio options + advanced (stepper) mode
  • 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.
2026-08-17Identity Swap
  • 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.
2026-08-17Voice Changer (real-time)
  • 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.
2026-07-25Accurate spend + usage aggregates
  • 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.