비동기 작업
이미지·영상 작업의 제출, 조회 또는 푸시 수신, 결과 다운로드 방법과 모든 상태·필드의 의미, 그리고 환불 방식.
비동기로 처리되는 엔드포인트
이미지와 영상 생성은 비동기 작업으로 실행됩니다. 호출하면 작업 ID가 바로 돌아오고, 생성은 백그라운드에서 진행됩니다. 채팅은 동기 방식이라 작업을 쓰지 않습니다.
| 메서드 | 경로 | 설명 |
|---|---|---|
| POST | /v1/images/generations | 텍스트-투-이미지. 작업 ID를 바로 돌려줍니다(HTTP 202). |
| POST | /v1/images/edits | 이미지-투-이미지. 위 엔드포인트에 image를 넣어도 같습니다. |
| GET | /v1/images/jobs/{id} | 이미지 작업의 상태와 결과를 조회합니다. |
| POST | /v1/videos/generations | 영상 생성. 작업 ID를 바로 돌려줍니다(HTTP 202). |
| GET | /v1/videos/jobs/{id} | 영상 작업의 상태와 결과를 조회합니다. |
처리 흐름
- 제출: 생성 엔드포인트를 호출하면 HTTP 202와 작업
id가 바로 돌아옵니다(img_...형태이며, 영상 작업도 같은 접두사를 씁니다). - 대기: 그 ID로 작업 상태를 주기적으로 조회하거나, 제출할 때
callback_url을 넣어 결과를 푸시로 받습니다. - 완료:
status가succeeded가 되면 결과 링크는data[0].url에 있습니다.failed가 되면 사유는error에 있고, 이 작업에 예약된 크레딧은 잔액으로 돌아옵니다. - 다운로드: 결과 링크는 60일 동안 유지됩니다. 계속 보관해야 하는 파일은 자체 저장소로 옮겨 두세요.
curl https://nezhagate.com/v1/videos/generations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "seedance-2.0", "prompt": "a paper boat drifting down a rainy street", "duration": 5, "size": "16:9"}'
# HTTP 202
{"id": "img_9c1e...7a", "object": "video.generation.job", "status": "queued", "created": 1791199400, "model": "seedance-2.0"}curl https://nezhagate.com/v1/videos/jobs/img_9c1e...7a -H "Authorization: Bearer YOUR_API_KEY"
작업 상태
| 상태 | 의미 |
|---|---|
queued | 대기 중. queue_position(앞에 있는 작업 수)과 eta_seconds(예상 대기 시간)가 함께 옵니다. |
processing | 생성 중. |
succeeded | 완료. 결과는 data[0].url, 과금 내역은 usage에 있습니다. |
failed | 실패. 사유는 error에 있고, 예약된 크레딧은 전액 환불됩니다. |
succeeded와 failed는 최종 상태이며, 그 뒤로는 바뀌지 않습니다.
응답 필드
| 필드 | 의미 |
|---|---|
id | 작업 ID. 조회와 웹훅에 쓰입니다. |
object | 이미지 작업은 image.generation.job, 영상 작업은 video.generation.job입니다. |
status | queued / processing / succeeded / failed. |
created | 작업을 제출한 시각(Unix 초). |
model | 제출할 때 쓴 모델 ID. |
queue_position | queued일 때만 있음: 앞에 있는 작업 수. |
eta_seconds | queued일 때만 있음: 시작까지 남은 예상 시간(초). |
data[].url | succeeded일 때만 있음: 결과 파일(이미지 또는 mp4)의 링크. 60일 동안 보관됩니다. |
usage | succeeded일 때만 있음: 과금 내역. 이미지는 images(실제 전달 장수)와 resolution, 영상은 clips를 담습니다. |
error | failed일 때만 있음: message, type, code, param. 원인은 code로 판단하세요. |
{
"id": "img_9c1e...7a",
"object": "video.generation.job",
"status": "succeeded",
"created": 1791199400,
"model": "seedance-2.0",
"data": [{"url": "https://img.nezhagate.com/i/5d41402abc....mp4"}],
"usage": {"clips": 1, "model": "seedance-2.0"}
}{
"id": "img_9c1e...7a",
"object": "video.generation.job",
"status": "failed",
"created": 1791199400,
"model": "seedance-2.0",
"error": {"message": "...", "type": "invalid_request_error", "code": "content_policy", "param": null}
}조회 요령
- 이미지는 2~3초, 영상은 5~10초 간격으로 조회하세요. 조회는 무료입니다.
succeeded나failed가 나오면 바로 조회를 멈추세요.- 프로그램에 전체 대기 시간 한도를 두세요. 이미지는 보통 수십 초에서 몇 분, 영상은 1~2분에서 1시간 넘게까지 걸립니다(영상이 길거나 대기열이 있으면 더 오래 걸립니다). 일반적인 소요 시간은 모델별 문서에 있습니다. 한도를 넘겨도 다시 제출하지 마세요. 작업은 그대로 남아 있으니 나중에 이어서 조회하면 됩니다.
- 작업은 제출한 계정에 속합니다. 그 계정의 어느 키로든 조회할 수 있고, 다른 계정에서 조회하면 404
job_not_found가 돌아옵니다. 작업 기록은 3일 동안 보관되며, 그 뒤에는 작업 ID로 조회해도 404가 돌아옵니다. 다만 결과 링크는 60일 동안 계속 쓸 수 있습니다. - 제출 요청이 내 쪽에서 시간 초과되어 작업 ID를 받지 못했다면, 다시 제출하기 전에 콘솔의 「호출 로그」에서 작업이 만들어졌는지 확인하세요. 그래야 두 번 과금되지 않습니다.
조회 대신 웹훅으로 받기
제출할 때 callback_url을 넣으면 작업이 끝나는 즉시(성공이든 실패든) 게이트웨이가 조회 엔드포인트와 똑같은 결과를 그 URL로 보냅니다. 서명이 포함되며, 전송에 실패하면 자동으로 재시도합니다. 콘솔의 「설정 → 웹훅」에서 계정 전체에 쓸 주소를 지정할 수도 있습니다.
{"model": "seedance-2.0", "prompt": "...", "duration": 5, "callback_url": "https://example.com/webhooks/nezhagate"}과금
- 제출하면 작업 가격만큼 잔액에서 크레딧이 예약됩니다. 잔액이 부족하면 402가 돌아오고 작업은 만들어지지 않습니다.
- 성공하면 실제로 전달된 만큼 정산합니다. 이미지는 전달된 장수로, 영상은 주문한 길이 또는 클립 수(예약한 금액과 같음)로 정산하며, 그보다 많이 예약된 크레딧은 자동으로 돌아옵니다. 실패하면 예약된 크레딧이 전액 돌아옵니다.
- 작업 조회와 웹훅 전송은 무료입니다.
결과 보관 기간
data[0].url은 당사 저장소의 링크이며 60일 뒤 자동으로 삭제됩니다. 결과를 계속 쓰려면 자체 저장소나 CDN에 내려받아 두세요.
작업이 실패하는 이유
실패한 작업은 항상 전액 환불됩니다. 실패한 작업에서 가장 자주 보게 되는 코드는 다음과 같습니다:
| code | 언제 나오나요 | 조치 |
|---|---|---|
content_policy | 영상: 프롬프트, 참조 소재, 또는 생성된 화면이나 소리가 콘텐츠 심사를 통과하지 못했습니다(소리가 걸렸으면 param이 audio입니다). | 프롬프트를 고쳐 쓰거나 참조 소재를 바꾼 뒤 다시 제출하세요. 같은 입력은 다시 거부됩니다. |
moderation_blocked | 이미지: 모델 제공사의 안전 시스템이 프롬프트나 참조 이미지를 거부했습니다(코드가 content_policy_violation일 수도 있습니다). | 프롬프트를 고쳐 쓰거나 참조 이미지를 바꾸세요. 그대로 다시 제출하면 대개 다시 거부됩니다. |
render_failed | 영상: 이번에는 영상이 나오지 않았습니다. 대개 일시적인 현상입니다. | 같은 요청을 그대로 다시 제출하면 대개 성공합니다. |
invalid_material | 영상: 업스트림이 입력 소재에 문제가 있다고 판단했습니다. 포괄적인 오류이며 일시적인 경우가 많습니다. | 먼저 그대로 다시 제출해 보세요. 계속 나오면 참조 소재와 프롬프트 길이를 확인하세요. |
render_timeout | 영상: 대기나 렌더링이 너무 오래 걸려 업스트림이 작업을 포기했습니다. | 다시 제출하세요. |
wait_timeout | 영상: 회선이 허용하는 최대 대기 시간 안에 영상이 나오지 않았습니다. | 다시 제출하세요. |
upstream_unstable | 영상: 렌더링 중에 연결이 끊겼습니다. | 다시 제출하세요. |
result_fetch_failed | 영상은 생성되었지만, 당사 측에서 영상을 내려받거나 저장하는 데 계속 실패했습니다. | 다시 제출하세요. |
invalid_request | 영상: 회선이 이 파라미터 조합(예: 길이, 비율, 해상도)을 받지 않습니다. | message에 적힌 대로 파라미터를 조정한 뒤 다시 제출하세요. |
upstream_error | 그 밖의 실패: 모든 회선을 시도했지만 실패했거나, 15분 동안 기다려도 시작되지 않았거나, 업스트림이 이유 없이 거부한 경우 등입니다. 구체적인 내용은 message에 있습니다. | 다시 제출하면 대개 성공합니다. |
전체 예제
제출, 조회, 다운로드를 한 번에 처리하며, 전체 대기 시간 한도와 실패 처리도 들어 있습니다. YOUR_API_KEY만 바꾸면 바로 실행할 수 있습니다.
import time
import requests
BASE = "https://nezhagate.com/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
def run_job(kind, payload, every=5, give_up=5400):
"""kind is "images" or "videos". Returns the finished job (succeeded or failed)."""
job = requests.post(f"{BASE}/{kind}/generations", headers=HEADERS, json=payload, timeout=120)
job.raise_for_status() # 4xx here: fix the request; nothing was billed
job_id = job.json()["id"]
deadline = time.time() + give_up
while time.time() < deadline:
time.sleep(every)
r = requests.get(f"{BASE}/{kind}/jobs/{job_id}", headers=HEADERS, timeout=30).json()
if r["status"] in ("succeeded", "failed"):
return r
raise TimeoutError(f"{job_id} still running; poll it again later, it is not lost")
job = run_job("videos", {"model": "seedance-2.0", "prompt": "a paper boat drifting down a rainy street",
"duration": 5, "size": "16:9"})
if job["status"] == "succeeded":
url = job["data"][0]["url"]
with open("clip.mp4", "wb") as f: # keep your own copy: links expire after 60 days
f.write(requests.get(url, timeout=300).content)
else:
print(job["error"]["code"], job["error"]["message"])