오류
모든 업무 코드의 의미와 해결 방법, 그리고 재시도할 가치가 있는 오류.
호출이 실패하면 응답은 문제의 종류를 나타내는 숫자(code)와 사람이 읽을 문장(msg)을 돌려줍니다. 분기는 숫자로 하고, 문장은 화면에 보여 주거나 로그에 남기세요.
응답 형식
{ "code": 40201, "msg": "Insufficient balance", "request_id": "req_…" }code는 업무 코드입니다.200이 성공이고 나머지는 아래 표에 있습니다.- 실패하면
data필드가 없습니다. 일부 에셋 라이브러리 오류만data.reason을 추가합니다. - HTTP 상태도 맞게 설정됩니다(
40201이면 HTTP402). 둘 중 하나로만 분기하고 섞지 마세요. 더 구체적인code를 권장합니다. request_id는X-Request-Id헤더에도 들어 있습니다. 고객 지원에 문의할 때 함께 보내 주세요.
응답이 아예 JSON이 아니라면(중간 네트워크가 돌려준 HTML이나 텍스트 페이지 등) HTTP 상태로 처리하고 본문 앞부분을 로그에 남기세요.
오류 메시지 언어
msg와 실패한 작업의 errorMessage는 기본이 영어입니다. Accept-Language에 en, ja, ko, de, fr, es, pt-BR, ru, it, pl 중 하나를 보내거나 콘솔에서 API 오류 언어를 설정하면 그 언어로 받습니다. 다른 언어는 영어로 돌아옵니다. 오류 응답에는 Content-Language가 붙습니다. 코드는 언어에 따라 바뀌지 않습니다. msg를 파싱하면 안 되는 또 하나의 이유입니다.
모델이 구체적인 실패 이유(예: 읽을 수 없는 참조 이미지)를 알려 주면, errorMessage는 그 이유를 내부 정보만 지운 영어 원문으로 전달합니다.
일반 코드
code | HTTP | 의미 | 재시도? |
|---|---|---|---|
200 | 200 / 202 | 성공 | — |
400 | 400 | 요청이나 input이 잘못됨. msg가 문제를 알려 줍니다 | 아니요, 고친 뒤에 |
40003 | 400 | 업로드한 파일이 업로드 티켓과 다름(크기, 형식, 내용) | 아니요, 티켓을 새로 받아 다시 업로드 |
40004 | 400 | 요청은 맞지만 지금 이 모델이 이 설정 조합을 처리할 수 없음 | 아니요, msg가 가리키는 설정을 바꾸세요 |
401 | 401 | 키가 없거나, 알 수 없거나, 폐기됨(자세히) | 아니요 |
403 | 403 | 허용되지 않음(권한 참고) | 아니요 |
404 | 404 | 존재하지 않거나 다른 키의 것 | 아니요 |
409 | 409 | 현재 상태와 충돌(재시도 기준) | 경우에 따라 |
40901 | 409 | 견적이 만료됐거나 가격이 바뀜 | 다시 견적 |
413 | 413 | 요청 본문이 너무 큼 | 아니요, 인라인 대신 업로드 |
429 | 429 | 요청이 너무 많음 | 예, Retry-After 이후에 |
500 | 500 | 저희 쪽 오류 | 예, 백오프하며 |
503 | 503 | 보조 서비스가 잠시 사용 불가 | 예, Retry-After 이후에 |
잔액과 한도
code | HTTP | 의미 | 해결 방법 |
|---|---|---|---|
40201 | 402 | 이 작업에 잡아 둘 크레딧이 부족함 | 크레딧을 구매한 뒤 다시 보내기 |
40202 | 402 | 키의 지출 한도에 도달 | API 콘솔에서 한도를 올리거나 초기화를 기다리기 |
권한
code | HTTP | 의미 | 해결 방법 |
|---|---|---|---|
40301 | 403 | 이 키로는 이 모델을 호출할 수 없음 | 키의 허용 모델에 추가 |
40302 | 403 | 요청 주소가 키의 IP 허용 목록에 없음 | 주소를 추가하거나 제한을 해제 |
40303 | 403 | 이 지역에서는 서비스를 제공하지 않음 | 우회할 수 없습니다 |
40310 | 403 | 계정 이메일이 아직 인증되지 않음 | 가입 메일의 링크를 여세요. 인증되면 바로 키가 작동합니다 |
유효한 키인데 일반 403이 오면 계정이 지금 API를 쓸 수 없는 상태입니다. request_id와 함께 고객 지원에 문의하세요.
모델 사용 가능 여부
code | HTTP | 의미 | 재시도? |
|---|---|---|---|
50301 | 503 | 이 모델이 지금 작업을 받을 수 없음 | 예, 나중에 또는 다른 모델로 |
40004 | 400 | 모델은 정상이지만 이 설정으로는 안 됨 | 아니요, 설정을 바꾸세요 |
둘 다 작업을 만들지 않고 크레딧도 잡아 두지 않습니다.
작업 실패 코드
접수된 뒤 실패한 작업은 state: "failed"가 되고 errorCode에 아래 중 하나가 들어갑니다. 목록은 닫혀 있으니 다른 값이 오면 upstream_failed로 처리하세요.
errorCode | 의미 | 재시도? |
|---|---|---|
invalid_request | 모델이 파라미터를 받아들이지 않음 | 아니요, 고친 뒤에 |
unsupported_combination | 지금은 이 설정 조합을 지원하지 않음 | 아니요, 설정을 바꾸세요 |
content_rejected | 모델이 이 내용을 거절함 | 아니요, 프롬프트나 참조 자료를 바꾸세요 |
invalid_asset | 참조 파일을 읽을 수 없거나 더 이상 없음 | 다시 업로드하고 새 작업을 만드세요 |
rate_limited | 생성 서비스가 바빴음 | 예, 백오프하며 |
upstream_unavailable | 생성이 잠시 불가능함 | 예, 잠시 후에 |
generation_failed | 생성은 돌았지만 결과가 없음 | 경우에 따라. 같은 입력은 같은 식으로 실패하기 쉽습니다 |
timeout | 시간 초과 | 경우에 따라. 더 짧게 또는 낮은 해상도로 |
upstream_failed | 그 밖의 모든 경우 | 예, 백오프하며 |
실패한 작업은 잡아 둔 크레딧을 돌려주므로 비용이 들지 않습니다(일부 모델은 콘텐츠 거절에도 과금하며 해당 모델 페이지에 적어 둡니다). errorMessage는 이유를 문장으로 알려 줄 뿐이니 분기는 errorCode로만 하세요.
409를 재시도해야 할 때
| 상황 | 재시도? |
|---|---|
같은 Idempotency-Key의 요청 두 개가 동시에 도착 | 예, 잠시 뒤 같은 키로 |
그 Idempotency-Key가 다른 요청이나 다른 키에서 이미 쓰임 | 아니요 |
| 업로드나 결과물이 아직 처리 중 | 예, 몇 초 뒤에 |
아직 끝나지 않은 작업에 jobs/retry | 예, 종료 상태가 된 뒤에 |
성공한 작업에 jobs/retry | 아니요, 새 작업을 만드세요 |
| 업로드 티켓이나 업로드 파일이 만료됨 | 아니요, 다시 업로드 |
재시도 코드 작성법
다시 보냈을 때 성공할 수 있는 것만 재시도하고, Retry-After가 있으면 따르고 없으면 지수 백오프로 기다립니다.
const RETRYABLE = new Set([429, 500, 503, 50301]);
async function call(path, init, attempts = 4) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(`https://api.popnow.ai/api/v1${path}`, init);
if (!res.headers.get('content-type')?.includes('json')) {
throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`);
}
const body = await res.json();
if (body.code === 200) return body.data;
if (!RETRYABLE.has(body.code)) throw new Error(`${body.code} ${body.msg} (${body.request_id})`);
const wait = Number(res.headers.get('retry-after')) * 1000 || 2 ** i * 1000 + Math.random() * 500;
await new Promise((r) => setTimeout(r, wait));
}
throw new Error('out of retries');
}import random, time, requests
RETRYABLE = {429, 500, 503, 50301}
def call(method, path, attempts=4, **kwargs):
for i in range(attempts):
res = requests.request(method, f"https://api.popnow.ai/api/v1{path}", timeout=60, **kwargs)
try:
body = res.json()
except ValueError:
raise RuntimeError(f"HTTP {res.status_code}: {res.text[:200]}")
if body["code"] == 200:
return body["data"]
if body["code"] not in RETRYABLE:
raise RuntimeError(f'{body["code"]} {body["msg"]} ({body.get("request_id")})')
retry_after = res.headers.get("retry-after")
time.sleep(float(retry_after) if retry_after else 2**i + random.random() / 2)
raise RuntimeError("out of retries")createTask를 재시도할 때는 항상 같은 Idempotency-Key를 보내세요. 실제로는 성공한 요청이 두 번 실행되고 두 번 과금되는 일을 막아 줍니다.
문제 추적
request_id와code를 적어 둡니다.- API 콘솔의 로그에서 작업 ID나
request_id로 작업을 찾습니다. - 그래도 해결되지 않으면
request_id, 시각(UTC),code를 함께 보내 고객 지원에 문의하세요. API 키는 절대 보내지 마세요.

