Python Text-to-Video API: 제출, 폴링, 다운로드
텍스트로 영상을 만드는 Sume API를 Python Requests로 호출하세요. POST /v1/videos 후 타임아웃을 두고 폴링하고, content 경로가 리다이렉트하는 MP4를 스트리밍하세요.

Python에서 텍스트로 영상을 만드는 API를 호출하려면, Requests로 model과 prompt를 담은 JSON 본문을 https://api.sume.com/v1/videos에 POST하고, 응답으로 받은 polling_url을 status가 completed가 될 때까지 폴링한 다음, 같은 Authorization: Bearer 헤더로 unsigned_urls[0]에 GET 요청을 보내 리다이렉트된 MP4를 디스크에 스트리밍으로 저장하세요. 모든 호출에는 timeout을 설정하세요.
Sume 관련 사실은 영상 생성 (영문)과 Job과 결과 (영문) 문서에서, Requests 동작은 Requests의 빠른 시작과 고급 사용법 페이지에서 가져왔으며, 모두 2026-09-27에 확인했습니다. Sume 문서는 TypeScript SDK인 @sume-com/sdk를 다루지만, 문서 자체의 Python 예제는 Requests로 평범한 HTTPS 호출을 하며 이 글도 그렇게 합니다. 같은 흐름을 curl로 하는 방법은 Sume API 빠른 시작에 있습니다.
텍스트로 영상을 만드는 Job은 Requests로 어떻게 제출하나요?
필수 필드는 model과 prompt뿐입니다. Sume가 모델을 고르게 하려면 sume/auto를 보내고, 모델을 고정하려면 GET /v1/videos/models에 나오는 접두사 없는 카탈로그 id를 보내세요. 본문은 json=으로 넘기세요. Requests가 인코딩해 주며, Requests 문서는 data=json.dumps(...)로는 붙지 않는 Content-Type: application/json 헤더가 필요할 때 json=을 쓰라고 안내합니다. 제출하면 202와 함께 id, polling_url, status: "pending", model이 돌아옵니다.
Idempotency-Key헤더를 보내세요. POST가 타임아웃되거나 연결이 끊기면 같은 키로 다시 보내세요. 재전송하면 두 번째 유료 Job을 시작하는 대신 원래 Job이 돌아옵니다.- Requests는 기본적으로 실패한 연결을 재시도하지 않습니다. Requests 문서는 허용 메서드에
POST를 넣은 urllib3의Retry를 Session에 마운트하는 예를 보여 줍니다. 그렇게 한다면 모든 시도에 같은 키를 쓰세요. Sume의 규칙은Idempotency-Key없이 안전하지 않은 제출을 재시도하지 않는 것입니다. raise_for_status()를 호출하세요. Requests 문서는 JSON 본문을 디코딩할 수 있다고 해서 호출이 성공한 것은 아니라고 짚습니다. Sume의 오류도 JSON이며,code와request_id가 담긴error객체 아래에 있습니다. 첫 호출에서 자주 만나는 오류는 빠른 시작에 정리되어 있습니다.
폴링 루프는 얼마나 기다려야 하나요?
문서가 권하는 대로 같은 헤더로 약 30초마다 polling_url을 폴링하고, completed, failed, cancelled 중 하나가 되면 멈추세요. 이 경로는 취소 상태를 l이 두 개인 철자로 씁니다. 일반적인 대기 시간은 AI 영상 생성에 걸리는 시간에 정리되어 있습니다.
- 모든 호출에
timeout=을 넘기세요. 타임아웃을 지정하지 않으면 Requests는 타임아웃되지 않으며, Requests 문서는 프로그램이 무기한 멈출 수 있다고 경고합니다. timeout은 다운로드 전체에 대한 제한이 아닙니다. 그 초만큼 바이트가 하나도 도착하지 않을 때 발동하므로, 작은 폴링과 큰 MP4에 같은 값 하나를 쓸 수 있습니다.(3.05, 27)같은 튜플은 연결 타임아웃과 읽기 타임아웃을 따로 정합니다.- 전체 마감 시간은 직접 관리하세요. Sume 문서는 영상에 20분 정도가 적당하다고 보며, 클라이언트 쪽 타임아웃은 Job을 취소하지 않습니다. Job은 계속 실행되고 계속 과금됩니다.
id를 저장해 두었다가 나중에 폴링을 이어 가세요. - 읽기와 쓰기는 키마다 요청 예산이 따로 있으므로, 폴링 루프 때문에 자신의 제출 요청이
429를 받는 일은 없습니다. 그래도 폴링이429를 받으면retry-after에 맞춰 백오프하세요.
unsigned_urls를 내려받으면 왜 401이 나오나요?
unsigned_urls의 항목은 파일이 아니라 https://api.sume.com/v1/videos/{id}/content?index=0 형태의 API 경로입니다. 제출이나 폴링과 마찬가지로 키가 필요하며, 키 없이 보낸 요청은 401 unauthorized를 받습니다. index의 기본값은 0이며, 모델이 출력을 여러 개 돌려줄 때 그중 하나를 고릅니다.
현재 코드에서 이 경로는 302로 응답하며, Sume 키가 필요 없는 다른 호스트인 media.sume.com의 공개 영상 파일로 리다이렉트합니다. Requests는 HEAD를 제외한 모든 메서드에서 리다이렉트를 따라가고, 리다이렉트가 다른 호스트로 가면 Authorization 헤더를 제거하므로, Bearer 키는 파일 호스트로 전달되지 않습니다. Requests 문서는 이 약속을 Authorization 헤더에 대해서만 하며 사용자 지정 헤더는 그대로 전달된다고 말하므로, x-api-key 헤더라면 리다이렉트를 따라 함께 가게 됩니다. 이 호출에는 Authorization: Bearer를 보내세요. 바이트 대신 파일 URL을 보관하려면 생성된 영상 내려받기를 참고하세요.
전체 스크립트는 어떻게 생겼나요?
제출, 폴링, 저장을 25줄로 처리합니다. 루프는 status를 확인하기 전에 먼저 폴링합니다. 재전송한 제출은 이미 끝났을 수도 있는 원래 Job을 돌려주고, 제출 응답에는 unsigned_urls가 절대 담기지 않기 때문입니다. stream=True는 본문 다운로드를 미루고, iter_content는 본문을 청크 단위로 쓰며, with 블록은 루프가 일찍 멈춰도 응답이 반드시 닫히게 합니다. 이 스크립트는 오류 상태를 받으면 모두 예외를 발생시킵니다. 프로덕션에서는 폴링에서 받은 429를 실패가 아니라 잠시 멈추라는 신호로 다루세요.
import os, time, requests
AUTH = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
body = {"model": "sume/auto", "prompt": "A paper boat drifting down a rainy street"}
r = requests.post("https://api.sume.com/v1/videos", json=body, timeout=30,
headers={**AUTH, "Idempotency-Key": "paper-boat-v1"})
r.raise_for_status()
job, deadline = r.json(), time.monotonic() + 20 * 60 # giving up does not cancel the job
while True:
poll = requests.get(job["polling_url"], headers=AUTH, timeout=30)
poll.raise_for_status()
job = poll.json()
if job["status"] in ("completed", "failed", "cancelled"):
break
if time.monotonic() > deadline:
raise TimeoutError(f"{job['id']} is still running; poll it later")
time.sleep(30)
if job["status"] != "completed":
raise RuntimeError(job.get("error", job["status"]))
with requests.get(job["unsigned_urls"][0], headers=AUTH, stream=True, timeout=30) as video:
video.raise_for_status()
with open("video.mp4", "wb") as f:
for chunk in video.iter_content(chunk_size=1024 * 1024):
f.write(chunk)호출마다 어떤 Requests 설정이 중요한가요?
아래 표에 세 호출을 정리했습니다. Developer API에는 SSE나 WebSocket 스트림이 없으므로 스크립트는 폴링하거나 웹훅을 받아야 합니다. 폴링을 건너뛰려면 HTTPS여야 하는 callback_url을 보내세요. Job이 종료 상태에 도달하면 Sume가 서명된 웹훅을 POST합니다. 서버 쪽은 Python 웹훅 수신기에서 다룹니다.
| 호출 | Sume 응답 | Requests 설정 |
|---|---|---|
POST /v1/videos | id와 polling_url이 담긴 202 | json=, Idempotency-Key 헤더, timeout |
polling_url에 GET | status가 담긴 Job | 같은 인증 헤더, timeout, 폴링 간격 약 30초 |
unsigned_urls 항목에 GET | 영상 파일로 가는 302 | Authorization: Bearer, stream=True, iter_content, timeout |
출처
관련 글
연동 카테고리의 다른 글
- Trigger.dev 웹훅 대기: Sume 실행용 waitpoint 토큰
Trigger.dev waitpoint 토큰을 만들고 token.url을 Sume 실행의 webhook_url로 보내면, Sume가 실행 결과를 POST할 때 wait.forToken()이 반환됩니다.
- Vercel AI SDK: Sume API 도구 호출로 영상 생성하기
Vercel AI SDK에서는 서버에서 Sume의 POST /v1/videos를 호출하는 tool()로 영상을 생성하세요. 도구는 Job id를 돌려주고, 클립은 폴링으로 받습니다.
- Vercel Cron Jobs: 중복 없이 매일 Sume API 호출하기
Vercel cron job이 라우트에 GET을 보내면 라우트가 날짜 기반 Idempotency-Key로 Sume API를 호출하므로, 중복 호출이 두 번 과금될 수 없습니다.
- Vercel 함수 타임아웃과 영상 생성: 웹훅을 쓰세요
Vercel Function은 기본적으로 300초에 멈추지만 Sume 영상 실행은 몇 분이 걸립니다. webhook_url과 함께 제출하고 바로 반환한 뒤, 서명된 POST를 검증하세요.
작성자 Sume