GoodTake AI Docs
Character Swap API

Create a Swap

POST /v1/character-swap — swap your character into a video

Endpoint

POST /v1/character-swap

Returns 202 Accepted immediately. Fetching the video, probing its duration, extracting its audio and mirroring it all happen in the background — so every ingest failure surfaces on the swap resource, not on this response.

Request body

{
  "character_id": "9d2f6a3e-4c11-4a90-b1d7-2f8e6a4c0b11",
  "video_url": "https://www.instagram.com/reel/ABC123/",
  "background": "a neon-lit rooftop at night",
  "with_audio": true,
  "resolution": "1080p"
}
FieldTypeRequiredDescription
character_idstringYesA character with a completed sheet. Otherwise 422.
video_urlstringYesA social post link or a direct video URL. Must resolve to a 4–30 second video. Internal/private addresses are rejected.
backgroundstringNoOptional environment replacement. Sanitized server-side before it can reach the prompt (600-char cap). Omitted = the original environment is kept.
with_audiobooleanNoDefault true. Keeps the source video's original audio track.
resolutionstringNo480p | 720p | 1080p. Drives the cost.

Response — 202 Accepted

{
  "id": "1f7b2c40-8e55-4b21-9f03-77a1d0c4e902",
  "status": "pending",
  "character_id": "9d2f6a3e-4c11-4a90-b1d7-2f8e6a4c0b11",
  "model": "dreamina-seedance-2-5-260628",
  "estimated_cost": null,
  "estimated_cost_range": { "min": 183, "max": 1368 },
  "with_audio": true,
  "resolution": "1080p",
  "poll_url": "/public/v1/character-swap/1f7b2c40-8e55-4b21-9f03-77a1d0c4e902"
}

estimated_cost is null here on purpose: the source has not been ingested yet, so its duration — the main cost driver — is not yet known. Poll the swap for the exact figure.

How audio works

with_audio: true (the default) extracts the original audio track during ingest and muxes it onto the generated video before the video is stored, so you never see a silent intermediate. The model is always asked for a silent video and the original track is layered back on.

with_audio: false delivers the silent result.

If the audio mux fails for any reason, you get the silent video rather than a failed generation. A paid generation is never thrown away over an audio step.

Content-safety rejections are terminal

The character sheet and the source video are both moderated before any generation is submitted. A moderation verdict is deterministic — the generation API would refuse the identical bytes for the identical reason moments later — so the swap stops there, nothing is dispatched, and nothing is billed.

Which input was refused is part of the contract, because the fix is different in each case:

The source video was refused (most often: it shows an identifiable real person):

{
  "status": "failed",
  "error": "the source video was rejected by the content-safety filter (it may show an identifiable real person); try a different video"
}

The character sheet was refused — the video is fine, and changing it accomplishes nothing:

{
  "status": "failed",
  "error": "the character sheet was rejected by the content-safety filter; recreate the character from different reference photos"
}

Regenerate cannot help in either case — the stored material is permanently unusable — and answers 422 with the same message. Create a new swap from a different video, or a new character from different photos.

Example

curl -s -X POST https://api.goodtake.ai/v1/character-swap \
  -H "Authorization: Bearer $GOODTAKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "character_id": "9d2f6a3e-4c11-4a90-b1d7-2f8e6a4c0b11",
    "video_url": "https://www.tiktok.com/@someone/video/7300000000000000000",
    "resolution": "720p",
    "with_audio": true
  }' | jq -r .id

Errors

StatusMeaning
400Invalid request, or a video_url rejected by the URL guard
401Missing or invalid API key
404Unknown character_id, or one belonging to another organization
422The character has no completed sheet
503The pinned model is disabled, or social-link ingestion is not configured on this deployment (direct video URLs keep working)