popnow/face-swap-v1-plus/edit24 點數起 /張換臉時自動配合膚色。
在 POST /jobs/createTask 把模型名稱填成 popnow/face-swap-v1-plus/edit 就能呼叫,24 點數/張起(US$0.24)。新帳戶送 30 點數,可以先在 Playground 免費試用。
- 換臉
popnow/face-swap-v1-plus/editPOST https://api.popnow.ai/api/v1/jobs/createTask# 1. Create the task. Keep the Idempotency-Key if you retry after a network error.
export IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST https://api.popnow.ai/api/v1/jobs/createTask \
-H "Authorization: Bearer $POPNOW_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"model": "popnow/face-swap-v1-plus/edit",
"input": {
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"reference_image_url": "https://media.popnow.ai/influencer/examples/ava.webp",
"output_format": "jpeg"
}
}'
# 2. Copy data.taskId from the response, then poll until state is succeeded, failed, canceled or expired.
TASK_ID="TASK_ID_FROM_RESPONSE"
curl "https://api.popnow.ai/api/v1/jobs/recordInfo?taskId=$TASK_ID" \
-H "Authorization: Bearer $POPNOW_API_KEY"// Node 18+ (or any runtime with fetch). Keep the API key on your server.
const BASE = "https://api.popnow.ai/api/v1";
const headers = { Authorization: `Bearer ${process.env.POPNOW_API_KEY}` };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function call(path, init = {}) {
const res = await fetch(BASE + path, { ...init, headers: { ...headers, ...init.headers } });
const body = await res.json();
if (body.code !== 200) throw new Error(`${body.code} ${body.msg}`);
return body.data;
}
const { taskId } = await call("/jobs/createTask", {
method: "POST",
headers: { "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({
"model": "popnow/face-swap-v1-plus/edit",
"input": {
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"reference_image_url": "https://media.popnow.ai/influencer/examples/ava.webp",
"output_format": "jpeg"
}
}),
});
// Poll with backoff. A finished task may still be copying files: wait while an asset is pending.
let task;
for (let delay = 2000; ; delay = Math.min(delay * 1.5, 15000)) {
await sleep(delay);
task = await call(`/jobs/recordInfo?taskId=${taskId}`);
const copying = (task.output?.assets ?? []).some((a) => a.pending && !a.unavailable);
if (["succeeded", "failed", "canceled", "expired"].includes(task.state) && !copying) break;
}
if (task.state !== "succeeded") throw new Error(`${task.state}: ${task.errorMessage ?? "no detail"}`);
// Each asset has a signed url (no API key needed) that expires at expiresAt: save the file.
for (const asset of task.output.assets) console.log(asset.url);import os, time, uuid, requests
BASE = "https://api.popnow.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['POPNOW_API_KEY']}"}
def call(method, path, **kwargs):
body = requests.request(method, BASE + path, headers={**HEADERS, **kwargs.pop("headers", {})}, timeout=30, **kwargs).json()
if body["code"] != 200:
raise RuntimeError(f'{body["code"]} {body["msg"]}')
return body["data"]
task_id = call(
"POST",
"/jobs/createTask",
headers={"Idempotency-Key": str(uuid.uuid4())}, # reuse it if you retry
json={
"model": "popnow/face-swap-v1-plus/edit",
"input": {
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"reference_image_url": "https://media.popnow.ai/influencer/examples/ava.webp",
"output_format": "jpeg"
}
},
)["taskId"]
# Poll with backoff. A finished task may still be copying files: wait while an asset is pending.
delay = 2.0
while True:
time.sleep(delay)
task = call("GET", "/jobs/recordInfo", params={"taskId": task_id})
assets = (task.get("output") or {}).get("assets") or []
copying = any(a.get("pending") and not a.get("unavailable") for a in assets)
if task["state"] in ("succeeded", "failed", "canceled", "expired") and not copying:
break
delay = min(delay * 1.5, 15.0)
if task["state"] != "succeeded":
raise RuntimeError(f'{task["state"]}: {task.get("errorMessage", "no detail")}')
# Each asset has a signed url (no API key needed) that expires at expiresAt: save the file.
for asset in assets:
print(asset["url"])input 依下方的 Schema 產生,原樣就能通過驗證。把提示詞和連結換成你自己的即可。
價格
| 選項 | 點數 /張 | ≈ 美元 |
|---|---|---|
| 標準 | 24 | US$0.24 |
1 點數 = $0.01 · 全部選項及價格:GET /models/popnow/face-swap-v1-plus/edit
限制
- 提示詞長度(字元)
- 200
參數
這些欄位來自我們驗證每個請求所用的同一份 Schema。沒列出的欄位會被拒絕。
image_urlstring必填作為起點的那張圖。
reference_image_urlstring必填Reference face image. The facial identity from this image is transferred onto the target.
promptstring選填Optional short note guiding one subtle attribute of the result, such as an accessory, an expression or a different hairstyle.
output_formatstring選填結果的檔案格式。
output_qualityinteger選填Requested image quality for the result, from 10 to 100.
seedinteger選填用同一個數字,結果就能重現。
原始 JSON Schema
{
"additionalProperties": false,
"properties": {
"image_url": {
"contentMediaType": [
"image/jpeg",
"image/png",
"image/webp",
"image/gif"
],
"description": "Target image. The face of the person in this image is the one that gets replaced.",
"minLength": 1,
"type": "string"
},
"output_format": {
"default": "jpeg",
"description": "File format of the returned image.",
"enum": [
"png",
"jpeg",
"webp"
],
"type": "string"
},
"output_quality": {
"default": 95,
"description": "Requested image quality for the result, from 10 to 100.",
"maximum": 100,
"minimum": 10,
"type": "integer"
},
"prompt": {
"description": "Optional short note guiding one subtle attribute of the result, such as an accessory, an expression or a different hairstyle.",
"maxLength": 200,
"type": "string"
},
"reference_image_url": {
"contentMediaType": [
"image/jpeg",
"image/png",
"image/webp",
"image/gif"
],
"description": "Reference face image. The facial identity from this image is transferred onto the target.",
"minLength": 1,
"type": "string"
},
"seed": {
"description": "Random seed; reuse it to keep two runs over the same image pair comparable.",
"maximum": 2147483647,
"minimum": 0,
"type": "integer"
}
},
"required": [
"image_url",
"reference_image_url"
],
"type": "object"
}回應、輪詢與回呼
所有模型的用法都一樣:先建立任務,再輪詢,或等待回呼。
先報價
curl -X POST https://api.popnow.ai/api/v1/jobs/quote \
-H "Authorization: Bearer $POPNOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "popnow/face-swap-v1-plus/edit",
"input": {
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"reference_image_url": "https://media.popnow.ai/influencer/examples/ava.webp",
"output_format": "jpeg"
}
}'請求內容和 createTask 相同。它回傳預估扣款和最高可能扣款,不會真的執行。
已受理
HTTP/1.1 202 Accepted
{
"code": 200,
"msg": "success",
"data": {
"taskId": "task_01k3m8x9q2z4v7n5p6r8s0t1w3",
"state": "queued"
},
"request_id": "req_example_create"
}HTTP 202 代表任務已受理,點數先凍結、尚未扣除。最終扣款不會超過凍結額。
查詢狀態
curl "https://api.popnow.ai/api/v1/jobs/recordInfo?taskId=$TASK_ID" \
-H "Authorization: Bearer $POPNOW_API_KEY"每隔幾秒查詢一次並逐步拉長間隔,state 變成 succeeded、failed、canceled 或 expired 時停止。
完成的任務
{
"code": 200,
"msg": "success",
"data": {
"taskId": "task_01k3m8x9q2z4v7n5p6r8s0t1w3",
"model": "popnow/face-swap-v1-plus/edit",
"state": "succeeded",
"output": {
"assets": [
{
"url": "https://cdn.popnow.ai/tasks/task_01k3m8x9q2z4v7n5p6r8s0t1w3/output.png?signature=FROM_RESPONSE",
"expiresAt": "2026-10-17T12:20:00Z",
"mime": "image/png",
"width": 1024,
"height": 1024
}
]
},
"settled": true
},
"request_id": "req_example_record"
}請在 expiresAt 之前從 url 下載。之後需要新連結,請呼叫 POST /common/download-url。
驗證回呼
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const app = express();
// express.raw keeps the exact bytes that were signed; express.json would not match.
app.post("/hooks/popnow", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.header("X-Webhook-Timestamp");
const signature = req.header("X-Webhook-Signature") ?? "";
const body = JSON.parse(req.body.toString("utf8"));
const taskId = body.data?.taskId ?? body.task_id;
const digest = createHash("sha256").update(req.body).digest("hex");
const expected = createHmac("sha256", process.env.POPNOW_WEBHOOK_SECRET)
.update(`${taskId}.${timestamp}.${digest}`)
.digest("base64");
const ok = expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
// Check the signature first, then reject anything older than five minutes.
if (!ok || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.status(401).end();
res.status(200).end(); // answer fast, then fetch the task and store its files
});設定 callBackUrl 後,我們會把完成的任務送給你。先用原始請求內容驗證簽名,再信任它。
上傳本機檔案
curl -X POST https://api.popnow.ai/api/v1/common/upload-url \
-H "Authorization: Bearer $POPNOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contentType": "image/png", "bytes": 482113}'
# PUT the file bytes to data.uploadUrl with every header in data.headers, then commit it:
curl -X POST https://api.popnow.ai/api/v1/files/$FILE_ID/commit \
-H "Authorization: Bearer $POPNOW_API_KEY"
# The commit returns a popnow://f/... reference. Put it in any image, video or
# audio field of the task input instead of a public https URL.公開的 https 連結可以直接用。本機檔案要先經過 upload-url 和 commit。

