The short version
약 10분에 REST 연동을 만들고 렌더링은 상태를 저장하는 비동기 작업으로 처리합니다.
공개 URL을 30초 16:9 모션 그래픽 작업으로 만듭니다. upload, create, status, download 연동 코드는 짧지만 렌더링이 10분 안에 끝난다는 보장은 없습니다. 실제 테스트가 더 오래 걸렸기 때문에 ID 저장과 네트워크 재시도를 포함합니다.
Review TapVid API and MCP access
01
이 튜토리얼의 text-to-video API 의미
Text to video는 prompt clip, image animation, avatar 등 여러 의미입니다. TapVid는 기존 자료를 여러 장면의 구조화된 정보 영상으로 바꿉니다. 공개 페이지와 brief가 사실, 대상, 길이, 금지 항목을 정합니다. 나머지를 유지하면서 한 장면만 다시 생성할 수 있습니다.
“10분”은 네 REST 작업의 연동 시간이지 render 보장이 아닙니다. Create는 `202 Accepted`를 반환하고 status와 export는 계속됩니다. ID를 저장하고 연결이 끊겨도 duplicate create를 하지 않아야 합니다.
삽입된 영상은 별도의 검증된 워크플로에서 완성된 TapVid 사례입니다. REST 테스트는 terminal 상태를 확인하기 전에 poller 연결이 끊겼으므로 그 결과라고 표시하지 않습니다.
02
코드 전에 계약 확인
Gateway는 `https://api.tapvid.ai/api/public/v1`입니다. API Keys에서 Bearer key를 만들고 server의 `process.env.TAPVID_API_KEY`에서 읽습니다. frontend나 repository에 넣지 않습니다.
- HTTPS REST base를 씁니다.
- server에서 `process.env.TAPVID_API_KEY`를 읽습니다.
- URL이나 file 하나만 보냅니다.
- `materialId`와 202 뒤 `videoId`를 저장합니다.
- `pollAfterSeconds`와 재개 timeout을 씁니다.
| 항목 | 제한 | 주의 |
|---|---|---|
| File | 100 MB | multipart |
| URL | HTTPS, 4,096자 | internal host 불가 |
| Materials | 30 | ID 저장 |
| Prompt | 12,000자 | source 기반 |
| Ratio | `16:9` or `9:16` | enum 정확히 |
| Duration | `30s` to `5m` | enum 정확히 |
03
1단계: URL 또는 파일 upload
URL은 HTTPS JSON, file은 100 MB 이하 multipart입니다. URL은 4,096자까지입니다. 반환된 `materialId`를 private state로 저장합니다.
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는 secret store를 씁니다. `payload_too_large`는 material을 수정하고 URL 거부는 HTTPS, 길이, 접근성을 확인합니다.
04
2단계: 비동기 video job create
Upload 성공 후에만 create합니다. 대상, source, 30초, 16:9, 영어, 순서, 금지 claim을 넣습니다. HTTP 202와 `videoId`는 수락이지 완료가 아닙니다.
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`를 저장합니다. process가 종료돼도 재개하고 이중 create를 막습니다. response는 `videoId`, query는 `video_id`입니다.
05
3단계: 저장, poll, download
`pollAfterSeconds`에 따라 status를 봅니다. progress는 남은 시간이 아닙니다. completed 뒤 download를 요청하고 약 한 시간짜리 signed 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 예시는 ID를 저장하고 안전한 read만 제한 retry합니다. 운영에서는 JSON 대신 database를 씁니다. 400과 401은 고치고 429, server, network는 backoff합니다.
06
실제 REST 테스트 결과
테스트는 upload 약 1.4초, create 약 0.3초에 HTTP 202였습니다. 81초에 running 50%, 331초에도 같았고 poller가 TLS 전 `ECONNRESET`으로 종료했습니다.

첫 script는 ID를 memory에만 둬 재개하지 못했습니다. duplicate는 만들지 않았습니다. MCP와 REST 합계는 180 credits, create당 90이었습니다. 10분 완성이라고 주장하지 않습니다.
07
중복 비용 없이 오류 처리
unauthorized는 key, invalid_request는 body 수정, insufficient_credits는 중단, rate_limited는 Retry-After, network는 safe read retry입니다. 응답 손실 뒤 create를 반복하지 않습니다.
| 조건 | Retry | 대응 |
|---|---|---|
| unauthorized | No | key 수정 |
| invalid_request | No | body 수정 |
| insufficient_credits | No | 중단 |
| rate_limited | Yes | Retry-After |
| network/server | 제한 | read retry |
| create 뒤 응답 손실 | 재생성 금지 | ID 재개 |
08
운영 체크리스트
운영에는 server secret, atomic ID, 분리된 latency, 재개 timeout, 안전한 log, 갱신 URL, 사람의 사실, 길이, 권리, brand 검수가 필요합니다.
- key는 server에만.
- ID atomic 저장.
- latency 분리.
- interval과 timeout.
- read/write 구분.
- signed URL 갱신.
- secret 없이 credits 기록.
- 사람이 검수와 공개.
09
무작위 clip이 아닌 완성 explainer에 사용
승인된 content로 일관된 explainer를 만드는 용도입니다. 대화는 Claude MCP tutorial, 영구 제어는 REST와 API/MCP overview를 사용합니다.
10
Frequently asked questions
항상 10분에 완성되나요?
아닙니다. 연동은 짧지만 render와 export는 비동기입니다. MCP 테스트는 completed까지 약 28분이었습니다.
HTTP 202는 무엇인가요?
Job 수락입니다. videoId를 저장하고 status를 poll합니다.
얼마나 자주 poll하나요?
pollAfterSeconds, 제한 retry, 재개 timeout을 사용합니다.
download URL 기한은?
현재 문서는 약 한 시간이라고 설명합니다.
watermark를 없앨 수 있나요?
default는 on이고 제거에는 active subscription이 필요합니다.
Turn them into a clear, publishable video
Keep reading
Related stories

Claude와 TapVid MCP로 모션 그래픽 영상을 만드는 법
공개 URL, 실제 다섯 도구, 30초 브리프, 솔직한 운영 테스트를 담은 실전 가이드입니다.
Aug 7, 2026

Claude 영상 생성: Seedance 2.5와 TapVid 중 무엇을 쓸까?
Claude만으로는 영상을 렌더링할 수 없습니다. 실측 결과와 프롬프트를 바탕으로 Seedance 2.5와 TapVid의 활용 기준을 알아보세요.
Aug 8, 2026

텍스트 애니메이터 기법: 시각적 잡음 없이 더 빠른 모션
읽기 쉽고 임팩트 있는 영상 메시지가 필요한 팀을 위한 실용적인 텍스트 애니메이터 프레임워크.
Apr 16, 2026

