TL;DR
約 10 分鐘寫完 REST 串接,再把算圖當作需要持久狀態的非同步任務處理。
這篇文字轉影片API教學會用TapVid把一個公開來源URL轉成30秒、16:9的英文動態圖像工作。整合步驟很短:上傳素材、建立工作、輪詢狀態,再要求一個已簽署的下載URL。算繪採非同步執行,不保證能在10分鐘內完成。受控測試花了更長時間,正因如此,下方正式環境程式碼會保存工作ID,並能承受暫時性網路故障。
01
這篇教學裡的 Text-to-Video API 是什麼
「文字到影片」涵蓋了幾個不同的產品。一些API將一個簡短的提示變成一個五秒鐘的電影剪輯。其他人為影象製作動畫,將數字演示者放在腳本上,或組裝素材。TapVid處理不同的工作:將創作者擁有的源材料轉換為結構化的多場景資訊影片。在本教程中,輸入不是模糊的視覺提示。這是公開的TapVid API和MCP頁面,以及定義受眾、持續時間、長寬比、訊息和禁止宣告的製作需求。輸出目標是一個完整的30秒資訊影片,而不是一個孤立的鏡頭。對於機構和小企業來說,基於提示的場景編輯也意味著一個場景可以重新生成,而其餘場景保持完整。
「在10分鐘內構建」這個短語指的是整合四個REST操作,而不是承諾10分鐘的渲染。影片生成是非同步的。建立端點立即返回「202已接受」,狀態可能會長時間保持不變,生成報告完成後可以繼續進行匯出準備。誠實的客戶將請求延遲與生成延遲分開。它顯示工作已被接受,儲存ID,報告當前狀態,並在稍後恢復。它永遠不會僅僅因為瀏覽器選項卡或網路連線消失就用重複的工作替換活動作業。
這是從公共API和MCP頁面製作的實際30秒TapVid輸出,其中包含本教程中使用的製作需求。我們在TapVid Studio中驗證了已完成的五場景專案,播放了完整結果,並下載了此本地MP4。它證明瞭源到完成的影片工作流程和可下載的輸出。它不會追溯性地將早期中斷的REST輪詢變成完整的REST基準。
02
寫程式前先看清介面契約
當前的官方閘道器是「https://api.tapvid.ai/api/public/v1」,每個請求都使用「授權:持有人 YOUR_KEY」透過HTTPS。在TapVid API Keys上建立金鑰,並將其作為`process.env.TAPVID_API_KEY`公開到本地程式中。不要將其硬編碼到部落格、儲存庫、前端捆綁包、螢幕截圖或客戶端應用程式中顯示的腳本中。建立流程接受一個到30個材料ID、一個必需的提示、可選標題、「16:9」或「9:16」、從「30s」到「5m」的持續時間,以及可選的語言。這些是線值,所以拼寫和標點符號很重要。
- 透過HTTPS使用TapVid API的公開REST基礎網址 `https://api.tapvid.ai/api/public/v1`。
- 在伺服器端读取 `process.env.TAPVID_API_KEY`。
- 每次只传 URL 或文件一种。
- 儲存 `materialId`,HTTP 202 后立即儲存 `videoId`。
- 按 `pollAfterSeconds` 轮询并设置可恢复超时。
| 字段 | 限制 | 說明 |
|---|---|---|
| 檔案 | 100 MB | multipart |
| URL | HTTPS,4,096 字符 | 不能是内网 host |
| 素材 | 最多 30 个 | 保存所有 ID |
| Prompt | 12,000 字符 | 基于来源 |
| 画幅 | `16:9` 或 `9:16` | 枚举需准确 |
| 时长 | `30s`、`60s`、`2m`、`3m`、`4m`或`5m` | 枚举需准确 |
03
步驟1:上傳公開URL或檔案
首先只上傳一種來源形式。若為URL,請傳送包含HTTPS `url`的JSON;若為檔案,則透過multipart form data傳送1個不超過100 MB的檔案。URL字串最多可包含4,096個字元,而且不能指向內部主機。這個TapVid MCP/API範例使用 `https://tapvid.ai/api-mcp`,因為該頁面公開、在本次測試中足夠穩定,並且介紹了要解說的服務。上傳回應會傳回`materialId`。請把它視為私有應用程式狀態;它不是方便人閱讀的slug,也不應出現在分析標籤或公開日誌中。
export TAPVID_API_KEY="replace-with-your-local-secret"
curl -X POST https://api.tapvid.ai/api/public/v1/materials \
-H "Authorization: Bearer ${TAPVID_API_KEY}" \
-H "Content-Type: application/json" \
-d '{ "url": "https://tapvid.ai/api-mcp" }'cURL示例設定環境變數只是為了使請求易於跟蹤。在共享機器或CI系統中,使用平臺秘密儲存,而不是將長壽命值匯出到外殼歷史記錄中。JSON響應應包含`materialId`和`type`。用您即將建立的工作記錄儲存兩者。如果上傳失敗`payload_too_large`,更改建立提示無濟於助。修復材料。如果URL被拒絕,請驗證HTTPS、長度、可訪問性、重定向,以及是否允許獲取源。
04
步驟2:建立非同步影片工作
僅在源上傳成功後才建立影片。所需的「使用者提示」最多可以包含12,000個字元,但長度不能替代可測試的製作需求。陳述受眾、來源邊界、預期持續時間、長寬比、語言、強制順序和禁止宣告。該示例明確請求30秒和16:9,而不是依賴自動選擇。響應以HTTP 202的形式到達,其中包含「videoId」、「status」和「createdAt」。官方HTTP語義規範將202定義為接受處理,未完成。這並不意味著影片是可下載或核准的。
curl -X POST https://api.tapvid.ai/api/public/v1/video/create \
-H "Authorization: Bearer ${TAPVID_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"materialIds": ["MATERIAL_ID_FROM_UPLOAD"],
"userPrompt": "Create a concise 30-second English explainer for developers. Stay faithful to the supplied source and do not invent claims.",
"title": "TapVid API developer explainer",
"aspectRatio": "16:9",
"duration": "30s",
"language": "en"
}'

在下一次網路呼叫之前堅持「videoId」。這條生產線是受控測試確定的最重要的生產變化。程式在建立後和第一個狀態響應之前可能會崩潰。如果ID僅存在於記憶體中,應用程式將無法恢復,並可能建立第二個帶薪工作。將ID儲存在您自己的請求ID、材料ID、快速修訂、請求的設定和使用者旁邊。狀態端點期望查詢引數「video_id」,而建立響應使用駱駝大小寫「videoId」。準確複製當前的官方電線名稱,而不是從記憶體中將其歸一化。
05
步驟3:保存、輪詢並要求下載
使用接受的ID進行投票「GET /video/status」,並尊重「pollAfterSeconds」。記錄在案的終端狀態是「已完成」和「失敗」;中間狀態包括「排隊」和「正在執行」。進度是0到1的分數,而不是剩餘的估計時間。完成後,如果「creditsUsed」仍然為空,請再次投票,因為結算可以跟隨終端更新。然後請求帶有解析度、水印和字幕選項的「GET /video/download」。簽名的「downloadUrl」將在大約一小時後過期。儲存持久的影片ID,而不是簽名的URL,並在使用者再次需要時重新整理URL。
import { writeFile } from 'node:fs/promises'
const apiKey = process.env.TAPVID_API_KEY
if (!apiKey) throw new Error('TAPVID_API_KEY is required')
const base = 'https://api.tapvid.ai/api/public/v1'
const headers = { Authorization: `Bearer ${apiKey}` }
async function request(path, init = {}, { attempts = 1 } = {}) {
let lastError
for (let attempt = 1; attempt <= attempts; attempt += 1) {
try {
const response = await fetch(`${base}${path}`, {
...init,
headers: { ...headers, ...init.headers },
})
const data = await response.json()
if (response.ok) return { response, data }
if (response.status !== 429 && response.status < 500) {
const error = new Error(`HTTP ${response.status}: ${data.code ?? 'unknown'}`)
error.retryable = false
throw error
}
lastError = new Error(`retryable HTTP ${response.status}`)
} catch (error) {
if (error?.retryable === false) throw error
lastError = error
}
if (attempt === attempts) break
await new Promise((resolve) => setTimeout(resolve, attempt * 1000))
}
throw lastError
}
const { data: material } = await request('/materials', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: 'https://tapvid.ai/api-mcp' }),
})
const { response: createResponse, data: video } = await request('/video/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
materialIds: [material.materialId],
userPrompt: 'Create a concise 30-second English explainer for developers. Stay faithful to the source.',
title: 'TapVid API developer explainer',
aspectRatio: '16:9',
duration: '30s',
language: 'en',
}),
})
if (createResponse.status !== 202) throw new Error('Expected 202 Accepted')
// Persist before the next network call. A lost poll must not cause a duplicate create.
await writeFile('.tapvid-job.json', JSON.stringify({ videoId: video.videoId }))
let status
for (let poll = 0; poll < 180; poll += 1) {
const result = await request(
`/video/status?video_id=${encodeURIComponent(video.videoId)}`,
{},
{ attempts: 4 },
)
status = result.data
if (status.status === 'completed' || status.status === 'failed') break
await new Promise((resolve) =>
setTimeout(resolve, Math.max(status.pollAfterSeconds ?? 5, 5) * 1000),
)
}
if (!status) throw new Error('No status received')
if (status.status === 'failed') throw new Error(status.error?.code ?? 'generation_failed')
if (status.status !== 'completed') throw new Error('Polling timeout; resume with the saved videoId')
const query = new URLSearchParams({
video_id: video.videoId,
resolution: '1080P',
watermark: 'true',
subtitle: 'false',
})
const { data: download } = await request(`/video/download?${query}`, {}, { attempts: 4 })
console.log({ status: download.status, expiresAt: download.expiresAt })Node.js示例在有界重試中包裝讀取,並在輪詢前將接受的影片ID寫入磁碟。在真實服務中,用資料庫行替換本地JSON檔案,並使用佇列工作。幫助程式重試網路錯誤、HTTP 429和伺服器錯誤。它不會重試正常的400或401,好像時間會修復錯誤的引數或憑據。180票的上限為工人創造了一個明確的終端條件。如果達到該上限,程式將保留ID並報告可恢復超時,而不是提交另一個建立請求。
06
真實 REST 測試暴露了什麼
2026年8月7日的REST測試使用了與MCP測試相同的源、提示、30秒持續時間、16:9比例和英語。材料上傳在大約1.4秒內返回HTTP 200。建立返回HTTP 202,並在大約0.3秒內排隊。第一個執行狀態出現在大約81秒。在大約331秒時,該作業仍然報告以50%的執行。然後,在建立TLS連線之前,本地節點程式收到了「ECONNRESET」。那是投票客戶端失敗,而不是記錄在案的影片作業失敗。



第一個測試腳本僅將私有影片ID保留在程式記憶體中,因此在程式退出後,它無法恢復已接受的工作。沒有建立重複的作業。在MCP和REST執行中,帳戶使用計數器已從90個學分增加到180個學分,與每次建立90個學分和核准的總上限相匹配。這就是為什麼最終樣本在輪詢前保留ID,並給出有界重試的GET請求。這也是為什麼本文沒有聲稱REST執行在10分鐘內生成可下載檔案。證據支援快速請求接受,而不是固定的渲染時間承諾。
07
處理錯誤,但不要重複消耗
根據其含義處理錯誤。「未經授權」呼叫檢查或撤銷金鑰。「invalid_request」呼叫更正欄位。「payload_too_large」呼叫減少材料。「insufficient_credits」呼籲在更改限制之前停止並獲得授權。「rate_limited」呼籲尊重「重試後」。「內部_錯誤」和傳輸失敗可能會收到有界重試。下載時的「無效狀態」通常意味著生成或匯出沒有準備好。五秒內重複建立可能會被拒絕,但對於稍後發生的崩潰,這不是完全的冪等策略。
| 条件 | 重試? | 操作 |
|---|---|---|
| unauthorized | 否 | 修复金鑰 |
| invalid_request | 否 | 修复 body |
| insufficient_credits | 否 | 停止 |
| rate_limited | 是 | 遵守 Retry-After |
| 網路或服务端 | 有限 | 重試读取 |
| create 后丢响应 | 不要重建 | 按 ID 恢复 |
08
可靠生產串接檢查清單
一個可靠的整合需要的不僅僅是一個有效的快樂路徑片段。伺服器端儲存機密。原子上堅持接受的ID。記錄狀態轉換、請求延遲、生成延遲、點數和安全錯誤程式。新增最大投票視窗和可恢復狀態。單獨的源上傳許可權、生成-支出許可權、匯出許可權和釋出許可權。重新整理已簽名的URL,而不是將它們儲存為永久資產。測試429和網路重置。向使用者顯示最後確認的狀態。最後,在外部釋出之前,檢視生成的解說影片的來源保真度、實際持續時間、場景順序、字幕可讀性、音訊、權利和品牌。
- 密钥只在伺服器端。
- 原子儲存 ID。
- 分开記錄时延。
- 遵守间隔和超时。
- 区分读取和写入。
- 刷新签名 URL。
- 不含 secret 地記錄 credits。
- 由人审核并發布。
09
用 API 做完整解說影片,而不是隨機短片
當工作是將已核准的內容轉換為具有多個場景、旁白、動作和可下載輸出的連貫解說影片時,請使用此文字到影片API。不要將其描述為原始模型基準,也不要承諾電影提示到剪輯引擎的視覺行為。如果您想透過對話來探索提示,同伴Claude和TapVid MCP教程透過MCP顯示相同的來源和製作需求。如果您想要持久的應用程式控制,請在發貨前保留REST流程,並檢視當前的TapVid API和MCP概述。
10
常見問題
一定能在 10 分钟完成影片吗?
不。四操作整合可以快速構建,但生成和匯出是非同步的。受控的MCP執行大約需要28分鐘才能完成,REST輪詢器在終端狀態之前失去了連線。
HTTP 202 是什么意思?
這意味著建立請求已被接受進行非同步處理。堅持返回的影片ID和投票狀態;這並不意味著影片是完整的。
多久轮询一次?
使用狀態返回的pollAfterSeconds值。為瞬態故障新增有界重試,以及可以從儲存的影片ID恢復的總體超時。
下載 URL 有效多久?
当前文件说明约一小时。
能去除水印吗?
默认保留水印,去除需要有效订阅。




