Character Swap Overview
Replace the person in a video with your character, keeping camera, motion, timing and audio
Overview
Character Swap puts a character created with the
Character API into a customer-supplied video. You send an
Instagram, TikTok, Threads or YouTube Shorts link — or a direct video URL —
plus a character_id, and the person in the video is replaced by your
character while the camera, motion, timing and (by default) the original
audio are preserved.
Base URL: https://api.goodtake.ai/v1
No prompt, no model, no reference config in the request. The swap is compiled
server-side and dispatched through the same generation pipeline as everything
else. The model is pinned to Dreamina Seedance 2.5
(dreamina-seedance-2-5-260628).
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /character-swap | Create a swap from a video link + character. Returns 202. |
GET | /character-swap/{id} | Poll: derived status, source metadata, finished video. |
GET | /character-swap | List your organization's swaps (paginated). |
POST | /character-swap/{id}/regenerate | New run from the stored source. Returns 202. |
GET | /character-swap/estimate | Quote a swap before creating one. |
Requirements
- The character must have a completed sheet — Character API status
ready, or at least a finished sheet image. Otherwise the create returns422. - The source video must be 4–30 seconds. This is a Seedance 2.5 video-edit constraint. It is checked twice: an early advisory reject from the scraper's metadata, and an authoritative probe during ingest.
Supported sources
Social hosts are classified by hostname, not by path, and resolved through a per-platform resolver. Direct video URLs skip that step entirely.
| Host | source.kind |
|---|---|
instagram.com | instagram |
tiktok.com, vm.tiktok.com, vt.tiktok.com | tiktok |
threads.net, threads.com | threads |
youtube.com, youtu.be | youtube |
any other http(s) video URL | direct |
Because classification is host-based, a link to a non-video path on a supported host still gets sent to that platform's resolver and comes back as a precise ingest error rather than an instant rejection.
Cost
One swap = one Seedance 2.5 video generation, charged on completion. Cost is
token-based and scales with source duration and resolution — a swap bills
tokens for the source clip and the generated output, so cost is roughly
2 × duration × the per-second token rate.
At current settings:
| Resolution | 10s source | Approx. |
|---|---|---|
480p | ~87 credits | ~$2.61 |
720p | ~186 credits | ~$5.58 |
1080p | ~456 credits | ~$13.68 |
Quote before you create
GET /v1/character-swap/estimate?resolution=1080p&duration_seconds=10curl -s "https://api.goodtake.ai/v1/character-swap/estimate?resolution=720p&duration_seconds=12" \
-H "Authorization: Bearer $GOODTAKE_API_KEY" | jq .{
"estimated_cost": 223,
"resolution": "720p",
"duration_seconds": 12,
"model": "dreamina-seedance-2-5-260628"
}Durations under the provider's 4-second billing minimum are priced as 4s.
Durations over 30s return 422.
Where the quote shows up
| Moment | estimated_cost |
|---|---|
Create 202 | null — the source has not been ingested yet, so its duration (the main cost driver) is unknown. Carries estimated_cost_range {min, max} instead: the 4s and 30s brackets at the chosen resolution. |
| Poll | Exact, once ingest has measured the source. Sits next to cost_credits (the actual charge, 0 until completion). |
Regenerate 202 | Exact — the stored source's duration is already known. |
The charge is capped at the quote. The billing dimensions carry the source duration as the requested output length, so a provider token overrun costs us margin, never you. Resolver runs and the audio processing are not billed separately.
Relationship to the Image API
Swap generations are visible in GET /image/generations
— billed rows stay visible — but with an empty prompt, which is the only
thing keeping the compiled prompt off the wire.