The current fal endpoint for MiniMax H3 Recast is minimax/h3-max/recast. It takes a finished video plus 1–4 reference photos, one for each person you want to replace, then returns a recast video and the seed used for the job.
That sounds like a small request body. The production work is everything around it: validating the clip before it enters the queue, keeping the API key out of the browser, making input files reachable, and reconnecting an asynchronous result to the correct user and project.
This site does not host Recast and its generator does not accept source-video uploads. The MiniMax H3 Recast guide explains the creative workflow. This page is for developers integrating the separate fal endpoint into their own server application.
The request contract at a glance
The official fal Recast API schema currently defines two required inputs:
| Field | Required | What it means |
|---|---|---|
video_url | Yes | A 5–30 second source video. No individual shot inside it may run longer than 15 seconds. |
reference_image_urls | Yes | One to four photos, one for each replacement person. By default, references replace the main people from left to right. |
The useful optional controls are deliberately limited:
| Field | Options | When to set it |
|---|---|---|
prompt | Up to 2,000 chars | Clarify who becomes whom or name details that must remain unchanged. |
resolution | 768P or 1080P | Choose an output size; the current default is 1080P. |
seed | Integer ≥ 0 | Save and reuse it when you need a more traceable retry. Leave it empty for a random seed. |
enhance_realism | Boolean | Ask for more natural skin texture and blending. It is off unless enabled. |
enable_safety_checker | Boolean | Safety screening is enabled by default. Keep the default unless your authorized integration has a specific, reviewed reason to do otherwise. |
Recast is not a general text-to-video endpoint. The source supplies the performance, timing, camera movement, cuts, and soundtrack. The reference photos change who appears. A prompt can disambiguate the mapping, but it does not replace a poorly chosen reference or repair an invalid source clip.
Use a server route, never a browser key
fal reads FAL_KEY from the server runtime. Do not place that value in client-side JavaScript, a NEXT_PUBLIC_ variable, a mobile bundle, or a form submission. A determined visitor can inspect all of those.
The browser should call an authenticated route in your application. That route should check the user, validate the request, authorize the files, apply your rate or credit limits, and submit the job using the server-only key. Here is a deliberately small Next.js-style example:
import { fal } from '@fal-ai/client';
const endpoint = 'minimax/h3-max/recast';
export async function POST(request: Request) {
const body = await request.json();
if (typeof body.videoUrl !== 'string') {
return Response.json({ error: 'videoUrl is required' }, { status: 400 });
}
if (
!Array.isArray(body.referenceImageUrls) ||
body.referenceImageUrls.length < 1 ||
body.referenceImageUrls.length > 4 ||
body.referenceImageUrls.some(
(url: unknown) => typeof url !== 'string' || url.length === 0
)
) {
return Response.json(
{ error: 'Provide 1 to 4 reference image URLs' },
{ status: 400 }
);
}
if (
body.prompt != null &&
(typeof body.prompt !== 'string' || body.prompt.length > 2000)
) {
return Response.json(
{ error: 'prompt must be a string of at most 2,000 characters' },
{ status: 400 }
);
}
if (body.seed != null && (!Number.isInteger(body.seed) || body.seed < 0)) {
return Response.json(
{ error: 'seed must be a non-negative integer' },
{ status: 400 }
);
}
const resolution = body.resolution ?? '1080P';
if (!['768P', '1080P'].includes(resolution)) {
return Response.json({ error: 'Unsupported resolution' }, { status: 400 });
}
// Authenticate the caller and reserve credits before this point.
const { request_id } = await fal.queue.submit(endpoint, {
input: {
video_url: body.videoUrl,
reference_image_urls: body.referenceImageUrls,
resolution,
prompt: body.prompt || undefined,
seed: body.seed ?? undefined,
enhance_realism: body.enhanceRealism === true,
enable_safety_checker: true,
},
webhookUrl: `${process.env.APP_URL}/api/fal/recast-webhook`,
});
// Store request_id with the authenticated user and project before returning.
return Response.json({ requestId: request_id }, { status: 202 });
}This is a request-shape example, not a complete billing or authorization layer. In a real application, do not trust ownership, durations, credit cost, callback URLs, or storage paths supplied by the browser. Resolve those on the server.
Your URLs must be reachable from fal
video_url and every entry in reference_image_urls are fetched by fal's servers, not from the user's computer. If you send URLs, they must be publicly reachable when the job runs. A localhost path, an expired signed URL, a private dashboard link, or a URL that blocks automated requests will not work reliably.
A practical upload sequence is:
- Upload the source video and reference photos to storage from an authenticated application flow.
- Record the files against the current user or project.
- Produce URLs that fal can fetch for the full lifetime of the queued job.
- Submit those URLs from your server route.
- Remove temporary files later according to your retention policy, not immediately after submission.
fal also provides its own storage upload helper. The API documentation permits data URIs, but base64 is a poor default for a video: it increases payload size and can make request handling slower. A hosted file URL is easier to retry and inspect.
If you use signed URLs, give them enough lifetime for queueing and processing. “It opened in my browser” is not sufficient validation; test the exact URL without your logged-in cookies.
Treat Recast as an asynchronous job
Video work should not keep a browser request open until the render finishes. Submit to the queue, persist the returned request_id, and move the UI into a processing state.
A durable flow looks like this:
- The application creates its own job row with a stable internal ID.
- The server validates the inputs and reserves the user's entitlement or credits.
fal.queue.submit()returns a provider request ID.- The application stores that ID with the user, source files, settings, and job status.
- A webhook updates the job when a result arrives. A status poll can be a fallback, not the only source of truth.
- The result is attached to the original job only after the callback data and request ID have been checked.
Do not accept a callback URL from the client and pass it through to fal. Configure your own webhook destination on the server. Treat every webhook body as untrusted input, follow fal's current webhook-authentication guidance, and make the handler idempotent: the same completion message should not grant credits, duplicate a library item, or overwrite another user's job twice.
The queue client also supports fetching status and retrieving a completed result by request ID. That is useful for reconciliation when a callback is delayed. A background repair task can revisit jobs that have stayed in processing longer than expected, ask fal for their current state, and settle them exactly once.
The result contains a video and a seed
A successful response follows this shape:
type RecastResult = {
video: {
url: string;
content_type?: string | null;
file_name?: string | null;
file_size?: number | null;
};
seed: number;
};The schema guarantees the downloadable video.url and the integer seed; the other file metadata may be useful when present. Copy the result to storage you control if it needs to remain in a user's library. Provider output URLs should not silently become your long-term asset policy.
Save the seed beside the exact input settings and source-asset versions. A seed is useful provenance, but it is not a promise that two future requests will be visually identical after model or service changes.
The returned video keeps the source soundtrack. Recast changes appearance; it does not automatically give the replacement person a different speaking voice. If dialogue identity matters, read the MiniMax H3 Recast audio guide before designing the rest of the pipeline.
Validate before spending a queue request
A preflight screen prevents more failures than an elaborate retry loop. Check these points before submission:
- Source duration: 5–30 seconds total.
- Shot length: no continuous shot exceeds 15 seconds. Detect or ask the editor to confirm cuts rather than assuming total duration covers this rule.
- Reference count: one clear reference photo for every person being replaced, up to the current maximum of four.
- Reference order: confirm the intended left-to-right mapping, or state it plainly in
prompt; use the reference-photo mapping guide for crossings and cuts. - Prompt length: keep
promptat or below 2,000 characters. - Seed: send only a non-negative integer, or omit it for a random seed.
- Resolution: send only
768Por1080P. - File access: open every submitted URL without application cookies and confirm it will not expire during processing.
- Ownership and consent: confirm the user has the right to transform the video and use each person's likeness.
- Idempotency: prevent a double-click or network retry from creating two paid jobs.
- Result ownership: bind the provider request ID to the authenticated user before returning it to the client.
- Failure settlement: define when a failed or rejected job releases a reservation, and make that operation safe to repeat.
The most important product decision happens before the API call: whether Recast is actually the right operation. If you need the same performance with a different person, it fits. If you need a new performance, a new camera move, or a new scene, generate a new shot instead.
The next shot is yours
Take the idea into the studio.
Start with a prompt or an image. Choose the duration and resolution, then check the credit cost before you generate.
Create an H3 Max video