Документация

Wan API

В каталоге 5 записей семейства Wan: версии и режимы. Имя модели ставится в поле model запроса createTask, а всё остальное уходит вложенным объектом input. У каждой модели ниже её поля, готовый вызов и ответ; общее для всех видео — на странице Видео, а сам порядок вызова разобран в разделе Как устроена задача.

Модели семейства

Идентификатор Модель Задача Страница на сайте
wan/3-0-video Wan 3.0 Video text-to-video Открыть
wan/2-7-text-to-video Wan 2.7 text-to-video text-to-video Открыть
wan/2-7-image-to-video Wan 2.7 image-to-video image-to-video Открыть
wan/2-7-r2v Wan 2.7 reference-to-video reference-to-video Открыть
wan/2-7-videoedit Wan 2.7 Video Edit video-edit Открыть

wan/3-0-video

Поля запроса

ПолеТипОбяз.Допустимые значения
model текст обязательное wan/3-0-video
input объект обязательное Параметры генерации — поля из таблицы ниже

Поля input

ПолеТипОбяз.Допустимые значения
prompt текст обязательное от 1 до 20 000 символов
first_frame_url ссылка опц. одна ссылка на изображение
last_frame_url ссылка опц. одна ссылка на изображение
reference_image_urls список ссылок опц. до 10 ссылок на изображение
reference_video_urls список ссылок опц. до 5 ссылок на видео
reference_audio_urls список ссылок опц. до 5 ссылок на аудио
reference_file_urls список ссылок опц. до 1 ссылок на аудио
reference_link_urls список ссылок опц. до 1 ссылок на аудио
resolution значение из списка опц. 480P · 720P · 1080P
aspect_ratio значение из списка опц. adaptive · 16:9 · 4:3 · 1:1 · 3:4 · 9:16
duration целое число опц. целое от 2 до 30
audio да или нет опц. true или false
seed целое число опц. целое от 0 до 2 147 483 647
nsfw_checker да или нет опц. true или false

Пример вызова

task.sh
# 1. Поставить задачу — в ответе придёт её номер
TASK=$(curl -s https://api.gen202.com/api/v1/jobs/createTask \
  -H "Authorization: Bearer sk-ваш-ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan/3-0-video",
    "input": {
      "prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
      "aspect_ratio": "adaptive",
      "resolution": "480P",
      "duration": 2
    }
  }' | jq -r '.data.taskId')

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while true; do
  RECORD=$(curl -s "https://api.gen202.com/api/v1/jobs/recordInfo?taskId=$TASK" \
    -H "Authorization: Bearer sk-ваш-ключ")
  STATE=$(echo "$RECORD" | jq -r '.data.state')
  case "$STATE" in success|fail) break;; esac
  sleep 3
done

# 3. Забрать результат. Ссылка на файл лежит в resultJson — это строка JSON
#    внутри JSON, поэтому её разбирают вторым разбором (fromjson)
echo "$RECORD" | jq -r '.data |
  if .state == "fail"
  then "отказ \(.failCode): \(.failMsg)"
  else .resultJson | fromjson | .resultUrls[0]
  end'
task.py
import json
import time

import requests

headers = {"Authorization": "Bearer sk-ваш-ключ"}

# 1. Поставить задачу — в ответе придёт её номер
created = requests.post(
    "https://api.gen202.com/api/v1/jobs/createTask",
    headers=headers,
    json={
      "model": "wan/3-0-video",
      "input": {
        "prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
        "aspect_ratio": "adaptive",
        "resolution": "480P",
        "duration": 2
      }
    },
).json()
task_id = created["data"]["taskId"]

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while True:
    task = requests.get(
        "https://api.gen202.com/api/v1/jobs/recordInfo",
        headers=headers,
        params={"taskId": task_id},
    ).json()["data"]
    if task["state"] in ("success", "fail"):
        break
    time.sleep(3)

# 3. Разобрать итог. У неудачи причина в failCode и failMsg
if task["state"] == "fail":
    raise SystemExit("отказ " + task["failCode"] + ": " + task["failMsg"])

# resultJson — строка JSON внутри JSON, поэтому разбор второй
result = json.loads(task["resultJson"])
print(result["resultUrls"][0])
print("списано кредитов:", task["creditsConsumed"])

Пример проходит весь путь: ставит задачу, повторяет запрос состояния раз в 3 секунды, пока задача не закончится, и разбирает ответ до ссылки на файл. Разборов два, потому что поле resultJson — строка JSON внутри JSON. В теле первого запроса стоят обязательные поля модели и значения из её перечислений, поэтому он проходит проверку шлюза как есть. Подставить остаётся только свой ключ. Пример на оболочке разбирает ответ через jq; на Python своего ничего не нужно, кроме requests.

Ответ, когда задача готова

recordInfo.json
{
  "code": 200,
  "msg": "success",
  "data": {
    "taskId": "cm8r4t0vk0001s60p2xq7f9ab",
    "model": "wan/3-0-video",
    "state": "success",
    "resultJson": "{\"resultUrls\":[\"https://file.example/result.mp4\"]}",
    "failCode": "",
    "failMsg": ""
  }
}

Ссылка на файл лежит в resultJson — это строка, внутри которой ещё один JSON, поэтому разбирать её нужно вторым разбором. Пока задача не завершилась, там пустая строка, а в statewaiting, queuing или generating. В настоящем ответе есть ещё поле creditsConsumed со списанием; здесь его нет намеренно — сумма зависит от запрошенного варианта, а цены живут в каталоге. Все поля записи перечислены в разделе Формат ответа.

Общее для всех моделей: Как устроена задача — чем отправить и как дождаться · Формат ответа — где в ответе ссылка на файл · Загрузка своих файлов — как получить ссылку на свою картинку · Отказы — что означает отказ

wan/2-7-text-to-video

Поля запроса

ПолеТипОбяз.Допустимые значения
model текст обязательное wan/2-7-text-to-video
input объект обязательное Параметры генерации — поля из таблицы ниже

Поля input

ПолеТипОбяз.Допустимые значения
prompt текст обязательное от 1 до 5 000 символов
negative_prompt текст опц. не длиннее 500 символов
resolution значение из списка опц. 720p · 1080p
ratio значение из списка опц. 16:9 · 9:16 · 1:1 · 4:3 · 3:4
duration целое число опц. целое от 2 до 15
audio_url ссылка опц. одна ссылка на аудио
prompt_extend да или нет опц. true или false
watermark да или нет опц. true или false
seed целое число опц. целое от 0 до 2 147 483 647
nsfw_checker да или нет опц. true или false

Пример вызова

task.sh
# 1. Поставить задачу — в ответе придёт её номер
TASK=$(curl -s https://api.gen202.com/api/v1/jobs/createTask \
  -H "Authorization: Bearer sk-ваш-ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan/2-7-text-to-video",
    "input": {
      "prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
      "resolution": "720p",
      "duration": 2
    }
  }' | jq -r '.data.taskId')

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while true; do
  RECORD=$(curl -s "https://api.gen202.com/api/v1/jobs/recordInfo?taskId=$TASK" \
    -H "Authorization: Bearer sk-ваш-ключ")
  STATE=$(echo "$RECORD" | jq -r '.data.state')
  case "$STATE" in success|fail) break;; esac
  sleep 3
done

# 3. Забрать результат. Ссылка на файл лежит в resultJson — это строка JSON
#    внутри JSON, поэтому её разбирают вторым разбором (fromjson)
echo "$RECORD" | jq -r '.data |
  if .state == "fail"
  then "отказ \(.failCode): \(.failMsg)"
  else .resultJson | fromjson | .resultUrls[0]
  end'
task.py
import json
import time

import requests

headers = {"Authorization": "Bearer sk-ваш-ключ"}

# 1. Поставить задачу — в ответе придёт её номер
created = requests.post(
    "https://api.gen202.com/api/v1/jobs/createTask",
    headers=headers,
    json={
      "model": "wan/2-7-text-to-video",
      "input": {
        "prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
        "resolution": "720p",
        "duration": 2
      }
    },
).json()
task_id = created["data"]["taskId"]

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while True:
    task = requests.get(
        "https://api.gen202.com/api/v1/jobs/recordInfo",
        headers=headers,
        params={"taskId": task_id},
    ).json()["data"]
    if task["state"] in ("success", "fail"):
        break
    time.sleep(3)

# 3. Разобрать итог. У неудачи причина в failCode и failMsg
if task["state"] == "fail":
    raise SystemExit("отказ " + task["failCode"] + ": " + task["failMsg"])

# resultJson — строка JSON внутри JSON, поэтому разбор второй
result = json.loads(task["resultJson"])
print(result["resultUrls"][0])
print("списано кредитов:", task["creditsConsumed"])

Пример проходит весь путь: ставит задачу, повторяет запрос состояния раз в 3 секунды, пока задача не закончится, и разбирает ответ до ссылки на файл. Разборов два, потому что поле resultJson — строка JSON внутри JSON. В теле первого запроса стоят обязательные поля модели и значения из её перечислений, поэтому он проходит проверку шлюза как есть. Подставить остаётся только свой ключ. Пример на оболочке разбирает ответ через jq; на Python своего ничего не нужно, кроме requests.

Ответ, когда задача готова

recordInfo.json
{
  "code": 200,
  "msg": "success",
  "data": {
    "taskId": "cm8r4t0vk0001s60p2xq7f9ab",
    "model": "wan/2-7-text-to-video",
    "state": "success",
    "resultJson": "{\"resultUrls\":[\"https://file.example/result.mp4\"]}",
    "failCode": "",
    "failMsg": ""
  }
}

Ссылка на файл лежит в resultJson — это строка, внутри которой ещё один JSON, поэтому разбирать её нужно вторым разбором. Пока задача не завершилась, там пустая строка, а в statewaiting, queuing или generating. В настоящем ответе есть ещё поле creditsConsumed со списанием; здесь его нет намеренно — сумма зависит от запрошенного варианта, а цены живут в каталоге. Все поля записи перечислены в разделе Формат ответа.

wan/2-7-image-to-video

Поля запроса

ПолеТипОбяз.Допустимые значения
model текст обязательное wan/2-7-image-to-video
input объект обязательное Параметры генерации — поля из таблицы ниже

Поля input

ПолеТипОбяз.Допустимые значения
prompt текст обязательное от 1 до 5 000 символов
negative_prompt текст опц. не длиннее 500 символов
resolution значение из списка опц. 720p · 1080p
first_frame_url ссылка опц. одна ссылка на изображение
last_frame_url ссылка опц. одна ссылка на изображение
first_clip_url ссылка опц. одна ссылка на видео
driving_audio_url ссылка опц. одна ссылка на аудио
duration целое число опц. целое от 2 до 15
prompt_extend да или нет опц. true или false
watermark да или нет опц. true или false
seed целое число опц. целое от 0 до 2 147 483 647
nsfw_checker да или нет опц. true или false

Пример вызова

task.sh
# 1. Поставить задачу — в ответе придёт её номер
TASK=$(curl -s https://api.gen202.com/api/v1/jobs/createTask \
  -H "Authorization: Bearer sk-ваш-ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan/2-7-image-to-video",
    "input": {
      "prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
      "resolution": "720p",
      "duration": 2
    }
  }' | jq -r '.data.taskId')

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while true; do
  RECORD=$(curl -s "https://api.gen202.com/api/v1/jobs/recordInfo?taskId=$TASK" \
    -H "Authorization: Bearer sk-ваш-ключ")
  STATE=$(echo "$RECORD" | jq -r '.data.state')
  case "$STATE" in success|fail) break;; esac
  sleep 3
done

# 3. Забрать результат. Ссылка на файл лежит в resultJson — это строка JSON
#    внутри JSON, поэтому её разбирают вторым разбором (fromjson)
echo "$RECORD" | jq -r '.data |
  if .state == "fail"
  then "отказ \(.failCode): \(.failMsg)"
  else .resultJson | fromjson | .resultUrls[0]
  end'
task.py
import json
import time

import requests

headers = {"Authorization": "Bearer sk-ваш-ключ"}

# 1. Поставить задачу — в ответе придёт её номер
created = requests.post(
    "https://api.gen202.com/api/v1/jobs/createTask",
    headers=headers,
    json={
      "model": "wan/2-7-image-to-video",
      "input": {
        "prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
        "resolution": "720p",
        "duration": 2
      }
    },
).json()
task_id = created["data"]["taskId"]

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while True:
    task = requests.get(
        "https://api.gen202.com/api/v1/jobs/recordInfo",
        headers=headers,
        params={"taskId": task_id},
    ).json()["data"]
    if task["state"] in ("success", "fail"):
        break
    time.sleep(3)

# 3. Разобрать итог. У неудачи причина в failCode и failMsg
if task["state"] == "fail":
    raise SystemExit("отказ " + task["failCode"] + ": " + task["failMsg"])

# resultJson — строка JSON внутри JSON, поэтому разбор второй
result = json.loads(task["resultJson"])
print(result["resultUrls"][0])
print("списано кредитов:", task["creditsConsumed"])

Пример проходит весь путь: ставит задачу, повторяет запрос состояния раз в 3 секунды, пока задача не закончится, и разбирает ответ до ссылки на файл. Разборов два, потому что поле resultJson — строка JSON внутри JSON. В теле первого запроса стоят обязательные поля модели и значения из её перечислений, поэтому он проходит проверку шлюза как есть. Подставить остаётся только свой ключ. Пример на оболочке разбирает ответ через jq; на Python своего ничего не нужно, кроме requests.

Ответ, когда задача готова

recordInfo.json
{
  "code": 200,
  "msg": "success",
  "data": {
    "taskId": "cm8r4t0vk0001s60p2xq7f9ab",
    "model": "wan/2-7-image-to-video",
    "state": "success",
    "resultJson": "{\"resultUrls\":[\"https://file.example/result.mp4\"]}",
    "failCode": "",
    "failMsg": ""
  }
}

Ссылка на файл лежит в resultJson — это строка, внутри которой ещё один JSON, поэтому разбирать её нужно вторым разбором. Пока задача не завершилась, там пустая строка, а в statewaiting, queuing или generating. В настоящем ответе есть ещё поле creditsConsumed со списанием; здесь его нет намеренно — сумма зависит от запрошенного варианта, а цены живут в каталоге. Все поля записи перечислены в разделе Формат ответа.

wan/2-7-r2v

Поля запроса

ПолеТипОбяз.Допустимые значения
model текст обязательное wan/2-7-r2v
input объект обязательное Параметры генерации — поля из таблицы ниже

Поля input

ПолеТипОбяз.Допустимые значения
prompt текст обязательное от 1 до 5 000 символов
negative_prompt текст опц. не длиннее 500 символов
resolution значение из списка опц. 720p · 1080p
reference_image список ссылок опц. до 5 ссылок на изображение
reference_video список ссылок опц. до 5 ссылок на видео
first_frame ссылка опц. одна ссылка на изображение
reference_voice ссылка опц. одна ссылка на аудио
aspect_ratio значение из списка опц. 16:9 · 9:16 · 1:1 · 4:3 · 3:4
duration целое число опц. целое от 2 до 10
prompt_extend да или нет опц. true или false
watermark да или нет опц. true или false
seed целое число опц. целое от 0 до 2 147 483 647
nsfw_checker да или нет опц. true или false

Пример вызова

task.sh
# 1. Поставить задачу — в ответе придёт её номер
TASK=$(curl -s https://api.gen202.com/api/v1/jobs/createTask \
  -H "Authorization: Bearer sk-ваш-ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan/2-7-r2v",
    "input": {
      "prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
      "aspect_ratio": "16:9",
      "resolution": "720p",
      "duration": 2
    }
  }' | jq -r '.data.taskId')

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while true; do
  RECORD=$(curl -s "https://api.gen202.com/api/v1/jobs/recordInfo?taskId=$TASK" \
    -H "Authorization: Bearer sk-ваш-ключ")
  STATE=$(echo "$RECORD" | jq -r '.data.state')
  case "$STATE" in success|fail) break;; esac
  sleep 3
done

# 3. Забрать результат. Ссылка на файл лежит в resultJson — это строка JSON
#    внутри JSON, поэтому её разбирают вторым разбором (fromjson)
echo "$RECORD" | jq -r '.data |
  if .state == "fail"
  then "отказ \(.failCode): \(.failMsg)"
  else .resultJson | fromjson | .resultUrls[0]
  end'
task.py
import json
import time

import requests

headers = {"Authorization": "Bearer sk-ваш-ключ"}

# 1. Поставить задачу — в ответе придёт её номер
created = requests.post(
    "https://api.gen202.com/api/v1/jobs/createTask",
    headers=headers,
    json={
      "model": "wan/2-7-r2v",
      "input": {
        "prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
        "aspect_ratio": "16:9",
        "resolution": "720p",
        "duration": 2
      }
    },
).json()
task_id = created["data"]["taskId"]

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while True:
    task = requests.get(
        "https://api.gen202.com/api/v1/jobs/recordInfo",
        headers=headers,
        params={"taskId": task_id},
    ).json()["data"]
    if task["state"] in ("success", "fail"):
        break
    time.sleep(3)

# 3. Разобрать итог. У неудачи причина в failCode и failMsg
if task["state"] == "fail":
    raise SystemExit("отказ " + task["failCode"] + ": " + task["failMsg"])

# resultJson — строка JSON внутри JSON, поэтому разбор второй
result = json.loads(task["resultJson"])
print(result["resultUrls"][0])
print("списано кредитов:", task["creditsConsumed"])

Пример проходит весь путь: ставит задачу, повторяет запрос состояния раз в 3 секунды, пока задача не закончится, и разбирает ответ до ссылки на файл. Разборов два, потому что поле resultJson — строка JSON внутри JSON. В теле первого запроса стоят обязательные поля модели и значения из её перечислений, поэтому он проходит проверку шлюза как есть. Подставить остаётся только свой ключ. Пример на оболочке разбирает ответ через jq; на Python своего ничего не нужно, кроме requests.

Ответ, когда задача готова

recordInfo.json
{
  "code": 200,
  "msg": "success",
  "data": {
    "taskId": "cm8r4t0vk0001s60p2xq7f9ab",
    "model": "wan/2-7-r2v",
    "state": "success",
    "resultJson": "{\"resultUrls\":[\"https://file.example/result.mp4\"]}",
    "failCode": "",
    "failMsg": ""
  }
}

Ссылка на файл лежит в resultJson — это строка, внутри которой ещё один JSON, поэтому разбирать её нужно вторым разбором. Пока задача не завершилась, там пустая строка, а в statewaiting, queuing или generating. В настоящем ответе есть ещё поле creditsConsumed со списанием; здесь его нет намеренно — сумма зависит от запрошенного варианта, а цены живут в каталоге. Все поля записи перечислены в разделе Формат ответа.

wan/2-7-videoedit

Поля запроса

ПолеТипОбяз.Допустимые значения
model текст обязательное wan/2-7-videoedit
input объект обязательное Параметры генерации — поля из таблицы ниже

Поля input

ПолеТипОбяз.Допустимые значения
video_url ссылка обязательное одна ссылка на видео
prompt текст опц. не длиннее 5 000 символов
negative_prompt текст опц. не длиннее 500 символов
resolution значение из списка опц. 720p · 1080p
reference_image список ссылок опц. до 5 ссылок на изображение
aspect_ratio значение из списка опц. 16:9 · 9:16 · 1:1 · 4:3 · 3:4
duration целое число опц. целое от 0 до 10
audio_setting значение из списка опц. auto · origin
prompt_extend да или нет опц. true или false
watermark да или нет опц. true или false
seed целое число опц. целое от 0 до 2 147 483 647
nsfw_checker да или нет опц. true или false

Пример вызова

task.sh
# 1. Поставить задачу — в ответе придёт её номер
TASK=$(curl -s https://api.gen202.com/api/v1/jobs/createTask \
  -H "Authorization: Bearer sk-ваш-ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan/2-7-videoedit",
    "input": {
      "prompt": "Заменить фон на вечернюю городскую улицу, свет тёплый",
      "video_url": "https://example.com/input.mp4",
      "aspect_ratio": "16:9",
      "resolution": "720p",
      "duration": 0
    }
  }' | jq -r '.data.taskId')

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while true; do
  RECORD=$(curl -s "https://api.gen202.com/api/v1/jobs/recordInfo?taskId=$TASK" \
    -H "Authorization: Bearer sk-ваш-ключ")
  STATE=$(echo "$RECORD" | jq -r '.data.state')
  case "$STATE" in success|fail) break;; esac
  sleep 3
done

# 3. Забрать результат. Ссылка на файл лежит в resultJson — это строка JSON
#    внутри JSON, поэтому её разбирают вторым разбором (fromjson)
echo "$RECORD" | jq -r '.data |
  if .state == "fail"
  then "отказ \(.failCode): \(.failMsg)"
  else .resultJson | fromjson | .resultUrls[0]
  end'
task.py
import json
import time

import requests

headers = {"Authorization": "Bearer sk-ваш-ключ"}

# 1. Поставить задачу — в ответе придёт её номер
created = requests.post(
    "https://api.gen202.com/api/v1/jobs/createTask",
    headers=headers,
    json={
      "model": "wan/2-7-videoedit",
      "input": {
        "prompt": "Заменить фон на вечернюю городскую улицу, свет тёплый",
        "video_url": "https://example.com/input.mp4",
        "aspect_ratio": "16:9",
        "resolution": "720p",
        "duration": 0
      }
    },
).json()
task_id = created["data"]["taskId"]

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while True:
    task = requests.get(
        "https://api.gen202.com/api/v1/jobs/recordInfo",
        headers=headers,
        params={"taskId": task_id},
    ).json()["data"]
    if task["state"] in ("success", "fail"):
        break
    time.sleep(3)

# 3. Разобрать итог. У неудачи причина в failCode и failMsg
if task["state"] == "fail":
    raise SystemExit("отказ " + task["failCode"] + ": " + task["failMsg"])

# resultJson — строка JSON внутри JSON, поэтому разбор второй
result = json.loads(task["resultJson"])
print(result["resultUrls"][0])
print("списано кредитов:", task["creditsConsumed"])

Пример проходит весь путь: ставит задачу, повторяет запрос состояния раз в 3 секунды, пока задача не закончится, и разбирает ответ до ссылки на файл. Разборов два, потому что поле resultJson — строка JSON внутри JSON. В теле первого запроса стоят обязательные поля модели и значения из её перечислений, поэтому он проходит проверку шлюза как есть. Заменить в нём нужно два значения: свой ключ и ссылки на исходные файлы — адреса example.com стоят заполнителями, файлов по ним нет. Пример на оболочке разбирает ответ через jq; на Python своего ничего не нужно, кроме requests.

Ответ, когда задача готова

recordInfo.json
{
  "code": 200,
  "msg": "success",
  "data": {
    "taskId": "cm8r4t0vk0001s60p2xq7f9ab",
    "model": "wan/2-7-videoedit",
    "state": "success",
    "resultJson": "{\"resultUrls\":[\"https://file.example/result.mp4\"]}",
    "failCode": "",
    "failMsg": ""
  }
}

Ссылка на файл лежит в resultJson — это строка, внутри которой ещё один JSON, поэтому разбирать её нужно вторым разбором. Пока задача не завершилась, там пустая строка, а в statewaiting, queuing или generating. В настоящем ответе есть ещё поле creditsConsumed со списанием; здесь его нет намеренно — сумма зависит от запрошенного варианта, а цены живут в каталоге. Все поля записи перечислены в разделе Формат ответа.

Общее для всех моделей: Как устроена задача — чем отправить и как дождаться · Формат ответа — где в ответе ссылка на файл · Загрузка своих файлов — как получить ссылку на свою картинку · Отказы — что означает отказ