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 sk_live_..."

# or the X-API-Key header
curl https://api.vectorclone.com/api/v1/pricing \
  -H "X-API-Key: sk_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.
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

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();
});
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.
402insufficient_creditsBalance too low — top up to continue.
400invalid_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.

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

CodeMeaning
face_not_detectedNo usable face was found in an input.
content_rejectedThe input violated our content policy.
processing_failedThe render failed for another reason (auto-refunded).
Capabilities

Avatar Studio

Turn a single portrait plus a script into a talking avatar video.

POST/avatar
FieldTypeRequiredDescription
image_keystringYesfile_key of the source portrait (from /uploads).
scriptstringYesWhat the avatar says. Up to 5,000 characters.
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.
bash
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
  }'
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.

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

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.
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,
  "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
}

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 /usagelog. 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

Changelog

What's changed in the API & docs. Last updated 2026-07-25.

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