MixMedia API
RUEN
API keys Sign inTry it
Open as Markdown
← Try it on the website

Pruna P-Video-Replace

Pruna AI · ~2 min
from $0.039per second

Fast person swap in your video from 1–3 photos. Motion, camera and sound stay as filmed. Up to 30 seconds, 720p or 1080p.

Pruna P-Video-Replace takes your video and 1–3 photos and puts the character from the photos in place of the person in the clip.

Motion, timing, camera and scene stay as in the source; the clip audio is kept. It is fast: about 3–4 seconds of generation per second of video.

The video is up to 30 seconds (longer is trimmed to 30); the result has the same length. Resolution 720p or 1080p, frame rate original, 24 or 48. The prompt is optional. You pay per second of your video.

One model, pruna-p-video-replace · ~2 min. The mode is the input.mode field.

Modes

One ID, pruna-p-video-replace. input.mode chooses what happens to the clip. Omitted, it is motion transfer. The official ids work too: higgsfield/genjutsu/object-swap/v1.0 and higgsfield/genjutsu/restyle/v1.0 select the mode, so mode can be left out.

modeWhat it doesWhat to send
motion-transferMotion, camera and timing come from the video; the look comes from the photosvideo_url + image_urls (1–3)
object-swapReplaces a person, outfit or object in the clip. The rest of the shot staysvideo_url + image_urls (1–3)
restyleThe same clip in a chosen style. The source motion and audio stayvideo_url + preset_id. image_urls — optional, up to 5

Shared by all three: the video is at least 4 seconds, longer than 30 seconds is trimmed to 30, and the result has the same length. prompt may be omitted. resolution: 480p, 720p (default) or 1080p. The mode does not change the price.

Object swap needs a source frame of at least 409,600 pixels (width × height). Ordinary HD is fine; a tiny preview is not.

Styles for restyle

Style list: GET https://mixmedia.tech/api/v1/genjutsu/presets. No key. preset_id is the item's id, not its name and not the preview image.

{
  "code": 200,
  "msg": "success",
  "data": {
    "items": [
      {
        "id": "c2143317-f28d-4c3c-a0b8-39bd547e08a7",
        "name": "Cel-Shaded CG Anime",
        "preview_url": "https://…/style.webp"
      }
    ]
  }
}

The list changes. If a saved preset_id is no longer accepted, fetch the list again and use a current id.

Price

ResolutionPrice per second of your video
720p$0.039
1080p$0.078
  • Price = per-second price × clip length, rounded up to $0.001. Charged when the task is created and fully refunded if it fails.
  • You pay per second of your video (2 to 30 s). A clip longer than 30 s is trimmed to 30; the result has the same length. prompt may be omitted.

Media limits

TypeLimits
Images1–3 for motion transfer and object swap; 0–5 for restyle. JPG / PNG / WebP; public https or files/upload — up to 30 MB per file
Videoone clip, video_url; 2 s to 30 s (longer is trimmed to 30); MP4 / MOV → MP4 on the server; upload ≤ 200 MB

Photos — public https or upload; video and audio — files/upload only.

Parameters (field input)

ParameterDescriptionValues (default in bold)
promptWhat happens in the clip: scene, motion, camera, light, styletext, up to 30000 chars (optional, default empty)
video_urlMotion video (MP4): one clip, at least 4 s. Longer than 30 s is trimmed to 30. Upload it via files/upload first"https://…"
image_urlsCharacter photos (https): look, face, clothes["https://…"], 1–3
resolutionVideo resolution720p · 1080p
target_fpsЧастота кадровoriginal 24 48
save_audioСохранить звук роликаtrue false (default true)
ignore_audioНе учитывать звук при генерацииtrue false (default false)
turboТурбо (быстрее, чуть ниже качество)true false (default false)
seedVariant number: the same number with the same prompt gives a similar result; empty or -1 = random-1…4294967295

Examples

Motion transfer

curl -X POST https://mixmedia.tech/api/v1/jobs/createTask \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"pruna-p-video-replace","input":{"mode":"motion-transfer","prompt":"Keep the face from the photo, take the motion from the video","video_url":"https://mixmedia.tech/files/<id>.mp4","image_urls":["https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"],"resolution":"720p"}}'
import os, requests

r = requests.post(
    "https://mixmedia.tech/api/v1/jobs/createTask",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    json={
      "model": "pruna-p-video-replace",
      "input": {
        "mode": "motion-transfer",
        "prompt": "Keep the face from the photo, take the motion from the video",
        "video_url": "https://mixmedia.tech/files/<id>.mp4",
        "image_urls": [
          "https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"
        ],
        "resolution": "720p"
      }
    },
)
print(r.json())  # {"code": 200, "data": {"taskId": "..."}}
const r = await fetch("https://mixmedia.tech/api/v1/jobs/createTask", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "model": "pruna-p-video-replace",
    "input": {
      "mode": "motion-transfer",
      "prompt": "Keep the face from the photo, take the motion from the video",
      "video_url": "https://mixmedia.tech/files/<id>.mp4",
      "image_urls": [
        "https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"
      ],
      "resolution": "720p"
    }
  }),
});
console.log(await r.json()); // { code: 200, data: { taskId: "..." } }

Object swap

curl -X POST https://mixmedia.tech/api/v1/jobs/createTask \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"pruna-p-video-replace","input":{"mode":"object-swap","prompt":"Replace the person with the character in the photo","video_url":"https://mixmedia.tech/files/<id>.mp4","image_urls":["https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"],"resolution":"720p"}}'
import os, requests

r = requests.post(
    "https://mixmedia.tech/api/v1/jobs/createTask",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    json={
      "model": "pruna-p-video-replace",
      "input": {
        "mode": "object-swap",
        "prompt": "Replace the person with the character in the photo",
        "video_url": "https://mixmedia.tech/files/<id>.mp4",
        "image_urls": [
          "https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"
        ],
        "resolution": "720p"
      }
    },
)
print(r.json())  # {"code": 200, "data": {"taskId": "..."}}
const r = await fetch("https://mixmedia.tech/api/v1/jobs/createTask", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "model": "pruna-p-video-replace",
    "input": {
      "mode": "object-swap",
      "prompt": "Replace the person with the character in the photo",
      "video_url": "https://mixmedia.tech/files/<id>.mp4",
      "image_urls": [
        "https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"
      ],
      "resolution": "720p"
    }
  }),
});
console.log(await r.json()); // { code: 200, data: { taskId: "..." } }

Restyle

curl -X POST https://mixmedia.tech/api/v1/jobs/createTask \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"pruna-p-video-replace","input":{"mode":"restyle","video_url":"https://mixmedia.tech/files/<id>.mp4","preset_id":"<preset_id>","resolution":"720p"}}'
import os, requests

r = requests.post(
    "https://mixmedia.tech/api/v1/jobs/createTask",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    json={
      "model": "pruna-p-video-replace",
      "input": {
        "mode": "restyle",
        "video_url": "https://mixmedia.tech/files/<id>.mp4",
        "preset_id": "<preset_id>",
        "resolution": "720p"
      }
    },
)
print(r.json())  # {"code": 200, "data": {"taskId": "..."}}
const r = await fetch("https://mixmedia.tech/api/v1/jobs/createTask", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "model": "pruna-p-video-replace",
    "input": {
      "mode": "restyle",
      "video_url": "https://mixmedia.tech/files/<id>.mp4",
      "preset_id": "<preset_id>",
      "resolution": "720p"
    }
  }),
});
console.log(await r.json()); // { code: 200, data: { taskId: "..." } }

Create and result

  1. POST /api/v1/jobs/createTask with model and input.
  2. Poll GET /api/v1/jobs/recordInfo?taskId=… every 3–5 s (or a callback).

Successful createTask (HTTP 200)

{
  "code": 200,
  "msg": "success",
  "data": {
    "taskId": "task_...",
    "task_id": "task_...",
    "status": "submitted"
  }
}

Completed task (recordInfo)

{
  "code": 200,
  "msg": "success",
  "data": {
    "taskId": "task_...",
    "task_id": "task_...",
    "state": "success",
    "status": "completed",
    "costUsd": "0.384",
    "refunded": false,
    "resultJson": {
      "videos": [
        {
          "url": "https://…/out.mp4"
        }
      ],
      "duration": 8
    }
  }
}

Aliases: task_id = taskId; status: pending / processing / completed / failed alongside state. Clip URL is in resultJson. size is an alias of aspect_ratio in input.

Photos can be any public https links or uploads via files/upload. Videos and audio — via files/upload only.

Error handling

API responses are JSON {"code", "msg", "data"}; errors also have a string error field (type). On /api/v1/* the HTTP status matches body code.

Example response bodies

400

{
  "code": 400,
  "msg": "input.duration: must be between 4 and 30, or \"auto\" / -1",
  "data": null,
  "error": "invalid_request_error"
}

401

{
  "code": 401,
  "msg": "Authentication failed. Please check your API key",
  "data": null,
  "error": "authentication_error"
}

402

{
  "code": 402,
  "msg": "Insufficient balance. Please top up and try again. Available: $0.01, required: $0.015. Top up at https://mixmedia.tech/cabinet/billing",
  "data": {
    "balance": "0.0100",
    "required": "0.0150",
    "currency": "USD",
    "topUpUrl": "https://mixmedia.tech/cabinet/billing"
  },
  "error": "payment_required"
}

429

{
  "code": 429,
  "msg": "Rate limit exceeded: 10 createTask/upload requests per 10 s per key. Retry after 8s",
  "data": null,
  "error": "rate_limit_error"
}

500

{
  "code": 500,
  "msg": "Internal server error",
  "data": null,
  "error": "server_error"
}

Codes on createTask / upload

codeerrorMeaningWhat to do
400invalid_request_errorInvalid parameter or bodySee msg
400unsupported_modelUnknown modelModel list
400nsfw_content_detectedModeration / content policy — msg / failMsg is the upstream text verbatimRead the refusal text, change the prompt or files
401authentication_errorMissing or invalid keyCheck the key
402payment_requiredNot enough balanceTop up
403permission_errorNo accessContact support
429rate_limit_errorToo many requestsWait for Retry-After
505model_disabledModel temporarily disabledPick another
500server_errorOur failureRetry later

Example msg values: Authentication failed. Please check your API key (401); Insufficient balance. Please top up and try again… (402).

Task errors (recordInfo)

If the task was created and then failed: state: "fail" (alias status: "failed"), failCode / failMsg, and error: { code, message, type }. Money is refunded (refunded: true).

failCodeerror.typeMeaning
400nsfw_content_detected / invalid_request_errorModeration — failMsg is upstream text verbatim
422invalid_request_errorThe model rejected the parameters (length, mode, resolution, file)
501server_errorThe model could not generate (failMsg)
504timeout_errorTimed out — retry
500 / 503server_errorService temporarily unavailable

Rate limits

WhatLimit
createTask and upload20 per 10 s per key
recordInfo / list / balance100 per 10 s per key

Over the limit — HTTP 429 and Retry-After.

© 2026 MixMedia