新模型DeepSeek V4.1 Flash 与 MiniMax M3.1 Flash 已上线
Inferway
登录注册
跳转至正文
Interactions API

通过 Interactions API 接入视频模型

视频示例

本示例为所选模型 inferway/minimax-h3-768p 的 Interactions API 流程:后台创建、轮询、下载。

前置条件:bash、curl、jq;Python 示例只用标准库;TypeScript 示例需要带 fetch 的 Node。先设置 INFERWAY_API_KEY。上次没等完的任务,带上同一个 INFERWAY_IDEMPOTENCY_KEY 重跑就能接着等;最长等多久由 INFERWAY_MAX_WAIT_SECONDS 决定,默认 7200 秒(2 小时)。

  1. 创建:向 /interactions 发送顶层 JSON 体(op=create,mode=background,duration_seconds 与 prompt),并带上持久的 Idempotency-Key。
  2. 轮询:拿返回的 interaction_id 用 op=get 每 15 秒查一次。只要任务还在排队或生成,就一直等,每次会显示前面还有几单、或者已经生成了多少秒;遇到 failed、cancelled、expired、recovery_required 就停下报错。
  3. 下载:成功且 result.download_url 非空后,用一次不带 API 头部的独立 HTTPS 请求下载,不跟随重定向。

等到上限时,任务多半还在排队或生成,并没有取消,成片后才扣费,所以不要重新下单。记下 interaction ID 和 Idempotency-Key,用 INFERWAY_IDEMPOTENCY_KEY=<key> 重跑,接回的还是原来那个任务。下载链接过期了,再调一次 get 就能拿到新的。

隐私层级:retained_7d。视频产物按所选模型的策略保存,并非聊天那样的零保留。

#!/usr/bin/env bash
# Inferway video generation via the Interactions API.
# Prerequisites: bash, curl, jq.
# Required env: INFERWAY_API_KEY.
# Optional env: INFERWAY_IDEMPOTENCY_KEY — resume a previous create with this
# key; otherwise a fresh key is generated.
# Optional env: INFERWAY_MAX_WAIT_SECONDS — total seconds to wait on a
# queued/running task (default 7200 = 2 hours, positive integer).
# On success the video is saved to ./inferway-video.mp4.
set -u

API_BASE="https://api.inferway.ai/v1"
MODEL="inferway/minimax-h3-768p"
PROMPT="A drone shot along a coastline at dawn."
DURATION_SECONDS=5
SAVE_PATH=inferway-video.mp4

workdir=$(mktemp -d) || exit 1
cleanup() { rm -rf "$workdir"; }
trap cleanup EXIT HUP INT TERM

if [ -z "${INFERWAY_API_KEY:-}" ]; then
  echo "error: INFERWAY_API_KEY is required" >&2
  exit 1
fi

# The total wait budget, validated BEFORE the first request: a typo must not
# turn into a run that waits on the default budget.
max_wait_raw=$(printf '%s' "${INFERWAY_MAX_WAIT_SECONDS-}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
max_wait=7200
if [ -n "$max_wait_raw" ]; then
  case "$max_wait_raw" in
    *[!0-9]*)
      echo "error: INFERWAY_MAX_WAIT_SECONDS must be a positive integer" >&2
      exit 1
      ;;
  esac
  if [ -z "$(printf '%s' "$max_wait_raw" | sed 's/^0*//')" ]; then
    echo "error: INFERWAY_MAX_WAIT_SECONDS must be a positive integer" >&2
    exit 1
  fi
  max_wait=$max_wait_raw
fi

idempotency_key="${INFERWAY_IDEMPOTENCY_KEY:-}"
if [ -z "$idempotency_key" ]; then
  if command -v uuidgen >/dev/null 2>&1; then
    idempotency_key=$(uuidgen | tr 'A-Z' 'a-z') || exit 1
  elif command -v uuid >/dev/null 2>&1; then
    idempotency_key=$(uuid | tr -d '-') || exit 1
  else
    idempotency_key="iv-$(date +%s%N)-$$"
  fi
fi
echo "Idempotency-Key: $idempotency_key"

interactions_url="$API_BASE/interactions"
response_headers=$workdir/headers
response_body=$workdir/body
create_body_file=$workdir/create-body.json

jq -cn \
  --arg model "$MODEL" \
  --argjson durationSeconds "$DURATION_SECONDS" \
  --arg prompt "$PROMPT" \
  '{op: "create", model: $model, mode: "background", duration_seconds: $durationSeconds, prompt: $prompt}' \
  > "$create_body_file" || exit 1

attempt=0
while :; do
  attempt=$((attempt + 1))
  rc=0
  curl -sS --connect-timeout 30 --max-time 30 \
    -D "$response_headers" \
    --data-binary @"$create_body_file" \
    -H "Authorization: Bearer $INFERWAY_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $idempotency_key" \
    "$interactions_url" > "$response_body" 2> "$workdir/curl-err" || rc=$?
  if [ "$rc" -ne 0 ]; then
    if [ "$attempt" -ge 5 ]; then
      echo "error: create request failed on the 5th attempt: $(cat "$workdir/curl-err")" >&2
      exit 1
    fi
    sleep $((1 << (attempt - 1)))
    continue
  fi
  status=$(head -n 1 "$response_headers" | tr -d '\r' | awk '{print $2}')
  case "$status" in
    200|202)
      id=$(jq -er '.id | select(type == "string" and length > 0)' "$response_body" 2>/dev/null) || {
        echo "error: accepted response has no usable id" >&2
        exit 1
      }
      echo "task id: $id"
      break
      ;;
    429|5[0-9][0-9])
      if jq -e '((.error? // {}) | (type == "object") and .retryable == false) or (.retryable? == false)' "$response_body" >/dev/null 2>&1; then
        echo "error: server reported a non-retryable failure (status $status)" >&2
        jq -c . "$response_body" >&2 || true
        exit 1
      fi
      if [ "$attempt" -ge 5 ]; then
        echo "error: create request failed with status $status on the 5th attempt" >&2
        exit 1
      fi
      retry_after=$(sed -n '1,/^$/s/^Retry-After:[[:space:]]*//Ip' "$response_headers" | head -n 1 | tr -d '\r' | tr -d '[:space:]')
      if printf '%s' "${retry_after:-}" | grep -Eq '^[0-9]+$'; then
        if [ "$retry_after" -gt 30 ]; then
          echo "error: server asked to wait $retry_after s (ceiling 30 s); resume later with:" >&2
          echo "  INFERWAY_IDEMPOTENCY_KEY=$idempotency_key" >&2
          exit 1
        else
          sleep "$retry_after"
        fi
      else
        sleep $((1 << (attempt - 1)))
      fi
      continue
      ;;
    *)
      if jq -e '((.error? // {}) | (type == "object") and .retryable == false) or (.retryable? == false)' "$response_body" >/dev/null 2>&1; then
        echo "error: server reported a non-retryable failure (status $status)" >&2
      else
        echo "error: create request failed with status $status" >&2
      fi
      jq -c . "$response_body" >&2 || true
      exit 1
      ;;
  esac
done

# --- Wait on the queue: poll while the task is queued or running ------------
# Video is generated serially, so a task's queue wait is the sum of the
# generation seconds estimated from recent observed runs of every order ahead
# of it (see docs/interactions-queue-contract-v1.md,
# queue.estimated_wait_seconds). A fixed poll count therefore gives up on a
# perfectly healthy task, and a customer shown an error re-submits the same job
# and is charged twice. So this loop waits for as long as the task says queued
# or running, reports the queue position every round, and is bounded only in
# total by INFERWAY_MAX_WAIT_SECONDS (default 7200 s = 2 h). Failures of the
# status check itself are retried too; only a non-429 4xx answer stops the run
# at once.
download_url=""
waited=0
transient=0
poll_delay=15

stop_waiting() {
  echo "Stopped waiting. The task was NOT cancelled: it stays in the queue and is charged only if it succeeds." >&2
  echo "Do not submit it again. To resume, re-run this script with:" >&2
  echo "  INFERWAY_IDEMPOTENCY_KEY=$idempotency_key" >&2
  exit 1
}

poll_delay_for() {
  # 429 and 503 may name their own delay: honor a whole number of seconds up
  # to 60; anything else (fraction, negative, > 60, absent) waits 15 s.
  if [ "$1" != "429" ] && [ "$1" != "503" ]; then
    echo 15
    return
  fi
  retry_after=$(sed -n '1,/^$/s/^Retry-After:[[:space:]]*//Ip' "$response_headers" | head -n 1 | tr -d '\r' | tr -d '[:space:]')
  if printf '%s' "${retry_after:-}" | grep -Eq '^[0-9]+$' && [ "$retry_after" -le 60 ]; then
    echo "$retry_after"
  else
    echo 15
  fi
}

while :; do
  : > "$response_headers"
  rc=0
  curl -sS --connect-timeout 30 --max-time 30 \
    -D "$response_headers" \
    --data-binary "$(jq -cn --arg id "$id" '{op: "get", interaction_id: $id}')" \
    -H "Authorization: Bearer $INFERWAY_API_KEY" \
    -H "Content-Type: application/json" \
    "$interactions_url" > "$response_body" 2> "$workdir/curl-err" || rc=$?
  status=$(head -n 1 "$response_headers" | tr -d '\r' | awk '{print $2}')
  poll_delay=15
  hard_stop=""
  if [ "$rc" -ne 0 ]; then
    # Network error or timeout: the next round gets to try again.
    transient=$((transient + 1))
  elif [[ ! "$status" =~ ^2[0-9][0-9]$ ]]; then
    case "$status" in
      429|5[0-9][0-9])
        transient=$((transient + 1))
        poll_delay=$(poll_delay_for "$status")
        ;;
      *)
        hard_stop="status=$status"
        ;;
    esac
  elif ! jq -e . "$response_body" >/dev/null 2>&1 || [ -z "$(jq -r '.state // empty' "$response_body")" ]; then
    transient=$((transient + 1))
  else
    transient=0
    state=$(jq -r '.state' "$response_body")
    case "$state" in
      failed|cancelled|expired|recovery_required)
        echo "error: task $id ended in state '$state'" >&2
        jq -r 'if ((.failure | type) == "object") and (.failure | has("charged")) then "charged: \(.failure.charged)" else empty end' "$response_body" >&2
        exit 1
        ;;
      queued)
        jq -r 'if ((.queue | type) == "object")
              and ((.queue.position | type) == "number")
              and (.queue.position == (.queue.position | floor))
              and (.queue.position >= 1)
              and ((.queue.estimated_wait_seconds | type) == "number")
              and (.queue.estimated_wait_seconds >= 0)
            then "queued: position \(.queue.position), about \([1, ((.queue.estimated_wait_seconds / 60) | ceil)] | max) min to start"
            else "queued" end' "$response_body"
        ;;
      running)
        jq -r 'if ((.progress | type) == "object")
              and ((.progress.elapsed_seconds | type) == "number")
              and (.progress.elapsed_seconds >= 0)
              and ((.progress.typical_seconds | type) == "number")
              and (.progress.typical_seconds >= 1)
            then "running: \(.progress.elapsed_seconds)s elapsed (typically \(.progress.typical_seconds)s)"
            else "running" end' "$response_body"
        ;;
      succeeded)
        candidate=$(jq -er '.result? | select(type == "object") | .download_url? | select(type == "string" and length > 0)' "$response_body" 2>/dev/null) || candidate=""
        if [ -n "$candidate" ]; then
          download_url="$candidate"
          break
        fi
        ;;
    esac
  fi

  if [ -n "$hard_stop" ]; then
    echo "polling stopped ($hard_stop); the job may still be running." >&2
    echo "resume later with:" >&2
    echo "  task id: $id" >&2
    echo "  INFERWAY_IDEMPOTENCY_KEY=$idempotency_key" >&2
    exit 1
  fi

  if [ "$transient" -ge 8 ]; then
    echo "error: the status check failed 8 times in a row" >&2
    stop_waiting
  fi

  if [ "$((waited + poll_delay))" -gt "$max_wait" ]; then
    stop_waiting
  fi
  sleep "$poll_delay"
  waited=$((waited + poll_delay))
done

case "$download_url" in
  https://*) ;;
  *)
    echo "error: refusing to download a non-HTTPS URL" >&2
    exit 1
    ;;
esac

# --- Download: separate request, no API headers, no redirects ---------------
rc=0
curl -sS --connect-timeout 30 --max-time 30 \
  -D "$response_headers" -o "$workdir/video" \
  "$download_url" || rc=$?
if [ "$rc" -ne 0 ]; then
  echo "error: download failed (network/timeout)" >&2
  exit 1
fi
status=$(head -n 1 "$response_headers" | tr -d '\r' | awk '{print $2}')
case "$status" in
  2??) ;;
  3??)
    echo "error: download redirected (status $status); refusing to follow redirects" >&2
    exit 1
    ;;
  *)
    echo "error: download failed with status $status" >&2
    exit 1
    ;;
esac

mv "$workdir/video" "$SAVE_PATH" || exit 1
echo "SAVED $SAVE_PATH"

用图片生成视频

两种用法:给一张首帧(可以再给一张尾帧),成片从首帧出发、落到尾帧;或者给 1–3 张参考图,成片参考这些图里的人物或场景。两者不能混用。用哪一种由你填了哪些图决定——不用单独传模式,mode 仍然是 background。

  1. 申请上传凭证:向 /interactions 发送 op=prepare_upload,写明 purpose(first_frame、last_frame 或 ref_image)、content_type 和 content_length(图片字节数),换回 upload_url——它是相对路径,拼到自己的 API 根地址后面用。每张图申请一次;凭证 15 分钟内有效,只能用一次。
  2. 上传原图:把图片的原始字节 PUT 到 upload_url,带上与申请时相同的 Authorization: Bearer 和你声明的 Content-Type。请求体就是原始字节,不是 multipart,也不是 base64;成功后返回 upload_id。
  3. 创建任务:把 upload_id 填进 first_frame(给了尾帧就再填 last_frame),或者填进 ref_image_1 到 ref_image_3,其余参数与文生视频完全相同。拿到 interaction id 之后,轮询与下载沿用上一节的示例。

图片限 JPEG、PNG、WebP,单张不超过 10 MiB。

计费与文生视频相同:按所选时长计价,下单时冻结额度,成片后才扣,失败不收费。

一个模型此刻开放哪些模式,看 GET /v1/models 里它的 available_modes(始终以 t2v 开头);不在列表里的模式会在下单时拒绝,发生在冻结额度之前,不产生费用。

前置条件与上一节相同:INFERWAY_API_KEY,外加 bash/curl/jq、Python 标准库,或带 fetch 的 Node。示例到拿到 interaction id 为止;之后的轮询与上一节相同:每 15 秒查一次,排队时会显示前面还有几单,最长等 2 小时,可用 INFERWAY_MAX_WAIT_SECONDS 调整。

示例代码加载中…