Swap faces in any video by uploading a video and an image — perform unlimited, seamless face replacements quickly. Ready-to-use REST inference API, best performance, no coldstarts, affordable pricing.
Integration Steps
Asynchronous Polling1.8s for up to 180s.succeeded / failed / cancelled.output_payload.assets[] URLs.Step 1 · Create Request
Submit generation input. Choose a backend language example below.
curl -X POST https://api.openoctopus.com/v1/videos/generations \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer ooq_your_api_key" \\
-d '{
"model": "openoctopus/video-face-swap",
"prompt": "a cinematic octopus swimming through a neon underwater city",
"input": {
"duration": 5,
"resolution": "720p"
}
}'Poll Task Status
Use the task ID from create response to poll execution status.
curl https://api.openoctopus.com/v1/tasks/task_id_from_previous_response \
-H "Authorization: Bearer ooq_your_api_key"const timeoutMs = 180000;
const intervalMs = 1800;
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
const r = await fetch("https://api.openoctopus.com/v1/tasks/task_id_from_previous_response", {
headers: { Authorization: "Bearer " + process.env.OPENOCTOPUS_API_KEY },
});
const task = await r.json();
if (!r.ok) throw new Error(task?.error?.message ?? "task query failed");
if (task.status === "queued" || task.status === "processing") {
await new Promise((resolve) => setTimeout(resolve, intervalMs));
continue;
}
if (task.status === "succeeded") {
console.log(task.output_payload);
break;
}
throw new Error(task?.error_message ?? "task failed");
}import time, requests
timeout_s = 180
interval_s = 1.8
started = time.time()
while time.time() - started < timeout_s:
resp = requests.get(
"https://api.openoctopus.com/v1/tasks/task_id_from_previous_response",
headers={"Authorization": f"Bearer {OPENOCTOPUS_API_KEY}"},
timeout=30,
)
task = resp.json()
if resp.status_code >= 400:
raise RuntimeError(task.get("error", {}).get("message", "task query failed"))
status = task.get("status")
if status in ("queued", "processing"):
time.sleep(interval_s)
continue
if status == "succeeded":
print(task.get("output_payload"))
break
raise RuntimeError(task.get("error_message", "task failed"))Request Example (From Internal)
{
"model": "openoctopus/video-face-swap",
"input": {
"video": "https://example.com/source.mp4",
"face_image": "https://example.com/face.jpg",
"target_index": 0,
"enable_sync_mode": false,
"enable_base64_output": false
}
}Submit Response Example
{
"id": "00000000-0000-0000-0000-000000000000",
"status": "queued",
"model": "openoctopus/video-face-swap"
}Input Schema (Standard + Provider Extension)
Standard request contract merged with this model's upstream input parameters.
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
| video | string | Yes | Source video used for face replacement. | - |
| face_image | string | Yes | Reference face image to swap into the source video. | - |
| target_index | integer | No | Target face index when multiple faces are present. 0 usually selects the largest or primary face. | 0 |
| enable_base64_output | boolean | No | If enabled, the output will be encoded into a BASE64 string instead of a URL. This property is only available through the API. | false |
| enable_sync_mode | boolean | No | If set to true, the function will wait for the result before returning the response. This property is only available through the API. | false |
{
"standard": {
"model": "public model slug (required)",
"prompt": "user prompt (required)",
"input": "provider-specific options (optional object)"
},
"providerExtension": {
"params": [
{
"name": "video",
"type": "string",
"required": true,
"description": "Source video used for face replacement.",
"exposedToCustomer": true
},
{
"name": "face_image",
"type": "string",
"required": true,
"description": "Reference face image to swap into the source video.",
"exposedToCustomer": true
},
{
"name": "target_index",
"step": 1,
"type": "integer",
"example": "0",
"maximum": 10,
"minimum": 0,
"required": false,
"description": "Target face index when multiple faces are present. 0 usually selects the largest or primary face.",
"exposedToCustomer": true
},
{
"name": "enable_base64_output",
"type": "boolean",
"example": "false",
"required": false,
"description": "If enabled, the output will be encoded into a BASE64 string instead of a URL. This property is only available through the API.",
"exposedToCustomer": false
},
{
"name": "enable_sync_mode",
"type": "boolean",
"example": "false",
"required": false,
"description": "If set to true, the function will wait for the result before returning the response. This property is only available through the API.",
"exposedToCustomer": false
}
]
}
}Output Schema (Standard + Provider Extension)
Standard output contract merged with provider extension fields.
Note: integrate against output_payload.assets[]. raw is optional and may be omitted by endpoint policy.
| Field | Type | Exposed | Description | Example |
|---|---|---|---|---|
| id | string | Yes | Unique provider prediction identifier. | - |
| model | string | Yes | Provider model ID used for the prediction. | - |
| status | string | Yes | Provider task status. | - |
| outputs | array of string | Yes | Generated video output URLs. | - |
| created_at | string | Yes | Provider creation timestamp when available. | - |
{
"standard": {
"id": "task id",
"status": "queued | processing | succeeded | failed | cancelled",
"queue": {
"enabled": "whether OpenOctopus local queue is enabled for this model",
"position": "1-based queue position while queued, 0 while processing, null when unavailable",
"size": "current queued request count for this model",
"concurrency": "local concurrent upstream submissions allowed for this model",
"upstreamQueueSupported": "whether the upstream provider has a reliable queue",
"upstreamCancelSupported": "whether the upstream provider supports cancellation"
},
"asset_storage": {
"provider": "supabase | aliyun-oss | tencent-cos",
"custom": "true when this model overrides default system asset storage"
},
"capability": "image_generation | video_generation",
"output_payload": {
"format": "openoctopus.image.output.v1 | openoctopus.video.output.v1",
"assets": "normalized output assets",
"raw": "optional sanitized debug payload (may be omitted depending on endpoint policy)"
}
},
"providerExtension": {
"fields": [
{
"name": "id",
"type": "string",
"description": "Unique provider prediction identifier.",
"exposedToCustomer": true
},
{
"name": "model",
"type": "string",
"description": "Provider model ID used for the prediction.",
"exposedToCustomer": true
},
{
"name": "status",
"type": "string",
"description": "Provider task status.",
"exposedToCustomer": true
},
{
"name": "outputs",
"type": "array of string",
"description": "Generated video output URLs.",
"exposedToCustomer": true
},
{
"name": "created_at",
"type": "string",
"description": "Provider creation timestamp when available.",
"exposedToCustomer": true
}
]
}
}Final Output Example (Normalized)
{
"capability": "video_generation",
"status": "succeeded",
"output_payload": {
"format": "openoctopus.video.output.v1",
"assets": [
{
"type": "video",
"url": "https://example.com/result.mp4",
"mimeType": "video/mp4"
}
],
"raw": {
"id": "example",
"model": "wavespeed-ai/video-face-swap",
"status": "completed",
"outputs": [
"https://example.com/result.mp4"
]
}
}
}Step 4 · Error Handling Guide
Error codes below are loaded from internal gateway error definitions and should be treated as source of truth.
| Code | HTTP | Retryable | Category | Message |
|---|---|---|---|---|
| invalid_request | 400 | No | validation | The request payload is invalid. Check the required fields and try again. |
| unauthorized | 401 | No | auth | Authentication is required for this request. |
| invalid_api_key | 401 | No | auth | The API key is invalid or inactive. |
| insufficient_balance | 402 | No | billing | Your wallet balance is insufficient. Please top up and try again. |
| model_not_available | 404 | No | routing | The requested model is currently unavailable. |
| task_not_found | 404 | No | task | The requested task could not be found. |
| file_not_found | 404 | No | asset | The requested generated file is not available. |
| provider_offline | 503 | Yes | upstream | The selected model is temporarily unavailable. Please retry later. |
| provider_model_inactive | 503 | Yes | routing | The selected model is temporarily unavailable. Please retry later. |
| provider_credential_missing | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_incomplete | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_unusable | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_legacy | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_unavailable | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_decrypt_failed | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| model_billing_not_configured | 503 | No | system | The selected model is temporarily unavailable. Please retry later. |
| provider_pricing_not_configured | 503 | No | system | The selected model is temporarily unavailable. Please retry later. |
| database_operation_failed | 503 | Yes | system | The service could not access required internal records. Please retry later. |
| billing_resolution_failed | 503 | Yes | system | The selected model pricing could not be evaluated. Please retry later. |
| request_record_write_failed | 503 | Yes | system | The request could not be recorded internally. Please retry later. |
| api_key_touch_failed | 503 | Yes | system | The request was accepted but internal key tracking failed. Please retry later. |
| queue_unavailable | 503 | Yes | system | The internal job queue is temporarily unavailable. Please retry later. |
| provider_submit_failed | 502 | Yes | upstream | The generation provider could not accept the request. Please retry shortly. |
| provider_poll_failed | 502 | Yes | upstream | The generation provider could not complete the request. Please retry shortly. |
| upstream_failed | 502 | Yes | upstream | The generation provider failed to complete the request. Please retry shortly. |
| content_policy_violation | 400 | No | safety | The prompt or image was rejected by the provider safety policy. Please adjust the content and try again. |
| upstream_timeout | 504 | Yes | upstream | The generation request timed out. Please retry shortly. |
| upstream_result_missing | 502 | Yes | upstream | The generation provider returned an incomplete result. Please retry shortly. |
| video_output_missing | 502 | Yes | upstream | The generation provider returned an incomplete result. Please retry shortly. |
| service_unavailable | 503 | Yes | system | The service is temporarily unavailable. Please retry later. |
| internal_error | 500 | Yes | system | The service encountered an unexpected error. Please retry later. |
task.status=failed, always read error.code and match the table above; retry only when retryable=true.