# Digital human / editing APIs

> Avatar library, digital-human video, lip sync, motion transfer, and social-style editing — all on one sk-gpushare-* key

来源：https://model.dflop.top/en/docs/reference/digital-human-apis

Register a **reusable digital-human avatar** from one photo or a short video, then drive it with audio or text to produce video. You can also lip-sync an existing video, transfer the motion from one video onto still portraits, or hand a talking-head video to [social-style editing](#5-social-style-editing-clip-compose) to have subtitle effects, a title, template styling and background music added automatically.

Authentication is identical to every other endpoint — the same `sk-gpushare-*` key in whichever of the four forms you prefer (`x-api-key` or `x-goog-api-key` header, `?key=` query, `Authorization: Bearer`); see [Authentication](./authentication.md). Everything bills against your account balance (shared by all keys), and an insufficient balance returns **402** `quota_exceeded`.

| Endpoint | Purpose | Billing |
|---|---|---|
| `POST /v1/videos/avatars` | Register an avatar (async) | **per call** |
| `GET /v1/videos/avatars` | Your avatar library | free |
| `GET /v1/videos/avatars/{id}` | Check an avatar's creation status | free |
| `DELETE /v1/videos/avatars/{id}` | Delete an avatar | free |
| `GET /v1/videos/avatars/presets` | The platform's built-in avatars | free |
| `POST /v1/videos/generations` | Produce video (digital human / lip sync / motion transfer **per second**; social-style editing **per task**) | **per second / per task** |
| `GET /v1/videos/generations/{id}` | Poll a production task | free |
| `GET /v1/videos/clip-templates` | Social-style editing templates (paginated) | free |
| `GET /v1/videos/clip-templates/categories` | Social-style editing template categories | free |
| `POST /v1/videos/clip-subtitles` | Social-style editing subtitle recognition (synchronous) | **per call** (currently 0) |

Producing and polling use the same generic video endpoints from the [media APIs](./media-apis.md); only the `model` and the body fields change. Each variation below comes with a complete example.

---

## 1. The avatar library

### POST /v1/videos/avatars

Register an avatar from a front-facing photo (or a video of the person). This is **async**: you get a `pending` record straight away and can only produce video once it polls `ready`, typically 5–10 minutes upstream.

```json
{
  "name": "My presenter",
  "source_url": "https://example.com/portrait.jpg",
  "source_kind": "image"
}
```

| Field | Required | Notes |
|---|---|---|
| `name` | yes | 1–20 characters, the avatar's name in your library |
| `source_url` | yes | A **publicly reachable** direct http(s) link. Upstream fetches the material itself and we offer no upload endpoint — host it yourself (object storage, a CDN, any public direct link) |
| `source_kind` | no | `image` (default) or `video` |

Response:

```json
{
  "id": "9f1c…",
  "name": "My presenter",
  "source_kind": "image",
  "status": "pending",
  "error": null,
  "created_at": "2026-07-29T08:12:00+00:00"
}
```

**That `id` is the avatar reference you'll use from now on** — put it in the `avatar` field when producing video; the underlying upstream identifier is not your concern.

### GET /v1/videos/avatars/{id}

Poll the creation status, every 15–30 seconds.

```json
{ "id": "9f1c…", "name": "My presenter", "status": "ready", "error": null, "created_at": "…" }
```

`status` has three states: `pending` / `ready` / `failed`. **Failures are refunded in full automatically** — the refund uses this record as its evidence, which is why deletion is blocked while it is `pending`.

### GET /v1/videos/avatars

Lists this account's avatars (up to 200, newest first).

### DELETE /v1/videos/avatars/{id}

Deletes an avatar. One still being created (`pending`) can't be deleted — wait until it succeeds or fails.

### GET /v1/videos/avatars/presets

The platform's built-in avatars, **free to use directly** with no registration:

```json
{ "avatars": [ { "id": "…", "name": "Professional female presenter" }, … ] }
```

Drop any `id` from `presets` straight into the `avatar` field of a production request.

---

## 2. Digital-human video (`dh-avatar`)

Make a ready avatar speak. **The driving audio or script sets the length**, and you're billed per second of finished video.

### Option 1: drive with audio

```json
{
  "model": "dh-avatar",
  "avatar": "9f1c…",
  "audio_url": "https://example.com/voice.mp3",
  "duration": 32
}
```

### Option 2: drive with text plus a voice (one step, no separate synthesis)

```json
{
  "model": "dh-avatar",
  "avatar": "9f1c…",
  "voice": "<voice id>",
  "text": "Hello everyone — today I want to introduce…",
  "duration": 30
}
```

`voice` can be either:

- the **id** of a voice you cloned through `POST /v1/audio/voices`, or
- a platform preset voice id from the `presets` in `GET /v1/audio/voices`.

> **`duration` is required**, in seconds. It's the basis for our cost hold — use the real length of the driving audio, or estimate from the script (Mandarin narration runs at roughly `characters ÷ 3.3` seconds). When the task ends we **settle on the actual seconds upstream reports** and refund the excess automatically; the hold also caps what this production can cost.

Submit and poll on the generic video endpoints:

```bash
curl https://api.dflop.top/v1/videos/generations \
  -H "Authorization: Bearer $GPUSHARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"dh-avatar","avatar":"9f1c…","audio_url":"https://example.com/voice.mp3","duration":32}'
# → {"id":"…","status":"queued","model":"dh-avatar","created_at":1753…}

curl https://api.dflop.top/v1/videos/generations/<id> \
  -H "Authorization: Bearer $GPUSHARE_API_KEY"
# → {"id":"…","status":"succeeded","video_url":"https://…","expires_at":…}
```

---

## 3. Lip sync (`dh-lipsync` / `-pro` / `-max`)

Swap the audio track on an **existing video of a person** and match the lips to it. The three tiers step up in quality and price.

```json
{
  "model": "dh-lipsync-pro",
  "source_video_url": "https://example.com/source.mp4",
  "audio_url": "https://example.com/new-voice.mp3",
  "duration": 45
}
```

The finished length follows the **driving audio**. Both the source video and the audio must be publicly reachable direct links.

---

## 4. Motion transfer (`dh-motion`)

Transfer the motion from a **source video** onto 1–7 portraits.

```json
{
  "model": "dh-motion",
  "source_video_url": "https://example.com/dance.mp4",
  "face_count": 2,
  "resolution": "standard",
  "content": [
    { "type": "image_url", "image_url": { "url": "https://example.com/person1.jpg" } },
    { "type": "image_url", "image_url": { "url": "https://example.com/person2.jpg" } }
  ],
  "duration": 20
}
```

| Field | Notes |
|---|---|
| `source_video_url` | The motion source video (public direct link) |
| `content[]` | 1–7 portraits |
| `face_count` | How many people are in frame, 1–7; should match the number of portraits |
| `resolution` | `fast` / `standard` (default) / `max` — **the tier sets the unit price**; see the pricing table below |
| `duration` | Required; use the source video's length |

The finished length follows the **motion source video**.

---

## 5. Social-style editing (`clip-compose`)

Turn a video with **clear speech** into a finished, packaged piece: subtitle effects, keyword highlights, a title, a name card, a template style, background music and picture-in-picture inserts. It takes four steps: **pick a template → recognise subtitles → submit the compose → poll for the result**.

- The key's model allowlist must permit `clip-compose` (the template, category and subtitle helper endpoints check the same thing); otherwise you get 403 `model_not_allowed`.
- The source video must be **≤ 5 minutes** with clearly audible speech, at a publicly reachable http(s) direct link (the platform offers no upload endpoint).
- A finished video typically arrives in **20–60 seconds**.

### Step 1: pick a template

```bash
# Template categories (six: 高级感 / 热门 / 简约 / 综艺感 / 本地引流 / 其他)
curl https://api.dflop.top/v1/videos/clip-templates/categories \
  -H "Authorization: Bearer $GPUSHARE_API_KEY"
# → {"list":[{"cate_id":"67d402f96574a9003050ea3a","cate_name":"高级感","sort":1}, …]}

# Templates (with cate_id: that category only; without: every template)
curl "https://api.dflop.top/v1/videos/clip-templates?cate_id=67d402f96574a9003050ea3a" \
  -H "Authorization: Bearer $GPUSHARE_API_KEY"
# → {"list":[{"style_id":"…","name":"…","cover_url":"https://…","demo_url":"https://…","cate_name":"","scene":""}, …],
#    "sid":"","api_version":"old"}
```

The template list is **not paginated**: one call returns the whole category (or every template), and `sid` is always an empty string. `demo_url` is the template's sample video. Put the chosen template's `style_id` into the compose request's `video_style_id`. Both endpoints are free.

> Subtitle effects, keyword highlights, the title and the name card are **all rendered by the template**: compose without a template and none of them appear — only the picture-in-picture inserts are overlaid. Always send `video_style_id`. Only templates listed by this endpoint are accepted; a `style_id` from anywhere else is rejected (400 "这套模板已经下架了").

### Step 2: recognise subtitles

```bash
curl https://api.dflop.top/v1/videos/clip-subtitles \
  -H "Authorization: Bearer $GPUSHARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"video_url":"https://example.com/talk.mp4"}'
```

This is **synchronous**, usually 5–10 seconds; it waits at most 240 seconds and then returns **504** `upstream_timeout`. The response:

```json
{
  "model": "clip-subtitle",
  "asr_id": "asr_…",
  "api_version": "old",
  "segments": [
    { "showRange": [0, 1750], "content": "很多人做事总纠结" },
    { "showRange": [1750, 2850], "content": "完美才开始" }
  ],
  "sentences": [
    { "showRange": [0, 1750], "content": "很多人做事总纠结" },
    { "showRange": [1750, 2850], "content": "完美才开始" }
  ]
}
```

| Field | Notes |
|---|---|
| `asr_id` | The id of this recognition — **required when composing** |
| `segments` | The result **by sentence**: one sentence per item, `showRange` is `[startMs, endMs]`, no punctuation |
| `sentences` | The same as `segments` (kept for compatibility) |

A video with no recognisable speech returns 400.

### Step 3: submit the compose

```bash
curl https://api.dflop.top/v1/videos/generations \
  -H "Authorization: Bearer $GPUSHARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "clip-compose",
    "video_url": "https://example.com/talk.mp4",
    "asr_id": "<asr_id from step 2>",
    "video_style_id": "<style_id from step 1>",
    "title": "Compound interest in three minutes",
    "enable_title_effect": true,
    "enable_caption_effect": true,
    "enable_keyword_effect": true,
    "name_card": { "name": "Ms Zhang", "description": "Financial planner" }
  }'
# → {"id":"…","status":"queued","model":"clip-compose","created_at":1790…}
```

| Field | Required | Notes |
|---|---|---|
| `model` | yes | Always `clip-compose` |
| `video_url` | yes | The source video's direct link — the **same** one you ran subtitles on in step 2 |
| `asr_id` | yes | The `asr_id` returned in step 2 |
| `enable_title_effect` | no | Add a title effect; default `false` |
| `enable_caption_effect` | no | Add subtitle effects; default `true` |
| `enable_keyword_effect` | no | Highlight keywords; default `true` |
| `title` | no | Title text, at most 30 characters (longer is truncated) |
| `video_style_id` | no | A template's `style_id` (step 1). **Send it**: without a template the video gets no subtitles, title or name card |
| `music_url` | no | A background music direct link (http(s)) |
| `name_card` | no | A name card `{"name": "…", "description": "…"}`; `name` at most 20 characters, `description` at most 40 |
| `sentences` | no | **Only if you edited the subtitles** — rules below; omit it to use step 2's original recognition as is |
| `pip_config` | no | Picture-in-picture inserts, at most 20: `[{"media_url": "https://…", "begin_time": 3000, "end_time": 6000}]`, times in **milliseconds**, `end_time` > `begin_time` |

#### `sentences`: edit the words, keep the lines

Send `sentences` only when your user changed the subtitle text. The rule is **one line for one line**:

```json
"sentences": [
  { "showRange": [0, 1750], "content": "好多人做事总纠结" },
  { "showRange": [1750, 2850], "content": "完美,才开始" }
]
```

- Start from step 2's `segments`: **same number of lines, same order**, and change only `content`. Lines may get longer or shorter and may gain punctuation.
- **Don't split, merge or delete lines.** A different line count returns 400 telling you how many lines are expected. (Upstream doesn't reject such subtitles — it silently drops all your edits or cuts the end of the video — so the platform stops the request before it is submitted, and nothing is charged.)
- No line's `content` may be empty.
- Timing comes from step 2's recognition; any `showRange` you send is ignored.
- If nothing was edited, **omit `sentences` entirely** and the original recognition is used.
- `asr_id` must be the one step 2 just returned; if an old recognition has expired you get 400 "字幕识别结果已经过期" — just run step 2 again.

### Step 4: poll for the result

Exactly like any other video task: poll [`GET /v1/videos/generations/{id}`](./media-apis.md#polling), every 5–10 seconds or so:

```bash
curl https://api.dflop.top/v1/videos/generations/<id> \
  -H "Authorization: Bearer $GPUSHARE_API_KEY"
# → {"id":"…","status":"succeeded","video_url":"https://…","expires_at":…}
```

### Social-style editing pricing

| Endpoint / model ID | Price | Notes |
|---|---|---|
| `clip-compose` (compose) | **48 per task** | A flat price per successful compose task, **regardless of video length**; failures are refunded in full automatically |
| `clip-subtitle` (subtitle recognition) | per call, **currently 0** | Charged only on success; failures and timeouts cost nothing |
| Templates / categories | free | |

---

## Pricing

For the per-second SKUs, **settlement uses the actual seconds upstream reports**; `duration` sizes the hold on submit and the excess is refunded. `clip-compose` is a flat price per task and needs no `duration`.

| Model ID | Purpose | Price |
|---|---|---|
| `dh-avatar-create` | Register an avatar | **220.11 each** |
| `dh-avatar` | Digital-human video | 3.96 / second |
| `dh-lipsync` | Lip sync · standard | 4.04 / second |
| `dh-lipsync-pro` | Lip sync · HD | 8.09 / second |
| `dh-lipsync-max` | Lip sync · UHD | 12.13 / second |
| `dh-motion` | Motion transfer | `fast` 4.04 / `standard` 8.09 / `max` 12.13 per second |
| `clip-compose` | Social-style editing | **48 per task** (regardless of length) |

The companion voice endpoints (synthesis and cloning) are in the [media APIs](./media-apis.md#post-v1audiospeech).

---

## Async and timeout conventions

- Avatar registration, video production and social-style compose are **all async** (social-style subtitle recognition is synchronous): submitting returns immediately and you poll the status endpoint afterwards. Polling itself is free.
- A production task's `video_url` is a **time-limited link**, with `expires_at` in the response as its expiry — copy it somewhere of your own if you need it long-term.
- Failed tasks (an upstream error, a timeout, expiry) are **refunded in full automatically**; you need do nothing.
- The avatar record is the refund's evidence: calling `DELETE` while it's `pending` returns 400, so wait for a final state.

## Common errors

| HTTP | `code` | Meaning |
|---|---|---|
| 400 | `invalid_request_error` | The `avatar` or `voice` reference doesn't exist, belongs to another account, or isn't `ready` yet; `duration` is missing; `name` is too long; `source_url` / `video_url` isn't http(s); subtitle recognition found no speech; `pip_config` start/end times are invalid |
| 401 | `authentication_error` | The key is invalid or disabled |
| 402 | `quota_exceeded` | Insufficient account balance |
| 403 | `model_not_allowed` | This key's model allowlist doesn't include that SKU |
| 404 | `model_not_found` | The model id is wrong, or that SKU isn't available yet |
| 503 | `no_channel_available` | No upstream is currently available for that SKU; retry later |
| 504 | `upstream_timeout` | Subtitle recognition didn't finish within 240 seconds (not charged); retry, or use a shorter video |

The error body matches every other endpoint — see [Error codes](./errors.md).
