AI 영상 API 오류 코드: 의미와 재시도 시점
영상 생성 작업은 일반 API 호출보다 실패하는 경우가 많습니다. 모델이 바쁘거나, 프롬프트를 거부하거나, 시간이 초과되기 때문입니다. 이 가이드는 받을 수 있는 모든 오류와 재시도할 가치가 있는 오류, 안전하게 재시도하는 방법을 정리합니다.
오류가 나타나는 두 지점
영상 또는 이미지 작업은 두 시점에서 문제가 생길 수 있으며, 코드에서 둘 다 처리해야 합니다.
- 제출할 때.
POST /v1/generations가 HTTP 오류 상태와{"error": {"code": "...", "message": "..."}}같은 본문으로 응답합니다. 아무것도 시작되지 않았고 요금도 청구되지 않습니다. - 실행 중에. 제출은
202로 성공했지만, 작업이 나중에status: "failed"로 끝납니다.error에는code, 요청한 사람에게 보여 줄 수 있는message, 그리고retryable가 들어 있습니다.
실패한 작업은 어떤 모델에서도 요금이 청구되지 않습니다. 제출할 때 잔액에서 금액이 보류되고, 작업이 실패하면 보류가 해제됩니다.
제출 시 HTTP 오류
| 상태 | 코드 | 의미 | 재시도? |
|---|---|---|---|
| 400 | invalid_request | 본문이 JSON이 아니거나, model 또는 input가 없거나, API가 모르는 필드가 있습니다. | 아니요. 본문을 수정하세요. |
| 401 | unauthorized | API 키가 없거나 올바르지 않습니다. | 아니요. 키를 확인하세요. |
| 402 | insufficient_credits | 잔액이 작업에 보류할 금액보다 부족합니다. | 충전 후에 가능합니다. |
| 403 | account_disabled, generation_not_enabled | 계정이 일시 중지되었거나, 키가 가격 조회만 되고 생성은 할 수 없습니다. | 아니요. 지원팀에 문의하세요. |
| 404 | not_found | 계정에 해당 id의 생성이 없습니다. | 아니요. |
| 409 | idempotency_conflict | Idempotency-Key가 이미 다른 본문으로 사용되었습니다. | 새 키로 가능합니다. |
| 409 | not_ready | 작업이 성공하기 전에 파일을 요청했습니다. | 가능합니다. 성공한 후에는 됩니다. |
| 413 | body_too_large | JSON 본문이 1 MB를 넘습니다. 파일을 업로드하고 링크를 보내세요. | 아니요. 본문 크기를 줄이세요. |
| 422 | invalid_input | 모델이 지원하지 않는 값입니다. 예를 들어 목록에 없는 길이나 크기입니다. 메시지에 어떤 값인지 표시됩니다. | 아니요. 값을 바꾸세요. |
| 422 | max_cost_exceeded | 모든 경로의 비용이 max_cost_usd보다 높습니다. | 아니요. 상한을 올리거나 설정을 낮추세요. |
| 422 | model_not_available | 해당 id의 모델이 없습니다. | 아니요. id를 확인하세요. |
| 429 | rate_limited | 한 계정에서 같은 초 안에 너무 많은 요청을 보냈습니다. | 가능합니다. Retry-After 후에 재시도하세요. |
| 429 | concurrency_limit | 계정에서 실행 중인 작업이 이미 최대 개수입니다. | 가능합니다. 작업 하나가 끝나면 재시도하세요. |
| 500 | server_error | 저희 쪽에서 문제가 발생했습니다. | 가능합니다. 같은 Idempotency-Key로 재시도하세요. |
| 503 | busy | 일시적인 부하입니다. | 가능합니다. Retry-After 후에 같은 키로 재시도하세요. |
| 503 | model_unavailable | 지금은 이 모델에 사용할 수 있는 경로가 없습니다. | 가능합니다. 잠시 후에 재시도하세요. |
경험칙: 429, 500, 503은 백오프를 두고 재시도할 가치가 있습니다. 429를 제외한 4xx 범위의 오류는 요청 자체를 바꿔야 한다는 뜻입니다.
작업이 시작된 후 실패하는 이유
| error.code | retryable | 발생한 일 |
|---|---|---|
content_policy | no | 모델이 프롬프트나 미디어를 거부했거나 결과물을 제공하지 않았습니다. reason가 이유를 알려 줍니다: sexual, minors, copyright, real_person, other. |
invalid_input | no | 참조 파일을 읽을 수 없거나 모델이 값을 거부했습니다. 메시지에 어떤 값인지 표시됩니다. |
model_declined | no | 모델이 이미지나 영상을 만드는 대신 글로 답했습니다. 프롬프트에 결과물 자체를 구체적으로 묘사하세요. |
reference_changed | no | 참조 파일이 검사된 후 변경되었습니다. 다시 보내세요. |
max_cost_exceeded | no | 실행할 수 있는 유일한 경로의 비용이 상한보다 높습니다. |
model_busy | yes | 모든 경로가 한도에 도달했습니다. 1분 후에 다시 시도하세요. |
model_unavailable | yes | 지금은 이 작업을 받을 수 있는 경로가 없습니다. |
timeout | yes | 제때 결과를 반환한 경로가 없거나, max_wait_s이 지났습니다. |
all_routes_failed | yes | 모든 경로가 각 경로 쪽 사정으로 실패했습니다. |
내부적으로는 바쁘거나 문제가 있는 경로를 만난 작업이 자동으로 다음 경로로 넘어갑니다. 따라서 model_busy 또는 all_routes_failed가 보일 때는 이미 모든 경로를 시도한 상태입니다. attempts 필드에 시도한 횟수가 나옵니다. 보통 1~2분 기다렸다가 다시 시도하면 충분합니다.
콘텐츠 거부
content_policy 은(는) 가장 흔히 겪는 실패입니다. API가 아니라 모델이 결정하며, reason가 어떤 규칙이 적용되었는지 알려 줍니다. 흔한 원인은 프롬프트나 참조 이미지에 유명인의 이름이나 얼굴이 있는 경우(real_person), 브랜드와 유명 캐릭터(copyright), 미성년자와 관련된 것으로 읽히는 모든 내용입니다. 같은 요청을 다시 보내도 소용없으니 프롬프트를 고쳐 쓰거나 이미지를 바꾸세요. 모델마다 엄격함이 달라서, 같은 프롬프트가 한 모델에서는 통과되고 다른 모델에서는 거부될 수 있습니다.
올바르게 동작하는 재시도 루프
이 Python 헬퍼는 재시도할 가치가 있는 오류만 재시도하고, 그렇지 않은 오류에서는 멈춥니다. 두 가지 세부 사항이 중요합니다.
503또는 연결 끊김 후에 제출을 다시 보낼 때는 같은Idempotency-Key를 사용하므로, 결제되는 두 번째 작업이 시작되는 일이 없습니다.- 작업이 실패한 후에 다시 시도할 때는 새 키를 사용합니다. 같은 키를 쓰면 실패한 작업이 그대로 반환됩니다.
import os
import time
import uuid
import requests
API = "https://api.revana.si/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVANA_API_KEY']}"}
RETRY_HTTP = {429, 500, 503}
class GenerationError(Exception):
pass
def submit(body):
"""POST a job. Retries 429, 500 and 503 with the same Idempotency-Key, so a retry never starts a second job."""
headers = {**AUTH, "Idempotency-Key": str(uuid.uuid4())}
for attempt in range(6):
r = requests.post(f"{API}/generations", headers=headers, json=body, timeout=60)
if r.ok:
return r.json()
if r.status_code in RETRY_HTTP:
time.sleep(float(r.headers.get("Retry-After", 2 ** attempt)))
continue
err = r.json()["error"] # 400, 401, 402, 403, 409, 413, 422: fix the request, do not retry
raise GenerationError(f"{r.status_code} {err['code']}: {err['message']}")
raise GenerationError("still busy after 6 tries")
def wait(job):
while job["status"] in ("queued", "running"):
time.sleep(5)
job = requests.get(f"{API}/generations/{job['id']}", headers=AUTH).json()
return job
def generate(body, tries=3):
for n in range(tries):
job = wait(submit(body)) # each call uses a new key: a failed job is final
if job["status"] == "succeeded":
return job
err = job["error"]
if not err["retryable"]:
raise GenerationError(f"{err['code']}: {err['message']}")
time.sleep(60 * (n + 1)) # model_busy, timeout and the like: wait, then try again
raise GenerationError(f"gave up after {tries} tries: {err['code']}")
job = generate({
"model": "seedance-2.0-fast",
"input": {"prompt": "Waves roll onto black sand at sunset.", "resolution": "480p", "duration": 5},
"max_cost_usd": 0.3,
})
print(job["output"]["urls"][0], job["cost_usd"])예제 작업은 5초 길이의 480p Seedance 2.0 Fast 클립이며, 성공하면 $0.25이고 실패하면 무료입니다. max_cost_usd 상한은 잘못된 설정으로 예상보다 많은 비용이 나가는 것을 막아 줍니다.
애초에 오류를 피하는 방법
- 보내기 전에 옵션을 확인하세요.
GET /v1/models와 모델 페이지에 모든 모델의 크기, 길이, 참조 제한이 나와 있습니다. 이 범위를 벗어난 값은422 invalid_input와 함께 즉시 거부됩니다. - 파일은 포함하지 말고 업로드하세요.
POST /v1/uploads는 최대 30 MB의 이미지, 50 MB의 영상, 15 MB의 오디오를 받고 14일간 유효한 링크를 반환하므로 요청 본문을 작게 유지할 수 있습니다. - 코드에서 잔액을 확인하세요. 대량 배치 전에
GET /v1/balance를 호출하면402오류가 연달아 나는 것을 막을 수 있습니다. 크레딧 팩은 요금 페이지에 있습니다.
성공한 작업의 비용을 줄이는 방법은 AI 영상 API 비용 줄이는 방법을 참고하세요. 전체 오류 레퍼런스는 문서에 있으며, 직접 사용해 보려면 API 키를 받으세요.
자주 묻는 질문
AI 영상 생성이 실패하면 요금이 청구되나요?
아니요. 요청을 제출할 때 금액이 보류되고, 작업이 실패하면 사유와 관계없이 보류가 해제됩니다.
어떤 오류를 재시도해야 하나요?
제출 시에는 429, 500, 503을 Retry-After 지연 후 재시도하세요. 작업이 실패한 경우에는 error.retryable이 true일 때(model_busy, model_unavailable, timeout, all_routes_failed) 재시도하세요.
content_policy는 무슨 뜻인가요?
모델이 프롬프트나 미디어를 거부했거나 생성한 결과물을 제공하지 않았다는 뜻입니다. reason 필드에 어떤 규칙인지(sexual, minors, copyright, real_person, other)가 표시됩니다. 재시도하지 말고 요청 내용을 바꾸세요.
중복 결제 없이 재시도하려면 어떻게 하나요?
Idempotency-Key 헤더를 보내고, 타임아웃이나 503 후에 다시 보낼 때 같은 키를 재사용하세요. 새 생성이 아니라 같은 생성 결과를 받게 됩니다.
422 invalid_input은 무슨 뜻인가요?
길이, 크기, 참조 이미지 수처럼 보낸 값 중 모델이 받지 않는 값이 있다는 뜻입니다. 메시지에 해당 필드와 허용되는 값이 표시됩니다.