FreeLipSync API documentation

Generate lip sync and music videos with the REST API. Copy cURL, Python and JavaScript examples for uploads, quotes, jobs and downloads.

On this page

Before you begin

Use the FreeLipSync REST API to generate lip sync, talking-photo and music videos from your own audio. You need a registered account, an API Key from Agent console, and API credits. Every account receives 30 one-time welcome credits on its first Agent-console balance load or valid generation submission, including existing users. Further credits can be purchased; membership does not include recurring API credits. Membership controls available models, duration and shared task limits.

This guide uses https://freelipsync.com/api/v1. When testing on staging, set FLS_BASE_URL to that environment's origin and create your key in the same environment. API access must be enabled on that deployment. The examples use a short speech.mp3 and a single-face face.jpg.

Open the OpenAPI reference for machine-readable request and response schemas. Prefer an agent? See the MCP setup guide.

Authentication and your first request

Create a key in the Agent console and copy it from the creation dialog. The full secret is shown once. Store it in an environment variable, not browser code or a public repository. If lost, revoke it and create another. All keys use the same account balance and limits; creating another key does not increase concurrency.

bash
export FLS_BASE_URL='https://freelipsync.com'
export FLS_API_KEY='YOUR_API_KEY'
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $FLS_API_KEY" \
  "$FLS_BASE_URL/api/v1/capabilities"

REST accepts your API Key. An OAuth access token issued for MCP is only valid for /mcp, not for REST. A successful response wraps its result in data and includes a request_id; errors have error and request_id instead. IDs are opaque strings and credit amounts are decimal strings.

json
{"data":{"id":"YOUR_ASSET_ID","kind":"image","status":"processing","poll_after_seconds":5,"expires_at":"2026-10-21T00:00:00.000Z"},"request_id":"REQUEST_ID"}

This is an illustrative asset response, not the response to the capabilities request. Read the returned capabilities to check the current account's models, max_audio_seconds, upload byte/MIME limits and task limits before preparing files.

The generation workflow

  1. Fetch capabilities and upload an image or video plus the driving audio.
  2. Confirm each direct upload and wait for both assets to become ready.
  3. Create a quote using the asset IDs and a generation mode. Review total_points and expires_at.
  4. Submit that quote with a stable idempotency key. This accepts the task and holds credits.
  5. Poll the returned job ID using poll_after_seconds, or receive a webhook.
  6. When completed, download from the signed URL. A failed task returns its original hold.

Creating assets and quotes does not spend credits or create video jobs. Once a generation has been accepted, it cannot be cancelled. Only processing failure returns credits; a client timeout is not proof that the task failed.

Upload local files or import URLs

For a local file, POST /assets returns a signed PUT URL. Send raw file bytes with the returned upload.headers, without your API Key. Then call POST /assets/{id}/confirm with {} and poll GET /assets/{id}. awaiting_upload and processing are not ready; failed is terminal. Use the server's polling advice.

json
{
  "kind":"image",
  "source":{
    "type":"upload",
    "filename":"face.jpg",
    "content_type":"image/jpeg",
    "size_bytes":123456
  }
}

size_bytes must be the actual byte count, not this example number. Image, video and audio inputs have different MIME and size limits; use GET /capabilities as the source of truth. File extensions alone do not determine validity.

You can also import a publicly reachable HTTPS URL. This is asynchronous: poll the returned asset until ready. A local path, file:// URL, private-network URL or URL requiring your website cookies is not a usable source. If using a signed source URL, give it enough time to be fetched.

bash
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $FLS_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: image-import-UNIQUE-REQUEST-ID' \
  --data '{"kind":"image","source":{"type":"url","url":"https://YOUR-PUBLIC-HOST/face.jpg"}}' \
  "$FLS_BASE_URL/api/v1/assets"

A third route is POST /asset-upload-sessions with {"kind":"image"}: return the browser_upload_url to the user, retain asset_id, and poll it after they upload in the browser. This is useful for clients that cannot read local files. Upload tickets and signed URLs are temporary credentials; do not publish them in logs or chat transcripts.

Complete code examples

Choose cURL, Python or JavaScript below. Each example performs the same upload → quote → submit → poll → download workflow, printing the quote and asking before spending credits. Long examples are collapsed; the Copy button always copies the complete program.

  • cURL: Bash, cURL, jq and Python 3 (for UUIDs). Save as a Bash script and run it.
  • Python: Python 3 with requests; install using python -m pip install requests.
  • JavaScript: Node.js 20+ and built-in modules only. Save as quickstart.mjs and run node quickstart.mjs.

Export the environment variables above and place face.jpg and speech.mp3 in the working directory. Run on your server or development machine, not in a public web page. Signed upload/download requests never include your API Key. Scripts stop on HTTP errors; check recovery rules below before retrying, rather than restarting a possibly accepted submission with a new key.

# Requires bash, curl and jq. Set these before running:
# export FLS_API_KEY='YOUR_API_KEY'
# export FLS_BASE_URL='https://freelipsync.com'  # or your staging origin
# Place face.jpg and speech.mp3 in this directory.
set -euo pipefail
: "${FLS_API_KEY:?Set FLS_API_KEY first}"
BASE="${FLS_BASE_URL:-https://freelipsync.com}/api/v1"
RUN="$(python3 -c 'import uuid; print(uuid.uuid4())')"
request() {
  curl --fail-with-body --silent --show-error \
    -H "Authorization: Bearer $FLS_API_KEY" "$@"
}
wait_ready() {
  local id="$1" row status
  for ((i=0; i<120; i++)); do
    row="$(request "$BASE/assets/$id")"
71 lines

Models, options and credits

ModeFace inputCredits / secondOptional settings
video_lipsyncVideo1None
image_fastImage1None
image_maxImage2None
image_superImage3options.motion.action, options.motion.camera
music_videoImage3options.mv

All modes require an uploaded driving audio asset. Text-to-speech, voice cloning and cancellation are not parameters of this API. Image Super / Motion Control requires Starter or Pro; buying credits alone does not enable it. Check capabilities for model availability and duration limits, including model-specific exceptions.

Billing rounds the verified audio duration up to a whole second, with a minimum of 5 seconds. A 5-second Image Fast quote is 5 credits; Image Max is 10; Image Super or Music Video is 15. Quote creation does not debit your wallet. Submission holds the quote amount; success charges that same amount, and processing failure releases it. There is no post-generation price adjustment. Purchased credits are separate from membership, do not expire, and cannot be refunded for cash.

json
{
  "mode":"image_super",
  "face_asset_id":"READY_IMAGE_ID",
  "audio_asset_id":"READY_AUDIO_ID",
  "options":{"motion":{"action":"Smile gently","camera":"Static camera"}}
}

For Music Video, use mode: "music_video" and the MV settings below. version accepts 1.0 or 1.1; ratio accepts 21:9, 16:9, 4:3, 1:1, 3:4 or 9:16; resolution accepts 480p or 720p. Defaults are 1.0, 16:9 and 480p. Check capabilities for the live schema. Do not attach motion options to another mode or send internal web model aliases.

json
{
  "mode":"music_video",
  "face_asset_id":"READY_IMAGE_ID",
  "audio_asset_id":"READY_AUDIO_ID",
  "options":{"mv":{"version":"1.0","ratio":"16:9","resolution":"480p","style":{"directorial":"A calm performance","artistic":"Warm evening light"}}}
}

Endpoint reference

Paths below are relative to /api/v1. All business calls require authentication.

Method and pathPurposeIdempotency-Key
GET /capabilitiesModels, upload limits, durations and account limits—
GET /usageAvailable, held and spent credits; current usage windows—
POST /assetsDirect upload intent or HTTPS URL importRequired
POST /asset-upload-sessionsCreate a user-facing browser upload linkRequired
POST /assets/{id}/confirmConfirm direct upload, body {}Required
GET /assets/{id}Poll asset state—
POST /quotesQuote ready assets, mode and options—
POST /generationsSubmit quote_id, optional webhook_endpoint_idRequired
GET /generations/{id}Job, billing state and output—
GET /generationsOwned API/MCP tasks; optional ids, status, cursor, limit—
GET /generations/{id}/download302 redirect to a temporary download URL—
GET /credit-transactionsCredit ledger, paginated—

Lists default to 20 items, maximum 50. Pass data.next_cursor unchanged to fetch the next page. For several active jobs, use comma-separated ids in one list request instead of polling each separately. The list is for API/MCP credit jobs, not a general list of your older website jobs.

Idempotency, errors and rate limits

Use a fresh Idempotency-Key for each distinct write operation, and keep the same key and payload when retrying that operation. Keys are 8–128 visible ASCII characters. A new user request needs a new key; a retry does not. The examples print the quote/key pair before submission. In production, persist that pair before sending and persist the returned job ID. Do not restart the whole example after an uncertain submission: that would create a new key.

Quotes expire in 10 minutes. A definite rejection before acceptance lets you correct the request or obtain a fresh quote; an uncertain response requires recovering the original attempt first. Replaying an accepted task returns its original job without a second debit. A quote cannot be spent twice under different keys.

SituationWhat to do
401 AUTH_REQUIRED / invalid credentialCheck environment and key; use MCP OAuth tokens only with MCP
403 / insufficient model accessInspect capabilities and account membership
404 NOT_FOUNDCheck the resource ID and owning account; do not probe other users' IDs
409 ASSET_NOT_READYWait for the asset to be ready before quoting
409 IDEMPOTENCY_CONFLICTSame key used for a different operation payload; recover the original first
QUOTE_ALREADY_USEDRecover the original job; do not create another key to spend the same quote
429Honor Retry-After; keep the same pending operation/key
413 / size errorCheck JSON and media size limits; shrink or replace the source
Network failure, 5xx or error.outcome: "unknown"Do not assume rejection; retry the same quote/key or query the saved job ID

REST and MCP share a per-account read/auxiliary bucket: Free 30, Starter 30, Pro 60 requests per minute. Auxiliary calls include uploads, confirmation and quotes. Respect poll_after_seconds and Retry-After, whichever requires a longer wait. Website status reads are managed separately; generation frequency and video execution capacity are shared across website, REST and MCP. The website and API/MCP each have an independent unfinished-task allowance: Free 1, Starter 3, Pro 10 per pool. REST and MCP share the API/MCP pool, including all keys and OAuth connections; extra credentials never multiply it. See why use API/MCP for workflow examples.

Results and optional webhooks

Jobs have queued, processing, completed or failed status. Keep the job ID when your application times out; your timeout does not cancel the server task. On success, use output.download_url while valid. Refetch the job or download endpoint to get a renewed link while the work is retained; repeat downloads do not spend credits. Retention follows your account's normal work retention, not a separate API-only period. Output is original quality, without a watermark, and usable commercially subject to your source rights and the terms.

For server integrations, create a webhook endpoint in Agent console → Advanced, then pass its ID as webhook_endpoint_id on submission. Your endpoint must first answer the endpoint.verification POST with the challenge as a plain-text response body. This setup uses the website console, not a public credential-management API.

Webhook events contain event_id, event_type, occurred_at, generation_id and data. Download links are omitted from webhook payloads; query the job for a fresh link. Verify FLS-Webhook-Signature against the raw request body: HMAC-SHA256 of timestamp + "." + body, using your endpoint secret, with the timestamp from FLS-Webhook-Timestamp; match a v1= signature in constant time and enforce a recent timestamp to reject replays. Deduplicate by event_id / FLS-Webhook-Id and return 2xx promptly. Duplicate notifications must not trigger another generation or a second business action. Keep polling as the simplest first integration.

Need to work through an assistant instead of HTTP? Set up MCP with your agent, or return to Agent console.