# Revid API — Character swap (`character-swap`)

> Replace every person in a video with the people of your photos, keeping the moves, lip sync, set and original audio.

Endpoint: `POST https://www.revid.ai/api/public/v3/render` — auth header `key: YOUR_API_KEY` (get a key at https://www.revid.ai/account).
Cost: 10 credits per call + generation costs. Estimate first with `POST https://www.revid.ai/api/public/v3/calculate-credits` (same body, no auth).

## Required input

- source.url
- one photo per person (media.provided images and/or characterIds), left to right, up to 4

## Defaults for this workflow

- media.type: `ai-video`
- AI voice-over: disabled by default
- Background music: disabled by default
- Captions: not used by this workflow
- Aspect ratio: kept from the source video

## Minimal request

```bash
curl -X POST 'https://www.revid.ai/api/public/v3/render' \
  -H 'Content-Type: application/json' \
  -H 'key: YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -d '{"workflow":"character-swap","source":{"url":"https://cdn.revid.ai/uploads/dance-two-people.mp4"},"media":{"provided":[{"url":"https://cdn.revid.ai/uploads/me.jpg","type":"image"},{"url":"https://cdn.revid.ai/uploads/friend.jpg","type":"image"}]}}'
```

## Full example

```json
{
  "webhookUrl": "https://your-server.com/revid/webhook",
  "workflow": "character-swap",
  "source": {
    "url": "https://www.youtube.com/watch?v=VIDEO_ID",
    "prompt": "The person on the right wears a red tuxedo"
  },
  "media": {
    "videoModel": "pro",
    "provided": [
      {
        "url": "https://cdn.revid.ai/uploads/me.jpg",
        "type": "image"
      }
    ]
  },
  "characterIds": [
    "insp_abc123"
  ],
  "options": {
    "clipRange": "12:42"
  },
  "metadata": {
    "duration": 95
  }
}
```

## Parameters for this workflow

Top-level fields:

| Field | Type | Required | Default | Allowed | Description |
|---|---|---|---|---|---|
| `workflow` | string | **Yes** |  | `script-to-video` / `prompt-to-video` / `audio-to-video` / `music-to-video` / `lyrics-to-video` / `song-ad` / `article-to-video` / `avatar-to-video` / `static-background-video` / `motion-transfer` / `character-swap` / `caption-video` / `video-to-clips` / `ad-generator` | The generation workflow. Decides which source input is required and every default below. |
| `webhookUrl` | string | No |  |  | Public URL called when the render finishes (success or failure). Strongly recommended over polling. Accepted aliases: webhook. |
| `projectId` | string | No |  |  | Reuse an existing project instead of creating a new one. The project must belong to the API key's workspace. Accepted aliases: pid. |
| `aspectRatio` | string | No | `"9:16"` | `9:16` / `16:9` / `1:1` / `4:5` / `auto` | Output aspect ratio. Accepted aliases: ratio. |
| `metadata` | object\|null | No |  |  | Free-form object stored with the project and echoed in webhooks. For URL sources, metadata.duration (seconds) speeds up processing. |
| `characterIds` | string[] | No |  |  | IDs of saved consistent characters to feature in the video. Get IDs from GET /api/public/v3/consistent-characters. Selecting characters can force media.imageModel to ultra when the preset requires it. Accepted aliases: selectedCharacters, options.characterIds, options.selectedCharacters. |

### `source` (required for this workflow)

| Field | Type | Required | Default | Allowed | Description |
|---|---|---|---|---|---|
| `source.prompt` | string | prompt-to-video; ad-generator (unless media.provided[] is set) |  |  | What the video should be about: the AI writes the script. For music-to-video it is an optional creative brief for the visual storyline. For lyrics-to-video, a description of the song to write (10 to 500 characters). For song-ad, the ad to write: the product or its page, the audience and the outcome. For character-swap, optional instructions on top of the swap (an outfit, ...). Workflows: prompt-to-video, ad-generator, music-to-video, lyrics-to-video, song-ad, character-swap. Accepted aliases: prompt, input. |
| `source.url` | string | audio-to-video, music-to-video, article-to-video, motion-transfer, caption-video, video-to-clips |  |  | The source content URL: audio file, video file, YouTube link, Spotify track, article page, or PDF depending on the workflow. http(s) is enforced; bare domains get https:// prepended. For character-swap, the video whose people are replaced (a file URL, or a YouTube, TikTok or Instagram link). Workflows: audio-to-video, music-to-video, article-to-video, motion-transfer, caption-video, video-to-clips, static-background-video, character-swap. |
| `source.websiteToRecord` | string | No |  |  | URL of a website to screen-record as footage for the video. Accepted aliases: websiteToRecord. |
| `source.quizzData` | object | No |  |  | Quiz definition for quiz videos: { title, questions: [{ id, question, answers: [{ id, answer, isCorrect }] }] }. Accepted aliases: options.quizzData, quizzData. |

### `media` (required for this workflow)

| Field | Type | Required | Default | Allowed | Description |
|---|---|---|---|---|---|
| `media.type` | string | No | moving-image for most workflows; ai-video for prompt-to-video and audio-to-video | `moving-image` / `ai-video` / `motion-graphics` / `stock-video` / `custom` | The visual engine for the video. Default: moving-image for most workflows; ai-video for prompt-to-video and audio-to-video. |
| `media.quality` | string | No | `"pro"` | `standard` / `pro` / `ultra` | Convenience profile that picks imageModel + videoModel for you. Explicit media.imageModel / media.videoModel override it. |
| `media.imageModel` | string | No |  | `cheap` / `good` / `ultra` | Image generation model. Auto-adjusted when needed: ai-video forces ultra, and presets/characters can upgrade it. Accepted aliases: imageGenerationModel. |
| `media.videoModel` | string | No |  | `base` / `pro` / `ultra` / `veo3` / `sora2` / `seedance-25-1080p` / `seedance-1080p` / `seedance-4k` / `minimax-h3-768p` / `minimax-h3-2k` / `wan-3-1080p` | Video generation model used when video clips are generated. The Ultra variants (seedance-1080p, seedance-4k, ...) pick the model and resolution of the ultra tier, like the create form's Ultra menu. media.type motion-graphics has three director tiers, the same as the create form, base directed by Sonnet 5.5 (2 credits/s), pro by Opus 5.5 (3 credits/s, default), ultra by Opus 5.5 at maximum effort (5 credits/s); each has a 15s minimum per generation, and veo3/sora2/the Ultra variants are automatically mapped to pro. character-swap takes base (Wan 3.0, default), pro (Seedance 2.5) or ultra (MiniMax Recast, swaps public figures too), billed per second of video. Accepted aliases: videoGenerationModel. |
| `media.density` | string | No |  | `low` / `medium` / `high` | How many distinct visuals per minute of video. More visuals = livelier video and higher generation cost. Accepted aliases: mediaMultiplier. |
| `media.animation` | string | No | `"soft"` | `none` / `soft` / `dynamic` / `depth` / `overlay` / `parallax` / `multiplane` / `cinemagraph` / `dust` / `hook` / `eco` | How still images are animated. Ignored for other media types. Only applies when media.type = moving-image. Accepted aliases: typeMovingImageAnim. |
| `media.mediaPreset` | string | No | `"DEFAULT"` |  | Visual style preset slug (e.g. DEFAULT, ANIME, PIXAR, GHIBLI, REALISM, ...). See the preset list in the docs. A preset can override media.imageModel when it requires a specific model. With media.type motion-graphics it is the motion graphics category instead, the create form's category cards: MOTION_INSPIRATION_AUTO (Auto, default: Let the agent choose the best category and reference for your brief), MOTION_INSPIRATION_CLEAN_WORDMARK (Minimal & type: Spacious layouts, expressive typography and focused graphic reveals), MOTION_INSPIRATION_PRODUCT_DEMOS (Product demos: Interface walkthroughs, cursor actions and clear feature demonstrations), MOTION_INSPIRATION_DARK_SAAS (Immersive SaaS: Layered interfaces, dramatic depth and polished feature launches), MOTION_INSPIRATION_WARM_LAUNCH (Mixed media: Photography, cutouts and textures woven into animated graphics), MOTION_INSPIRATION_FOOTAGE_LED (Footage-led: People, places and product footage with supporting motion graphics), MOTION_INSPIRATION_MASCOT (Mascot: A character mascot that acts and talks over clean product UI), MOTION_INSPIRATION_EDITORIAL (Editorial: Paper collage, archive photos, highlighted documents and maps, like a news explainer), MOTION_INSPIRATION_DIAGRAM (Diagram: Step-by-step schematics and labelled parts that build an idea), MOTION_INSPIRATION_ILLUSTRATED (Illustrated: Flat illustrated scenes and characters that turn each idea into a picture), MOTION_INSPIRATION_DATA_STORY (Data story: Charts, counters, rankings and maps that make numbers mean something), MOTION_INSPIRATION_MAPS (Maps: Animated maps: borders, routes and zooms from the world down to a city). Values match case-insensitively, with or without the MOTION_INSPIRATION_ prefix, or by label ("footage-led", "Minimal & type"); no value, DEFAULT or an image preset sent with motion-graphics means Auto, like the create form. Only applies when media.type = moving-image, ai-video or motion-graphics. Accepted aliases: media.generationPreset, options.generationPreset, generationPreset. |
| `media.maxItems` | integer | No |  | min 1 | Hard cap on the number of generated/selected media items. Accepted aliases: maxNbMedias. |
| `media.provided` | array | media.type = custom (>= 1 item); motion-transfer (target image) |  |  | Your own media items. Used as the only visuals when media.type = custom or media.useOnlyProvided = true; otherwise used as references/anchors. Items: { url (required), type, title, ... }. Accepted aliases: inputMedias. |
| `media.useOnlyProvided` | boolean | No | `false` |  | Use only media.provided[] items; disable AI media generation/search. Default: true for motion-transfer and ad-generator. Accepted aliases: options.useOnlyProvidedMedia, useOnlyProvidedMedia. |
| `media.useProvidedInOrder` | boolean | No | `false` |  | Map media.provided[] items to script sections in order (no AI picking). Forces useOnlyProvided. Fewer media than sections: adjacent sections are grouped; extra media are ignored. Requires at least one image/video item. Only applies when media.type = custom. |
| `media.mergeVideos` | boolean | No | `false` |  | Merge multiple provided videos into one sequence. |
| `media.mergeVideosFull` | boolean | No | `false` |  | Merge provided videos keeping their full length. |
| `media.addAudioToVideos` | boolean | No | true for prompt-to-video with media.type = ai-video, false otherwise |  | Generate ambient audio/sound for AI video clips. Default: true for prompt-to-video with media.type = ai-video, false otherwise. |
| `media.turnImagesIntoVideos` | boolean | No | `false` |  | Animate provided still images into video clips. |
| `media.applyStyleTransfer` | boolean | No | `false` |  | Re-style provided media to match the selected preset. |

### `options` (optional)

| Field | Type | Required | Default | Allowed | Description |
|---|---|---|---|---|---|
| `options.clipRange` | string | No |  |  | Clip one exact time range instead of letting the AI pick: "start:end" in seconds (e.g. "30:90"), or "start:end" with the word end (e.g. "30:end") to run to the end of the source. At least 10 seconds. Always a single clip, so options.outputCount above 1 is rejected with it. For character-swap, the part of the source to swap (up to 2 minutes, billed per second). Workflows: video-to-clips, character-swap. |
| `options.disableAudio` | boolean | No |  |  | Hard override: strip background music from the output regardless of the music block. Accepted aliases: disableAudio. |
| `options.disableVoice` | boolean | No |  |  | Hard override: no voice-over regardless of the voice block. Accepted aliases: disableVoice. |
| `options.soundEffects` | boolean | No | `false` |  | Generate sound effects matched to the scenes. Accepted aliases: hasToGenerateSoundEffects. |
| `options.addStickers` | boolean | No | `false` |  | Add animated stickers/emojis matched to the script. Accepted aliases: addStickers. |
| `options.nsfwFilter` | boolean | No | `false` |  | Filter NSFW content in generated media. Accepted aliases: enableNsfwFilter. |
| `options.language` | string | No |  |  | Language override for the whole generation (same as voice.language). Accepted aliases: language, lang, voice.language. |
| `options.watermark` | object\|null | No |  |  | Custom watermark object forwarded as-is ({ url, position, opacity, ... }). null removes it. Accepted aliases: watermark. |
| `options.selectedPalette` | string | No |  |  | Color palette name for generated visuals. |
| `options.preventSummarization` | boolean | No | `false` |  | Never shorten the provided text (overrides summarization heuristics). |
| `options.hasToGenerateCover` | boolean | No | `false` |  | Generate a cover/thumbnail image for the video. |
| `options.coverTextType` | string | No | `"layers"` |  | Style of the text on the generated cover. |
| `options.coverStyle` | string | No |  | `depth` / `editorial` / `magazine` / `journal` / `scrapbook` / `viral` | Look of the title on the generated cover. Omit for the plain title box. Only applies when options.coverTextType = layers. |
| `options.coverPrompt` | string | No |  |  | What the cover should show, up to 1200 characters (longer is truncated). Steers the cover concept; omit to design it from the script. |
| `options.hasTextSmallAtBottom` | boolean | No | `false` |  | Show a small text line at the bottom of the video. |

### `render` (optional)

| Field | Type | Required | Default | Allowed | Description |
|---|---|---|---|---|---|
| `render.resolution` | string | No | `"1080p"` | `720p` / `1080p` / `4k` | Output resolution. 4k exports cost extra credits. Accepted aliases: resolution. |
| `render.compression` | number | No | `18` | min 0, max 50 | H.264 CRF-style compression. Lower = better quality and bigger files. Accepted aliases: compression. |
| `render.frameRate` | number | No | `30` | min 1, max 60 | Output frame rate. Accepted aliases: frameRate. |

### `advanced` (optional)

| Field | Type | Required | Default | Allowed | Description |
|---|---|---|---|---|---|
| `advanced.customCreationParams` | object | No |  |  | Expert escape hatch: extra legacy creationParams merged into the final payload. Workflow identity keys (slug, flowType) stay locked. Use only with guidance from the Revid team. Accepted aliases: customCreationParams. |

## Notes

- Runs the same tool as the Character Swap form on /create. The source keeps its own ratio and soundtrack.
- source.url is the video: a file URL, or a YouTube, TikTok or Instagram link. Up to 2 minutes are swapped: options.clipRange ("start:end" in seconds) picks the part.
- People go left to right: media.provided images first, then the saved characters of characterIds, in the order given.
- media.videoModel picks the model: base (Wan 3.0, default), pro (Seedance 2.5, retried on Wan if refused) or ultra (MiniMax Recast, swaps everyone in one pass, public figures included). Billed per second of the swapped part.
- source.prompt adds instructions on top of the swap, for example an outfit.
- metadata.duration (seconds of the source) makes the credit estimate exact; the build measures the video either way.

## Response

Success: `{ "success": 1, "pid": "...", ... }` — the render is asynchronous. Get the result via your `webhookUrl` or poll `GET /api/public/v3/status?pid=...`.

Errors: 400 — validation error. The message names the exact field, e.g. "voice.speed must be between 0.5 and 2." 401 — missing or invalid API key (header `key`). 402 — the API key is valid but the account has no active plan. Generation endpoints require one; see https://www.revid.ai/pricing. 403 — the request was blocked in front of the API and never reached it; the body is plain text `error code: 1010`, not JSON. Usual cause: the HTTP client sent its default `User-Agent`. Set an explicit, descriptive `User-Agent` (for example `my-app/1.0`) and retry. 500 — internal error. Credits consumed by a failed render are refunded automatically.

---

All workflows: [script-to-video](https://www.revid.ai/llm/api/script-to-video.md) · [prompt-to-video](https://www.revid.ai/llm/api/prompt-to-video.md) · [audio-to-video](https://www.revid.ai/llm/api/audio-to-video.md) · [music-to-video](https://www.revid.ai/llm/api/music-to-video.md) · [lyrics-to-video](https://www.revid.ai/llm/api/lyrics-to-video.md) · [song-ad](https://www.revid.ai/llm/api/song-ad.md) · [article-to-video](https://www.revid.ai/llm/api/article-to-video.md) · [avatar-to-video](https://www.revid.ai/llm/api/avatar-to-video.md) · [static-background-video](https://www.revid.ai/llm/api/static-background-video.md) · [motion-transfer](https://www.revid.ai/llm/api/motion-transfer.md) · [character-swap](https://www.revid.ai/llm/api/character-swap.md) · [caption-video](https://www.revid.ai/llm/api/caption-video.md) · [video-to-clips](https://www.revid.ai/llm/api/video-to-clips.md) · [ad-generator](https://www.revid.ai/llm/api/ad-generator.md)

More: [endpoint reference](https://www.revid.ai/llm/api/endpoints.md) · [interactive docs](https://www.revid.ai/docs) · [OpenAPI spec](https://www.revid.ai/postman/revid-public-v3-render.openapi.json) · [MCP server](https://www.revid.ai/mcp)
