Sociaro

pruna/p-video-2-pro-replace

Pruna's P-Video-2-Pro Replace, served direct from Pruna.

A source video of 1 to 120 seconds and 1 or 2 pictures go in; the same video comes back with the pictured person, or two people, in place, keeping the camera, the motion, the background and the soundtrack, at 24 fps.

Billed per second of the finished video, rounded up: 480p $0.05, 768p $0.10, 1080p $0.12, 2k $0.14, 4k $0.18.

Launch price until 17:00 UTC on 17 November 2026: 50% off, for jobs that complete by then.

The generator's safety filter is always on.

You must hold the rights to, or the consent of, every person whose likeness you send or replace.

Your use of the Pruna models and of their outputs is subject to Pruna's Terms of Service, including its Acceptable Use Policy (section 10), and to the licence of the model each one is built on.

You must follow them, and you may not use the outputs to train or improve a competing AI model.

If you make these models or their outputs available to your own users, you must bind them to restrictions at least as protective and tell them these restrictions apply.

Slugpruna/p-video-2-pro-replace
Kindvideo
Vendorpruna
EndpointPOST /v1/pruna/generations

Charged per job from what the generator reports it rendered — your Logs show the exact figure for each one; see balance and billing.

Calling it

curl https://api.sociaro.com/v1/pruna/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "pruna/p-video-2-pro-replace",
        "input": {"video": "https://your-cdn.example.com/clip.mp4", "images": ["https://your-cdn.example.com/person.png"]}
      }'

# then poll the job id it returns:
curl https://api.sociaro.com/v1/pruna/generations/JOB_ID \
  -H "Authorization: Bearer $SOCIARO_API_KEY"

Parameters

P-Video-2-Pro Replace — put the person from a picture into a video

FieldTypeRequiredDefaultValuesWhat it does
input.videostringyes—a public https urlThe source video, 1 to 120 seconds; a longer one is cut to 120 seconds. Its motion, scene and audio are kept.
input.imagesstring[]yes—1 or 2 public https urlsOne person, or two people with one picture each. A third picture is a `400`.
input.instruction_promptstringno""textWho replaces whom. Without it, two pictures are matched to the people from left to right.
input.resolutionstringno768p480p · 768p · 1080p · 2k · 4kThe aspect ratio is kept. Sets the per-second rate.
input.save_audiobooleannotruetrue · falseKeeps the source's soundtrack in the result.
input.ignore_audiobooleannofalsetrue · falseGenerates without listening to the source's audio, so the lips do not follow it.
input.seedintegernorandomintegerReproduces a video.

Anything not listed here is refused rather than ignored, so a typo fails loudly instead of quietly producing something else.

Worked example

curl https://api.sociaro.com/v1/pruna/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "pruna/p-video-2-pro-replace",
        "input": { "video": "https://example.com/clip.mp4", "images": ["https://example.com/person.png"] } }'

curl https://api.sociaro.com/v1/pruna/generations/{job_id} -H "Authorization: Bearer $SOCIARO_API_KEY"

Submit and poll. Send pruna/p-video-2-pro-replace to POST /v1/pruna/generations and poll GET /v1/pruna/generations/{job_id}. It is not on the Jobs API (/v2).

How it is billed. Per second of the video that comes back, read from the finished file when the job completes and billed in whole seconds, rounded up, at the rate of the resolution you chose. The result is as long as the source, so a 10 second clip at 768p is $1.00. The pictures, instruction_prompt and the audio settings do not change the price.

Resolution Per second
480p $0.05
768p $0.10
1080p $0.12
2k $0.14
4k $0.18

Launch price. Until 17:00 UTC on 17 November 2026 every rate above is 50% off: 480p $0.025, 768p $0.05, 1080p $0.06, 2k $0.07, 4k $0.09 per second, so a 10 second clip at 768p is $0.50. The price is set when the job completes, so a job that completes after the end is charged the full price.

Limits. The source video runs 1 to 120 seconds. A longer one is not refused: the generator cuts it to 120 seconds, and the job is billed on the 120 seconds that come back. Send 1 or 2 pictures; a third is a 400. A long video at 2k or 4k may be refused by the generator, in its own words; such a job is never billed, so choose a lower resolution for it. A job whose finished video the gateway cannot read stays processing and is never billed.

Timing. A job can be polled for 3 hours from submission. The result link works for 1 hour from the poll that first returns it.

The generator's content check stays on: enable_safety_filter is set by the gateway, and a request that sends it with another value is a 400.

How a job runs

Generation takes longer than a request should wait, so it goes in three moves: submit, then poll, then collect.

# 1. submit — the model id plus the fields above
curl -sS https://api.sociaro.com/v1/alibaba/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "…", "prompt": "…" }'
# -> { "job_id": "<id>", "status": "submitted" }

# 2. poll every 3-5 s for images, 5-10 s for video
curl -sS https://api.sociaro.com/v1/alibaba/generations/<id> \
  -H "Authorization: Bearer $SOCIARO_API_KEY"
# -> { "job_id": "…", "status": "processing" }
# -> { "job_id": "…", "status": "completed", "model": "…",
#      "media_url": "https://api.sociaro.com/v1/media/<media_id>" }
# -> { "job_id": "…", "status": "failed", "model": "…", "error": { "code": …, "message": … } }

# 3. collect within the hour — the opaque id IS the credential, no header needed
curl -sSL "https://api.sociaro.com/v1/media/<media_id>" -o out.mp4

Use the job_id verbatim as the path segment when polling: the two names are the same value. Which submit and poll URL this model uses is at the top of this page, under Calling it.

Reading the poll

status is one of processing · completed · failed. Only completed carries media_url, only failed carries error.

Gate on status, never on the poll's HTTP code. A poll-time failure is HTTP 200 with status: "failed" and an error object of { "code", "message" }, plus provider_code when the generator supplied one — on every failure branch, including one where the result could not be delivered. A submit-time rejection is different: it comes back as its own HTTP status with the reason in the body.

code is ours and stable; message is the generator's. Match on code: it comes from a small fixed set, and provider_failed still means what it always meant. A rejection of what you sent — a size out of range, an unusable input — additionally sets code: "invalid_request", because that is a different thing for your code to do. message now carries the generator's own sentence, and provider_code its own code (OutputVideoSensitiveContentDetected.PolicyViolation, IPInfringementSuspect), so a moderated generation, a copyright refusal and a broken renderer are finally distinguishable. Treat provider_code as informational: the vendors change these strings without telling us, so branch on ours.

message is variable text, capped at 400 characters. It used to be a single fixed 43-character sentence, so if you compare it by equality or keep it in a narrower column, that needs changing — match on code, and on provider_code when you need the finer distinction.

Two things that text is not. It does not name the generator behind the model: the names of the services and hosts that actually run your job are substituted out before you see it, so do not parse it for one. What it does not hide is a name you already have — the halves of the model id you called (alibaba, bytedance, wan, qwen, seedance, seedream, happyhorse) stay as written, because blanking those would garble the message exactly where it is useful: Model alibaba/wan-2-7-image not found has to survive intact. And the text can quote your own request back: a moderation message often contains the fragment it objected to. That is why we do not write it into our own logs, and why forwarding or storing a failed poll's message is your decision to make rather than something to do by default.

Collecting the result

media_url points at our host, not the generator's. It is a capability: the opaque id is the credential, so no Authorization header is needed and anyone holding the link can fetch it. It expires within the hour — download rather than store the link.

Several images are not optional extras. An Alibaba image model asked for more than one picture (n above 1) is charged for the number of pictures the generator reports making, and every link it returns is delivered, never fewer than that number: media_urls lists all of them in the generator's order (present on every image result, even a single one) and media_url is the first. Each has its own link and its own expiry. If any one of them cannot be delivered the job is reported failed and nothing is charged — unlike a still or a layer, an image you paid for is never left out.

Rules the gateway adds

Two fields are ours and never reach the generator:

Field Notes
model which model to run
user your own end-user id. Recorded against this job's spend so you can attribute cost per end user, and stripped before the request leaves us, so the generator never sees it. Validated BEFORE the job is created, so a rejection is free: a string of at most 128 characters, no control characters, valid UTF-8. Omitting it, or sending null, is fine and simply records no end user

An unknown field is always a 400. Most generators reject one themselves; some accept it, ignore it, render something other than what you asked for and bill you — so we refuse it for you. Either way you get 400 unknown field(s): [...] naming the offender, never a surprise render.

Ranges and enums are the generator's own and we do not re-validate most of them, so an out-of-range value comes back as its rejection, in its wording and with its numbers. The one thing we DO check ourselves is resolution, because an unsupported tier is silently ignored rather than refused: you would ask for the higher tier, be handed a lower one, and be charged. The tiers on this page are read from what the vendor prices, so they change when the vendor publishes one.

Authentication

Authorization: Bearer <your-api-key> on submit and on poll. The key must be valid AND granted this model — 403 otherwise.

One deliberate exception on poll: a key that is over budget may still poll and collect a job it already submitted, so work you have already paid for is never stranded by a budget that ran out mid-generation. While the key is over budget the gateway cannot read its model list at all, so the per-model grant is not re-checked on those polls — a grant revoked after submit still collects that job. Ownership is always enforced: a job can only be polled by the key that created it, and submit is always refused in that state.