Start here

What you get

This API puts FazeSwap’s face and video models behind an HTTP call. You post an image, a clip or a live stream; you get back the same thing with a different face on it. Seven features, one key, nothing to install.

Two shapes of call. Most features are asynchronous: you submit a job, poll it, and collect a signed URL when it finishes. Full Live Swap is a session — you open it, stream through it, and close it, charged by the second you actually use.

Every call is authenticated with a bearer key and paid for from a balance you top up in advance. Nothing recurring, no floor to clear, and a failed job costs nothing.

Start here

Your first call

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

Your first call
# 1 · Upload an asset → get a file_key
FILE_KEY=$(curl -s https://api.fazeswap.com/api/v1/uploads \
  -H "Authorization: Bearer $FAZESWAP_API_KEY" \
  -F "file=@base.png" | jq -r .file_key)

# 2 · Submit the job
JOB=$(curl -s https://api.fazeswap.com/api/v1/face-swap-image \
  -H "Authorization: Bearer $FAZESWAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"image_key\":\"$FILE_KEY\",\"face_key\":\"$FACE_KEY\"}")

# 3 · Poll until it is terminal
curl -s https://api.fazeswap.com/api/v1/jobs/$(echo "$JOB" | jq -r .id) \
  -H "Authorization: Bearer $FAZESWAP_API_KEY"

Start here

Using your key

Send your key as a bearer token on every request.

Header
Authorization: Bearer fzs_live_xxxxxxxxxxxxxxxxxxxxxxxx

Keep your key secret

Your key spends your balance. Call the API from your own server only — never from browser or mobile code, where anyone can read it. Keys are shown once and stored only as a hash, so we cannot recover one for you; revoke and create another instead. A revoked key stops working immediately.

Start here

Endpoints & versions

Base URL
https://api.fazeswap.com/api/v1

How it works

Sending files

Capability calls reference inputs by file_key, not by URL. Upload each asset first and pass back the key you get. We host the upload — there is no CORS or presign dance, and no external URLs are fetched.

POST/v1/uploadsmultipart/form-data
FieldTypeRequiredDescription
filefileYesImage, audio or video. Type is detected from the content type.
Response
{
  "file_key": "api/images/9f8c…/base.png",
  "url": "https://…"
}

Images and audio up to 45 MB, video up to 500 MB.

How it works

Getting results back

Every capability except Full Live Swap is asynchronous: a submit returns immediately with an id, and you poll its status URL until it reaches a terminal state.

GET/v1/jobs/{job_id}
Response
{
  "id": "…",
  "status": "queued | processing | succeeded | failed",
  "output_url": "https://…",
  "charged_usd": 0.06,
  "error": { "code": "face_not_detected", "message": "…" }
}

Output URLs expire

Results are signed links with a limited lifetime. Download the output and store it on your own infrastructure rather than linking to it long-term.

You are billed when a job completes. A job that fails is refunded in full — you are never charged for a result you did not get.

How it works

Safe retries

Send an Idempotency-Key header on any submit. A retry carrying the same key returns the original job — no second charge, no duplicate render. A network timeout costs you nothing.

Safe to retry
-H "Idempotency-Key: your-own-unique-id"

How it works

Throughput caps

60 requests per minute by default. Exceeding it returns 429 with code rate_limited.

rate_limited is not at_capacity

rate_limited means you are sending too fast — back off and retry. at_capacity means the platform is briefly saturated; retry shortly. They look alike and need different responses.

How it works

When a call fails

Errors carry a stable code you can branch on and a message meant for a human. Branch on the code — the prose may change, the codes will not.

HTTPCodeMeaning
401unauthorizedMissing, unknown or revoked key.
402insufficient_balanceTop up to continue.
400invalid_inputA field failed validation.
400unsupported_formatThat file type isn't accepted.
400input_too_largeOver the size or duration limit.
400capability_unavailableCurrently switched off.
404not_foundNo such job, or not yours.
429rate_limitedSlow down and retry.
429at_capacityBriefly saturated — retry shortly.
503capacity_unavailableNo capacity for a live session right now.
503capacity_warmingVoice capacity is starting up — retry in about a minute.
500internal_errorFailed on our side. Nothing was billed; retry, and tell us if it persists.
face_not_detectedJob-level: no face in the input.
content_rejectedJob-level: failed a content check.
processing_failedJob-level: the render didn't complete.

Features

Character Swap

Put your character into a reference video, keeping its motion.

POST/v1/character-swap
FieldTypeRequiredDescription
video_keystringYesfile_key of the reference video.
character_keystringYesfile_key of the character image.
resolutionstringNo1k or 2k. Defaults to 1k.
callback_urlstringNoWebhook POSTed the terminal status.

Features

Face Swap — video

Swap a face into a video.

POST/v1/face-swap
FieldTypeRequiredDescription
video_keystringYesfile_key of the source video.
face_keystringYesfile_key of the face to swap in.
output_resolution_pintNo480, 720 or 1080.

Features

Face Swap — image

Swap a face into a single image.

POST/v1/face-swap-image
FieldTypeRequiredDescription
image_keystringYesfile_key of the base image.
face_keystringYesfile_key of the face to swap in.
Request
curl https://api.fazeswap.com/api/v1/face-swap-image \
  -H "Authorization: Bearer $FAZESWAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_key":"api/images/…","face_key":"api/images/…"}'

Features

Motion Control

Animate a still character image to match a reference video’s motion.

POST/v1/motion-control
FieldTypeRequiredDescription
image_keystringYesfile_key of the character image.
motion_video_keystringYesfile_key of the motion reference.

Features

Avatar

A talking avatar from one portrait and a script.

POST/v1/avatar
FieldTypeRequiredDescription
image_keystringYesfile_key of the source portrait.
scriptstringYesWhat the avatar says. Up to 5,000 characters.
voice_idstringNoA specific voice. A default is chosen if omitted.
languagestringNoLanguage hint for the voice.
output_resolution_pintNo480, 720 or 1080.

Features

Lip Sync

Match a video’s mouth movement to your audio.

POST/v1/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.

Features

Full Live Swap

Real-time face swap over a live stream. Unlike the others this is a session, not a job: create one, connect over WebSocket, and stream.

POST/v1/full-live-swap/session
FieldTypeRequiredDescription
duration_minutesintYesMinutes to fund. The stream hard-stops here. 1–240.
promptstringNoOptional swap instruction.
Response
{
  "session_id": "…",
  "stream_url": "wss://api.fazeswap.com/realtime",
  "session_token": "rt_…",
  "max_duration_sec": 600,
  "max_cost_usd": 25.00
}
WS{stream_url}?session_token={session_token}

You pay for seconds streamed, not the block reserved

max_cost_usd is the ceiling — keep at least that much available to start. You are billed for the seconds you actually stream, and a session that never starts costs nothing.

Features

Voice Changer

Real-time voice conversion. Like Full Live Swap this is a session rather than a job: open one, stream audio frames over the socket, and receive converted audio back on the same connection.

Pick a target voice first. The reference clip stays on our side — you never handle it.

GET/v1/voices
Response
[
  {
    "id": "8f2c…",
    "name": "Narrator",
    "description": "Warm, measured.",
    "preview_url": "https://…/preview.mp3"
  }
]
POST/v1/voice/session
FieldTypeRequiredDescription
duration_minutesintNoMinutes to fund. The session hard-stops here. 1–120, default 10.
voice_profile_idstringNoid from GET /v1/voices. Omit to pass audio through unchanged — useful for measuring latency before picking a voice.
Response
{
  "session_id": "…",
  "stream_url": "wss://api.fazeswap.com/realtime",
  "session_token": "rt_…",
  "max_duration_sec": 600,
  "max_cost_usd": 3.60
}
WS{stream_url}?session_token={session_token}

On connect we hand the engine your chosen voice, then send you one JSON frame with the audio format to use in both directions:

Response
{ "sample_rate": 22050, "chunk_frames": 4096 }

After that it is audio both ways: send raw mono PCM (int16, little-endian, at the sample_rate above) as binary frames, and converted audio comes back in the same format. Send roughly chunk_frames per message — much smaller wastes round-trips, much larger adds latency.

You send audio, nothing else

There is no handshake for you to implement. The engine needs the target voice before it can convert, and we send it for you when the socket opens.

Billed by the second, like Full Live Swap

Nothing is debited when you open the session. You are charged for the seconds you actually stream, so a session that never connects costs nothing.

There is no on-device fallback

If we have no capacity free the call fails with capacity_unavailable or capacity_warming rather than returning a session that cannot carry audio. Retry shortly — warming is usually under a minute.

Billing & usage

Your balance

Check your prepaid balance and spend from your own system.

GET/v1/balance
Response
{
  "balance_usd": 84.20,
  "total_spent_usd": 15.80,
  "spent_this_week_usd": 4.10,
  "spent_this_month_usd": 15.80,
  "spent_this_year_usd": 15.80
}

Billing & usage

What you’ve spent

GET/v1/usage?limit&from&to
GET/v1/usage/summary?from&to

Use /usage/summary for totals — it is computed from your full history. The /usage log is capped at 500 rows, so summing it under-reports once you are busy.

Billing & usage

Rate card

Live rates, read from the API itself — this table cannot go stale. Public, no authentication required.

GET/v1/pricingno auth

Ready to build?

Create a key and add funds in the console.

Open the console