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.
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.
{"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
- Fetch capabilities and upload an image or video plus the driving audio.
- Confirm each direct upload and wait for both assets to become
ready. - Create a quote using the asset IDs and a generation mode. Review
total_pointsandexpires_at. - Submit that quote with a stable idempotency key. This accepts the task and holds credits.
- Poll the returned job ID using
poll_after_seconds, or receive a webhook. - 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.
{
"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.
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.mjsand runnode 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")"Models, options and credits
| Mode | Face input | Credits / second | Optional settings |
|---|---|---|---|
video_lipsync | Video | 1 | None |
image_fast | Image | 1 | None |
image_max | Image | 2 | None |
image_super | Image | 3 | options.motion.action, options.motion.camera |
music_video | Image | 3 | options.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.
{
"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.
{
"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 path | Purpose | Idempotency-Key |
|---|---|---|
GET /capabilities | Models, upload limits, durations and account limits | — |
GET /usage | Available, held and spent credits; current usage windows | — |
POST /assets | Direct upload intent or HTTPS URL import | Required |
POST /asset-upload-sessions | Create a user-facing browser upload link | Required |
POST /assets/{id}/confirm | Confirm direct upload, body {} | Required |
GET /assets/{id} | Poll asset state | — |
POST /quotes | Quote ready assets, mode and options | — |
POST /generations | Submit quote_id, optional webhook_endpoint_id | Required |
GET /generations/{id} | Job, billing state and output | — |
GET /generations | Owned API/MCP tasks; optional ids, status, cursor, limit | — |
GET /generations/{id}/download | 302 redirect to a temporary download URL | — |
GET /credit-transactions | Credit 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.
| Situation | What to do |
|---|---|
401 AUTH_REQUIRED / invalid credential | Check environment and key; use MCP OAuth tokens only with MCP |
403 / insufficient model access | Inspect capabilities and account membership |
404 NOT_FOUND | Check the resource ID and owning account; do not probe other users' IDs |
409 ASSET_NOT_READY | Wait for the asset to be ready before quoting |
409 IDEMPOTENCY_CONFLICT | Same key used for a different operation payload; recover the original first |
QUOTE_ALREADY_USED | Recover the original job; do not create another key to spend the same quote |
429 | Honor Retry-After; keep the same pending operation/key |
413 / size error | Check 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.