> ## Documentation Index
> Fetch the complete documentation index at: https://docs.percify.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Analyze and replicate a short video with the Percify API

> Send a TikTok, Shorts or Reels link to the Percify API, get a shot-by-shot blueprint, then render an AI replica with a side-by-side comparison video.

The Percify replication API turns a short video into a shot-by-shot blueprint and then renders an AI copy of it. `POST /v3/replicate/v1/analyze` takes a TikTok, YouTube Shorts, Instagram Reels or direct video link under two minutes and returns an analysis id. When the analysis is `ready`, `POST /v3/replicate/v1/analyses/{id}/replicate` renders each clip with a video model, stitches them and produces a side-by-side comparison with the original.

```text theme={"system"}
https://api.percify.io/v3/replicate/v1
```

All endpoints use the same `Authorization: Bearer pk_live_…` header as the rest of the API. See [Authentication](/percify/api-auth).

| Method | Path                          | What it does                                                      |
| ------ | ----------------------------- | ----------------------------------------------------------------- |
| `POST` | `/v1/analyze`                 | Start an analysis of a video link                                 |
| `GET`  | `/v1/analyses/{id}`           | Read the analysis and its blueprint                               |
| `POST` | `/v1/analyses/{id}/blueprint` | Build the blueprint for an analysis that stopped at the shot list |
| `POST` | `/v1/analyses/{id}/replicate` | Render a replica                                                  |
| `GET`  | `/v1/runs/{id}`               | Read a replication run                                            |

## Analyze a video

<ParamField body="url" type="string" required>
  An `https` link to a TikTok, YouTube (including Shorts) or Instagram video, or a direct `.mp4`, `.mov` or `.webm` file. The video must be two minutes or shorter. Photo posts are refused.
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  Reuse the same value on retries to get the original analysis back instead of paying again. Kept for 24 hours.
</ParamField>

```bash theme={"system"}
curl -s -X POST https://api.percify.io/v3/replicate/v1/analyze \
  -H "Authorization: Bearer $PERCIFY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: analyze-launch-reel" \
  -d '{"url":"https://www.tiktok.com/@creator/video/7412345678901234567"}'
```

```json 201 Response theme={"system"}
{ "success": true, "data": { "analysisId": "9c41e2d0-7f3b-4a8e-b1d6-2e5f8a0c4b19", "status": "analyzing", "creditsSpent": 5 } }
```

An analysis costs a flat 5 credits, refunded if it fails.

## Get an analysis

`GET /v3/replicate/v1/analyses/{id}` returns:

<ResponseField name="data.status" type="string">
  `analyzing`, `ready` or `failed`.
</ResponseField>

<ResponseField name="data.title" type="string | null">
  A title for the video.
</ResponseField>

<ResponseField name="data.durationSeconds" type="number | null">
  Length of the source video.
</ResponseField>

<ResponseField name="data.clipCount" type="integer | null">
  Number of clips in the blueprint.
</ResponseField>

<ResponseField name="data.sourceUrl" type="string">
  The link you sent.
</ResponseField>

<ResponseField name="data.videoUrl" type="string | null">
  Percify's copy of the source video.
</ResponseField>

<ResponseField name="data.posterUrl" type="string | null">
  A poster frame.
</ResponseField>

<ResponseField name="data.blueprint" type="object | null">
  The shot-by-shot breakdown. `clips` is an array where each clip has `start` and `end` times, a generation `prompt`, `camera` framing and movement, per-second `beats`, `onScreenText` and `overlays`, `keyframes` and, for split-screen shots, a `layout`. The blueprint also carries `styleBible`, `soundtrack`, `aspect`, `shots`, `speakers` and `characters`.
</ResponseField>

<ResponseField name="data.error" type="string | null">
  Why the analysis failed.
</ResponseField>

If the analysis is `ready` but its clips have `"prompt": null`, only the shot list exists. Call `POST /v3/replicate/v1/analyses/{id}/blueprint` to build the rest, then poll again until it is `ready`. The call answers `{ "started": true }`, or `started: false` with a `reason` such as `Blueprint already built`.

## Render a replica

`POST /v3/replicate/v1/analyses/{id}/replicate`

<ParamField body="modelId" type="string" default="seedance-2-fast-i2v">
  The video model for every clip. Models the API names include `seedance-2-fast-i2v` (follows the original frames most closely), `kling-v2-6-std-motion-control` (driven by the original's movement), `seedance-v1-5-pro-i2v`, `p-video` and `seedance-2-fast-t2v` (prompt only). Another model returns `400` with the list.
</ParamField>

<ParamField body="quality" type="string" default="medium">
  `low`, `medium` or `high`.
</ParamField>

<ParamField body="accuracy" type="string" default="fast">
  How many blueprint clips share one generation on multi-shot models. `fast` packs consecutive clips into fewer, longer takes, `balanced` keeps hard fades as seams, and `exact` renders one generation per shot.
</ParamField>

<ParamField body="clips" type="integer[]">
  Clip indexes to render. Leave it out for the full video.
</ParamField>

<ParamField body="characterImage" type="string">
  An `https` image of a person or product to put in place of the original subject in every clip. It is checked by content moderation.
</ParamField>

<ParamField body="captions" type="boolean" default="false">
  Burn the original's text overlays into the replica.
</ParamField>

<ParamField body="audio" type="object">
  `{"mode": "chunks"}` keeps each clip's original audio, `{"mode": "original"}` lays the source soundtrack over the result, and `{"mode": "upload", "url": "https://…"}` uses your own track. Leave it out for a silent video.
</ParamField>

<ParamField body="marketingAspect" type="string" default="9:16">
  `9:16` or `16:9` for the branded side-by-side marketing cut.
</ParamField>

<ParamField body="webhook" type="string">
  `https` URL that receives the run once it is `done` or `failed`. See [Webhooks](/guides/webhooks).
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  Same value on retries returns the original run. Kept for 24 hours.
</ParamField>

```bash theme={"system"}
curl -s -X POST https://api.percify.io/v3/replicate/v1/analyses/9c41e2d0-7f3b-4a8e-b1d6-2e5f8a0c4b19/replicate \
  -H "Authorization: Bearer $PERCIFY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: replica-launch-reel-1" \
  -d '{"modelId":"seedance-2-fast-i2v","quality":"medium","audio":{"mode":"chunks"},"captions":true}'
```

```json 201 Response theme={"system"}
{ "success": true, "data": { "runId": "4e8a1c5f-2b9d-4f07-8c3e-6a1d9b2f0e57", "modelId": "seedance-2-fast-i2v" } }
```

A replica is charged per generation as each clip renders, at that model's price. Clips that fail are refunded.

## Get a replication run

`GET /v3/replicate/v1/runs/{id}` returns:

<ResponseField name="data.status" type="string">
  `generating` and `stitching` while it works, then `done` or `failed`.
</ResponseField>

<ResponseField name="data.totalClips" type="integer">
  Clips in this run. `doneClips` counts finished ones.
</ResponseField>

<ResponseField name="data.outputVideoUrl" type="string | null">
  The finished replica.
</ResponseField>

<ResponseField name="data.compareVideoUrl" type="string | null">
  Side-by-side video of the original and the replica.
</ResponseField>

<ResponseField name="data.cleanOutputVideoUrl" type="string | null">
  The same replica without the burned-in text, when `captions` was on.
</ResponseField>

<ResponseField name="data.marketingVideoUrl" type="string | null">
  The branded marketing cut. `marketingVideoUrl169` is the 16:9 version. They can arrive a little after the run is `done`.
</ResponseField>

<ResponseField name="data.shareUrl" type="string | null">
  A public link to the result on app.percify.io, once the run is `done`.
</ResponseField>

The object also has `id`, `analysisId`, `modelId`, `quality`, `error` and `createdAt`.

## Errors

| Status | Message starts with                                                                    | Fix                                                                     |
| ------ | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `400`  | `URL must be https` or `Use a TikTok / YouTube / Instagram link or a direct video URL` | Send a supported `https` link.                                          |
| `400`  | `That link is a photo post, not a video`                                               | Use a video post.                                                       |
| `400`  | `That video is` … `long`                                                               | Trim the video to two minutes or less.                                  |
| `400`  | `Flow is not ready`                                                                    | Wait until the analysis is `ready`.                                     |
| `400`  | `Still reading the shots`                                                              | The blueprint is not written yet. Wait, or call the blueprint endpoint. |
| `400`  | `Clip selection is out of range`                                                       | Use indexes from `0` to `clipCount - 1`.                                |
| `400`  | `Unknown replication model`                                                            | Use one of the listed model ids.                                        |
| `400`  | `Not enough credits`                                                                   | Top up at [app.percify.io/billing](https://app.percify.io/billing).     |
| `404`  | `Flow not found` or `Run not found`                                                    | The id does not belong to your account.                                 |

These endpoints have no `wait` parameter. Poll every few seconds, or pass a `webhook`.

## Related

<CardGroup cols={2}>
  <Card title="Remake a video in the app" href="/create/replicate">
    The same pipeline without code.
  </Card>

  <Card title="Async jobs and polling" href="/api-reference/async-jobs">
    Statuses for every job type.
  </Card>

  <Card title="Webhooks" href="/guides/webhooks">
    The replication.completed payload.
  </Card>

  <Card title="MCP server" href="/mcp-server">
    analyze\_video and replicate\_video for agents.
  </Card>
</CardGroup>
