본문으로 건너뛰기

Agent API

kvidAI Agent API 로 kvid.ai 에디터를 구동하는 동일한 AI 어시스턴트를 직접 코드에서 사용할 수 있습니다. 자연어 지시문 + Remotion composition 을 보내면 agent 가 알아서 결정합니다 — 아이템 추가/수정, 이미지 생성, 비디오 생성, 멀티-씬 long-video 플랜 작성, 위 모든 것을 묶어 완성된 composition 으로 stitch.

응답은 Server-Sent Events (SSE) 로 스트리밍되므로 agent 가 작업 중인 동안에도 진행 상황을 사용자에게 보여줄 수 있습니다.

🎯 서비스 개요

Agent 가 할 수 있는 것

  • Short video 편집 — 텍스트 오버레이 추가, 배경 교체, 단일 이미지/비디오 아이템 재생성 ("흰색 쇼파를 가죽 검은색으로 변경"). 새 composition diff 반환.
  • Long video 플래닝 — 주제만 주면 ("아이폰 17 칩 8씬으로 설명") agent 가 먼저 씬 플랜을 emit 한 다음 모든 생성 작업 (이미지/비디오 + TTS 나레이션) 을 동시 미디어 큐로 실행. 진행 이벤트가 작업 중 스트리밍됨.
  • 실패 후 이어가기 — 렌더 중 크레딧 부족, 씬 생성 실패, 사용자 탭 종료 등으로 중단된 경우 /api/agent/resume 으로 미완성 씬만 재실행.
  • 씬별 재시도 — 일시적 이미지/비디오 provider 실패 시 한 씬만 surgical 재시도. 전체 플랜 재실행 X.

핵심 개념

  • projectId — 장시간 작업은 프로젝트에 묶입니다 (Project 관리 API 참조). Agent 가 해당 프로젝트의 composition 을 읽고 씁니다.
  • composition — 요청 body 에 포함해서 agent 가 추가 round trip 없이 현재 상태를 추론. Agent 는 변경된 composition snapshot 을 checkpoint / done 이벤트로 반환.
  • templateId — 선택적 Strapi video-template ID. 음성, 톤, 색상 팔레트 등 결정. 미지정 시 system_default fallback → locale 기반 default.
  • localeen / ko / es. 최종 사용자 노출 메시지 언어 결정 + template 미선택 시 나레이션 기본 voice 결정.

인증

Project 관리 API 와 동일:

  • api-key 헤더 — kvidAI API 키
  • 요청 body 의 email 필드 — Agent 가 대신 실행할 사용자

API 키: kvid.ai/dashboard/api-keys

매 agent 실행은 크레딧을 미리 예약합니다 (Claude 토큰 + 다운스트림 미디어 생성). 잔액 부족 시 작업이 시작되기 전에 402 INSUFFICIENT_CREDIT 응답. 요율: Pricing 참조.

📡 API 엔드포인트

기본 정보

Base URL:       https://api.kvid.ai
Authentication: api-key 헤더
Content-Type: application/json
Response style: 성공 시 text/event-stream (SSE), 사전 거절 시 application/json
MethodPath용도
POST/api/agentAgent 실행 (short edit 또는 long-video 플랜)
POST/api/agent/resume부분 완료된 long-video 작업 이어가기
POST/api/agent/retry-scene단일 실패 씬 재시도
GET/api/agent/check-resume프로젝트에 미완료 작업 있는지 확인

1. Agent 실행

POST /api/agent

Request body

필드타입필수설명
messagestring자연어 지시문.
compositionobject현재 composition. Agent 가 이걸 기반으로 추론.
projectIdnumber작업이 묶일 프로젝트.
emailstring소유자.
apiKeystring사용자별 kvidAI API 키. (헤더가 아니라 body 에 — 내부적으로 AI gateway 에 forwarding 되기 때문.)
localestring아니오en (default) / ko / es.
templateIdstring아니오적용할 Strapi 템플릿. 미지정 시 system_default fallback.
attachedFilesarray아니오이미지 / 비디오 / 오디오 / PDF / 텍스트 업로드. 각각: { name, type, mimeType, size, base64 }.
chatHistoryarray아니오클라이언트에서 압축한 이전 메시지 (긴 세션에서 토큰 절약).
selectedItemContextobject아니오UI 에서 사용자가 이미지/비디오 1개 선택 중이면 { itemId, type, assetId, remoteUrl?, sourceImageUrl?, from, durationInFrames }. Agent 가 그 아이템 한정으로 편집.
autoSaveboolean아니오기본 true. done 후 직접 composition 을 PATCH 하고 싶으면 false.

Server-Sent Events

각 이벤트는 event: <이름>\ndata: <json>\n\n 라인. 처리해야 할 이벤트 이름들:

이벤트발생 시점Payload
tool_startAgent 가 sub-tool 호출 직전{ toolUseId, toolName }
tool_endSub-tool 완료 후{ toolUseId, toolName, success, error? }
plan_readyLong-video 플래닝 완료{ jobId, totalScenes, estimatedMinutes }
scene_start씬 생성 시작{ sceneId, sceneIndex }
scene_complete씬 성공 완료{ sceneId, voiceError? }
scene_failed씬 실패{ sceneId, error }
checkpointLong video 중 주기적 composition snapshot{ composition }
insufficient_credit실행 중 크레딧 부족{ completedScenes, totalScenes, remainingCredit, estPerScene }
template_warning템플릿 config 검증 경고{ severity, field, message }
doneAgent 완료{ success, data: { message, messageKey?, messageParams?, composition, toolResults[], projectId, resumeJobId?, remainingScenes?, totalScenes? } }
error치명적 에러{ error }

donemessageKey / messageParams 는 사용자가 UI locale 을 변경했을 때 agent 를 재실행하지 않고 메시지만 클라이언트에서 재번역할 수 있게 해줍니다. i18n 카탈로그의 키 (Agent.longVideo.done.*, Agent.longVideo.resume.done.* …) 와 매칭.

사전 거절 (non-SSE)

스트림 시작 전에 요청이 거절되면 (인증 실패, 크레딧 부족, 동시 실행 한도) 응답이 정상 JSON body 로 4xx status + { success: false, error: "<CODE>", message: "...", data: { ... } }. body 를 stream 으로 읽기 전에 response.headers['content-type'] 검사 필수.

Python (httpx + SSE)

import httpx
import json

API_KEY = "YOUR_API_KEY"
EMAIL = "[email protected]"

body = {
"message": "아이폰의 새 칩에 대한 30초 설명 영상을 8씬으로 만들어줘.",
"composition": empty_composition, # Project 관리 API 참조
"projectId": 1234,
"email": EMAIL,
"apiKey": API_KEY,
"locale": "ko",
"templateId": "sod",
}

with httpx.stream(
"POST",
"https://api.kvid.ai/api/agent",
headers={"api-key": API_KEY, "Content-Type": "application/json"},
json=body,
timeout=None,
) as resp:
if "text/event-stream" not in resp.headers.get("content-type", ""):
print("rejected:", resp.json())
raise SystemExit(1)

event_name = None
for line in resp.iter_lines():
if not line:
event_name = None
continue
if line.startswith("event: "):
event_name = line[7:]
elif line.startswith("data: ") and event_name:
payload = json.loads(line[6:])
print(event_name, payload)
if event_name == "done":
final_composition = payload["data"]["composition"]
elif event_name == "scene_complete":
print(f" ✓ {payload['sceneId']}")
elif event_name == "scene_failed":
print(f" ✗ {payload['sceneId']}: {payload['error']}")

JavaScript (Node, fetch + 수동 SSE 파싱)

const res = await fetch("https://api.kvid.ai/api/agent", {
method: "POST",
headers: {
"api-key": process.env.KVIDAI_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
message: "화면 중앙에 'Summer Sale' 타이틀 3초간 추가해줘.",
composition,
projectId: 1234,
email: "[email protected]",
apiKey: process.env.KVIDAI_API_KEY,
locale: "ko",
}),
});

if (!res.headers.get("content-type")?.includes("text/event-stream")) {
console.error("rejected:", await res.json());
process.exit(1);
}

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = "";

while (true) {
const { value, done } = await reader.read();
if (done) break;

buf += decoder.decode(value, { stream: true });
const blocks = buf.split("\n\n");
buf = blocks.pop() ?? "";

for (const block of blocks) {
let name = "", data = "";
for (const line of block.split("\n")) {
if (line.startsWith("event: ")) name = line.slice(7);
else if (line.startsWith("data: ")) data = line.slice(6);
}
if (!name) continue;
const payload = JSON.parse(data);

switch (name) {
case "tool_start": console.log("→", payload.toolName); break;
case "tool_end": console.log("←", payload.toolName, payload.success ? "ok" : payload.error); break;
case "checkpoint": console.log("checkpoint at", payload.composition.tracks.length, "tracks"); break;
case "done": console.log("done:", payload.data.message); break;
case "error": console.error("error:", payload.error); break;
}
}
}

2. 부분 완료된 long-video 작업 이어가기

POST /api/agent/resume

Long-video 렌더가 중단된 경우 (크레딧 부족, 씬 생성 실패, 사용자 탭 종료) 이미 생성된 씬 플랜과 composition 은 저장돼 있습니다. Resume 은 실패/누락 씬만 재실행합니다.

Request body

필드필수설명
jobIddone 이벤트의 data.resumeJobId 또는 check-resume 결과.
email소유자.
apiKey사용자별 API 키.
locale아니오/api/agent 와 동일.

/api/agent 와 동일한 이벤트 형식의 SSE 스트림 반환. 마지막 done 이벤트는 resume 전용 메시지 키 (Agent.longVideo.resume.done.*) 사용.

with httpx.stream(
"POST",
"https://api.kvid.ai/api/agent/resume",
headers={"api-key": API_KEY, "Content-Type": "application/json"},
json={"jobId": job_id, "email": EMAIL, "apiKey": API_KEY, "locale": "ko"},
timeout=None,
) as resp:
for line in resp.iter_lines():
...

3. 단일 실패 씬 재시도

POST /api/agent/retry-scene

전체 플랜을 건드리지 않고 한 씬만 재실행. Agent 가 기존 씬의 prompt / 음성 / 나레이션 텍스트 재사용 — 일시적 이미지/비디오 provider 실패에 유용.

Request body

필드필수설명
jobIdLong-video 작업.
sceneId재시도할 씬.
email소유자.
apiKey사용자별 API 키.
locale아니오기본 en.
await fetch("https://api.kvid.ai/api/agent/retry-scene", {
method: "POST",
headers: { "api-key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
jobId: "job_abc123",
sceneId: "scene-4",
email: "[email protected]",
apiKey: API_KEY,
}),
});

응답은 그 한 씬에 대해 scene_startscene_complete / scene_faileddone 이벤트를 emit 하는 SSE 스트림.


4. 미완료 작업 확인

GET /api/agent/check-resume?projectId={id}&email={email}

특정 프로젝트에 진행 중인 long-video 작업이 있는지 조회. 이어갈 작업 없으면 { data: null }, 있으면:

{
"success": true,
"data": {
"jobId": "job_abc123",
"remainingScenes": 6,
"totalScenes": 24
}
}

페이지 로드 시 사용 — 예: 사용자가 떠났던 프로젝트로 돌아왔을 때 "▶ 남은 6씬 이어서 생성" 버튼 표시.

resp = requests.get(
"https://api.kvid.ai/api/agent/check-resume",
headers={"api-key": API_KEY},
params={"projectId": 1234, "email": EMAIL},
)
data = resp.json()["data"]
if data and data["remainingScenes"]:
print(f"resume 가능: {data['remainingScenes']}/{data['totalScenes']} 남음")

End-to-End: Resume 포함 long-video 파이프라인

import httpx
import json
import requests

API_KEY = "YOUR_API_KEY"
EMAIL = "[email protected]"
PROJECT = 1234

def stream_agent(url, body):
"""(event_name, payload) 튜플 yield."""
with httpx.stream(
"POST", url,
headers={"api-key": API_KEY, "Content-Type": "application/json"},
json=body, timeout=None,
) as resp:
if "text/event-stream" not in resp.headers.get("content-type", ""):
raise RuntimeError(f"rejected: {resp.json()}")
name = None
for line in resp.iter_lines():
if not line:
name = None
continue
if line.startswith("event: "):
name = line[7:]
elif line.startswith("data: ") and name:
yield name, json.loads(line[6:])

# 1. 시작
job_id = None
for name, payload in stream_agent(
"https://api.kvid.ai/api/agent",
{
"message": "한국 길거리 음식 30초 설명 영상 만들어줘.",
"composition": empty_composition,
"projectId": PROJECT,
"email": EMAIL,
"apiKey": API_KEY,
"locale": "ko",
},
):
if name == "plan_ready":
job_id = payload["jobId"]
print(f"플랜: {payload['totalScenes']}씬")
elif name == "scene_failed":
print(f" ✗ {payload['sceneId']}")
elif name == "done":
print("완료:", payload["data"]["message"])

# 2. 중간에 크레딧 부족이면 done 이벤트 `resumeJobId` 가 포함됨.
# 그 자리에서 처리하거나 다음 세션에서 lookup:
status = requests.get(
"https://api.kvid.ai/api/agent/check-resume",
headers={"api-key": API_KEY},
params={"projectId": PROJECT, "email": EMAIL},
).json()["data"]

if status and status["remainingScenes"]:
for name, payload in stream_agent(
"https://api.kvid.ai/api/agent/resume",
{"jobId": status["jobId"], "email": EMAIL, "apiKey": API_KEY, "locale": "ko"},
):
if name == "done":
print("resume 완료:", payload["data"]["message"])

Error Response

스트림 시작 전 에러는 JSON. 스트림 중 에러는 error SSE 이벤트.

Code의미일반적 해결
UNAUTHORIZEDapiKey 잘못됨kvid.ai/dashboard/api-keys 에서 재발급
CONCURRENT_LIMIT이미 agent 실행 진행 중이전 실행 완료 대기 또는 check-resume 조회 후 resume
INSUFFICIENT_CREDIT예약 크레딧 > 가용 잔액kvid.ai/credits/purchase 충전 — Pricing 참조
MISSING_PARAMETERS필수 필드 누락message 에서 자세한 내용 확인
ZOMBIE_TIMEOUT이전 작업이 멈춰있었고 자동 종료됨다시 시도

관련 문서