# 오류 코드

모든 오류에 공통으로 쓰이는 형식, 코드별 의미, 그리고 오류를 받았을 때의 조치 방법.

> https://nezhagate.com/ko/docs/guide/errors

## 오류 형식

`/v1` 엔드포인트에서 오류가 나면 4xx / 5xx 상태 코드와 함께 아래 예시 같은 JSON을 돌려줍니다. `message`는 사람이 읽는 설명, `type`은 오류의 큰 분류, `code`는 바뀌지 않는 기계 판독용 식별자, `param`은 문제가 된 필드(없으면 `null`)입니다. HTTP 상태 코드와 `code`로 판단하고, `message` 문구로는 판단하지 마세요. 영어, 중국어 또는 둘 다일 수 있고 문구도 바뀝니다.

```
{
  "error": {
    "message": "Insufficient balance — please top up",
    "type": "insufficient_quota",
    "code": "insufficient_quota",
    "param": null
  }
}
```

`/anthropic` 엔드포인트는 Anthropic 고유의 형식을 따르며 `code`와 `param`이 없습니다. HTTP 상태 코드와 `error.type`으로 판단하세요:

```
{
  "type": "error",
  "error": {
    "type": "not_found_error",
    "message": "model: gpt-5.5"
  }
}
```

- 일부 오류에는 최상위 필드가 더 붙습니다. 예를 들어 모든 회선이 가득 찼을 때는 `attempts`, 잠시 쓸 수 있는 회선이 없을 때는 `retry_after`가 붙습니다. 모르는 필드는 무시하면 됩니다.

- 모델 제공사가 요청을 거부하면(예: 컨텍스트가 너무 긴 경우) `message`에는 모델 제공사의 원래 문구가, `code`에는 모델 제공사가 준 값이 들어가며, 값을 주지 않았으면 `invalid_request`입니다.

## HTTP 상태 코드 한눈에 보기

| HTTP | 언제 나오나요 |
| --- | --- |
| **400** | 잘못된 파라미터 또는 없는 모델(`model_not_found` 등). `error.param`이 해당 필드를 알려 줍니다. |
| **401** | 키가 없거나, 유효하지 않거나, 정지된 키(`missing_api_key`, `invalid_api_key`). |
| **402** | 잔액이 부족하거나 키가 예산 한도에 도달했습니다(`insufficient_quota`). |
| **403** | 이 키의 모델 허용 목록에 해당 모델이 없거나, 키의 IP 허용 목록 밖에서 온 요청입니다(`permission_denied`). |
| **404** | 경로가 없거나, 작업 ID가 없거나 3일 기록 보관 기간이 지났거나 다른 계정이 제출한 작업입니다(`job_not_found`). |
| **429** | 이 키에 설정한 분당 요청 수나 무료 모델의 하루 한도를 넘었거나, 이 모델의 모든 회선이 가득 찼습니다(`rate_limit_exceeded`). `Retry-After`가 있으면 그 초만큼, 없으면 몇 초 기다린 뒤 다시 시도하세요. |
| **502** | 업스트림 오류 또는 시간 초과로, 회선을 바꿔도 실패했습니다. 이유는 `code`(`upstream_timeout`, `upstream_unavailable` 등)에 있습니다. 과금되지 않으니 다시 시도해도 됩니다. |
| **503** | 모델 점검 중(`model_maintenance`)이거나 당사 측의 일시적인 오류(`service_unavailable`)입니다. 과금되지 않으니 잠시 후 다시 시도하세요. |

## 전체 오류 코드

게이트웨이가 돌려주는 오류를 분류별로 정리했습니다. HTTP 상태 코드 하나에 여러 코드가 있을 수 있으니 `code`를 기준으로 판단하세요.

### 인증과 계정

| HTTP | code | 원인 | 조치 |
| --- | --- | --- | --- |
| **401** | `missing_api_key` | `Authorization: Bearer` 헤더가 없습니다. `/v1` 엔드포인트는 `x-api-key`를 읽지 않습니다. | `Authorization: Bearer YOUR_API_KEY`를 추가하세요. |
| **401** | `invalid_api_key` | 키가 없거나, 삭제되었거나, 비활성화되었습니다. 계정이 비활성화된 경우에도 나옵니다. | 콘솔의 「API Keys」 페이지에서 이 키를 확인하세요. |
| **402** | `insufficient_quota` | 계정 잔액을 모두 썼습니다(허용된 마이너스 한도보다 낮음). | 충전한 뒤 다시 시도하세요. |
| **403** | `permission_denied` | 요청이 이 키의 IP 허용 목록 밖에서 왔습니다. | 허용된 IP에서 호출하거나, 콘솔에서 허용 목록을 수정하세요. |

### 키에 설정한 제한

| HTTP | code | 원인 | 조치 |
| --- | --- | --- | --- |
| **429** | `rate_limit_exceeded` | 이 키에 설정한 분당 요청 수를 넘었습니다(최근 60초 기준). `Retry-After` 헤더는 붙지 않습니다. | 몇 초 기다린 뒤 다시 시도하거나, 콘솔에서 이 키의 한도를 올리세요. |
| **429** | `rate_limit_exceeded` | 무료 모델의 하루 한도를 다 썼습니다(계정별, UTC 기준 하루). `message`에 대신 쓸 수 있는 유료 모델이 추천될 수 있습니다. | UTC 0시 이후에 다시 쓰거나, 유료 모델로 바꾸세요. |
| **402** | `insufficient_quota` | 이 키의 일일 예산(UTC 기준 하루) 또는 총 사용 한도를 다 썼습니다. | 다음 날 다시 쓰거나, 콘솔에서 한도를 올리세요. |
| **403** | `permission_denied` | 이 모델이 키의 모델 허용 목록에 없습니다. | 허용 목록에 있는 모델을 쓰거나, 콘솔에서 허용 목록을 수정하세요. |

### 요청 자체의 문제

| HTTP | code | 원인 | 조치 |
| --- | --- | --- | --- |
| **400** | `invalid_json` / `invalid_encoding` | 요청 본문이 올바른 JSON이 아닙니다. UTF-8 인코딩이 아니면 `invalid_encoding`입니다. | JSON 문법과 인코딩을 확인하세요. |
| **400** | `invalid_request` | 파라미터가 올바르지 않습니다. 요청 본문이 JSON 객체가 아니거나, `n`이 정수가 아니거나, 참조 이미지나 참조 영상을 읽을 수 없거나(`message`에 몇 번째인지와 이유가 적혀 있습니다), `/v1/images/edits`에 참조 이미지가 없거나, 영상의 길이 / 해상도 / 비율 / 참조 소재 수가 모델이 허용하는 범위를 벗어난 경우 등입니다. | `message`에 적힌 대로 고친 뒤 다시 제출하세요. |
| **400** | `invalid_messages` / `invalid_role` / `invalid_max_tokens` | 채팅 요청의 `messages`가 없거나 비어 있거나 형식이 잘못되었거나, `role`이 허용된 값이 아니거나, `max_tokens`가 양의 정수가 아닙니다. 문제가 된 필드는 `param`에 표시됩니다. | `param`이 가리키는 필드를 고치세요. |
| **400** | `missing_prompt` | `/v1/images/generations` 요청에 `prompt`가 없습니다. | `prompt`를 추가하세요. |
| **400** | `unsupported_resolution` | 이 모델은 선택한 해상도 등급을 제공하지 않습니다(예: Grok 이미지 생성에는 4K가 없습니다). | 다른 등급을 고르세요. 모델별로 지원하는 등급은 각 모델 문서에 있습니다. |
| **400** | `invalid_callback_url` | `callback_url`이 공개된 http(s) 주소가 아니거나 2000자를 넘습니다. | 인터넷에서 접근할 수 있는 주소를 쓰세요. |
| **400** | `max_tokens_too_small` | 추론 모델이 `max_tokens`를 모두 사고에 써서 답변을 쓰지 못했습니다(비스트리밍 채팅). | `max_tokens`를 늘린 뒤 다시 시도하세요. |
| **404** | `not_found` | 엔드포인트 경로가 없습니다. | 경로를 확인하세요. 전체 엔드포인트는 [API 레퍼런스](https://nezhagate.com/ko/docs/api)에 있습니다. |
| **404** | `job_not_found` | 작업 ID가 없거나, 3일 기록 보관 기간이 지났거나, 다른 계정이 제출한 작업입니다. 이미지 작업을 영상 조회 엔드포인트로 조회할 때(또는 그 반대)도 이 오류가 납니다. | 작업 ID와 조회 엔드포인트를 확인하세요. 결과 링크는 60일 동안 유지되며, 작업 기록이 만료되어도 영향을 받지 않습니다. |

### 모델 상태

| HTTP | code | 원인 | 조치 |
| --- | --- | --- | --- |
| **400** | `model_not_found` | 모델 ID의 철자가 틀렸거나, `model`이 없거나, 이 엔드포인트에서 쓸 수 없는 모델입니다(예: 이미지 엔드포인트에 채팅 모델을 지정한 경우). | `GET /v1/models` 또는 [API 레퍼런스](https://nezhagate.com/ko/docs/api)에 있는 모델 ID를 쓰세요. |
| **400** | `model_disabled` | 판매가 끝난 모델입니다. | 다른 모델로 바꾸세요. 대체 모델이 있으면 기존 모델의 페이지가 그 모델로 자동 이동합니다. |
| **400** | `model_coming_soon` | 공개는 되었지만 아직 호출할 수 없는 모델입니다. | 출시 공지를 기다려 주세요. |
| **503** | `model_maintenance` | 모델이 점검 중이라 요청을 받지 않습니다. 점검 중에는 다른 모델이 대신 답변하지도 않습니다. | 점검이 끝나면 다시 시도하세요. 진행 상황은 [서비스 상태](https://nezhagate.com/ko/status)에서 볼 수 있습니다. |

### 용량과 크레딧 예약

| HTTP | code | 원인 | 조치 |
| --- | --- | --- | --- |
| **402** | `insufficient_quota` | 이 요청의 크레딧을 예약하기에 잔액이 부족합니다. 게이트웨이는 요청에 들 수 있는 최대 비용을 먼저 예약하므로, 동시 호출이 몰릴 때도 이 오류가 날 수 있습니다. | 동시 호출이 몰린 경우라면 몇 초 기다린 뒤 다시 시도하고, 그렇지 않으면 충전하세요. |
| **429** | `rate_limit_exceeded` | 이 모델의 모든 회선이 가득 차서 요청을 보내지 못했습니다. 채팅 엔드포인트에서만 나오며, 이미지와 영상 작업은 거절되지 않고 대기열에서 기다립니다. OpenAI 호환 엔드포인트는 `Retry-After`를 붙입니다. | `Retry-After`의 초만큼 기다린 뒤 다시 시도하세요. |
| **503** | `service_unavailable` | 참조 소재 저장 실패처럼 당사 측에서 일시적인 오류가 났습니다. `/v1/responses`는 잠시 쓸 수 있는 회선이 없을 때도 `Retry-After`와 함께 503을 돌려줍니다. | 잠시 후 다시 시도하세요. |

### 업스트림 오류(채팅 엔드포인트)

채팅 엔드포인트(`/v1/chat/completions`, `/v1/responses`, `/anthropic/v1/messages`)에서 오류가 나면 게이트웨이가 먼저 다른 회선으로 재시도하고, 모든 회선이 실패했을 때만 아래 오류 중 하나를 돌려줍니다. 이 오류들은 모두 과금되지 않습니다.

| HTTP | code | 원인 | 조치 |
| --- | --- | --- | --- |
| **502** | `upstream_timeout` | 회선이 시간 초과되었습니다. 첫 토큰이 너무 늦게 오는 경우도 포함됩니다. | 다시 시도하세요. 긴 답변에는 `stream: true`를 쓰세요. |
| **502** | `upstream_unavailable` | 회선에 오류가 났거나 잠시 쓸 수 없습니다. | 잠시 후 다시 시도하세요. |
| **502** | `upstream_busy` | 모델 제공사가 요청 빈도를 제한하고 있습니다. 모델 제공사의 429는 그대로 전달되지 않고 이 502로 바뀌어 돌아옵니다. | 잠시 후 다시 시도하세요. |
| **502** | `upstream_connection_error` | 모델 제공사와의 연결이 끊겼습니다. | 다시 시도하세요. |
| **502** | `empty_completion` | 모델 제공사가 빈 내용을 돌려주었습니다. 요청 내용에 대한 판정이 아닙니다. | 다시 시도하세요. |
| **502** | `upstream_error` | 회선이 답변 대신 오류 문장을 돌려주었습니다. | 다시 시도하세요. |
| **400** | `content_filter` | 모델이 요청을 처리했지만 아무 내용도 돌려주지 않았습니다. 대개 내용이 모델 제공사의 안전 정책에 걸린 경우입니다. | 프롬프트를 고친 뒤 다시 시도하세요. 같은 프롬프트는 대개 다시 거부됩니다. |
| **502** | `upstream_prompt_filter` | 모델 제공사의 사전 검사에서 프롬프트가 차단되었습니다. 모델이 내용을 판정한 것은 아닙니다. | 잠시 후 다시 시도하거나, 표현을 조금 바꿔 보세요. |
| **4xx** | — | 모델 제공사가 요청을 거부했습니다(예: 컨텍스트가 너무 길거나 지원하지 않는 파라미터). 상태 코드와 `message`는 모델 제공사가 보낸 그대로 전달되며, `code`는 모델 제공사가 준 값입니다. | `message`에 적힌 대로 요청을 고치세요. |

### Anthropic 엔드포인트

- `error.type`은 상태 코드별로 Anthropic의 표준 값을 따릅니다: 400 `invalid_request_error`, 401 `authentication_error`, 402 `billing_error`, 403 `permission_error`, 404 `not_found_error`, 429 `rate_limit_error`(모든 회선이 가득 찼을 때는 `overloaded_error`), 502 `api_error`, 503 `overloaded_error`(모델 점검 중에는 `api_error`).

- `/anthropic`은 Claude 모델만 받습니다. 다른 모델을 지정하면 404 `not_found_error`가 돌아옵니다.

- `max_tokens`는 필수이며 0이면 안 됩니다. 그렇지 않으면 400이 돌아옵니다.

## 작업 실패(이미지와 영상)

이미지나 영상 작업이 접수된 뒤 실패해도 작업 조회 엔드포인트는 HTTP 200과 함께 `status` `failed`를 돌려줍니다. 사유는 `error`에 있고, `error.code`는 아래 값 중 하나입니다. 이 작업에 예약된 크레딧은 잔액으로 돌아옵니다. 웹훅을 쓰면 같은 내용의 `image.failed` / `video.failed`를 받습니다.

| 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`에 있습니다. | 다시 제출하면 대개 성공합니다. |

[비동기 작업 전체 안내 →](https://nezhagate.com/ko/docs/guide/async-jobs)

## 스트리밍 도중의 오류

- 스트리밍 요청이 HTTP 200으로 응답하고 출력을 시작한 뒤에는, 오류가 나도 상태 코드가 바뀌지 않습니다. 오류는 `error`가 담긴 데이터 이벤트로 오거나, 스트림이 그냥 끝납니다.

- 스트림이 끝까지 왔는지 확인하세요. OpenAI 형식은 `finish_reason`과 `data: [DONE]`, Anthropic 형식은 `message_stop`을 받았는지 봅니다. 받지 못했다면 중간에 끊긴 것으로 처리하세요.

- 중간에 끊긴 스트림은 끊기기 전까지 생성된 사용량만큼 과금됩니다. 자세한 내용은 [과금과 환불](https://nezhagate.com/ko/docs/guide/billing)을 보세요.

## 오류가 나도 과금되나요?

- 오류를 돌려준 요청은 과금되지 않습니다. 과금 전에 오류가 나거나, 예약된 크레딧이 전액 돌아옵니다.

- 실패한 이미지·영상 작업은 전액 환불됩니다.

- 유일한 예외는 이미 출력을 시작한 스트리밍입니다. 중간에 끊기면 끊기기 전까지 생성된 사용량만큼 과금됩니다.

## 언제 재시도하나요

4xx는 먼저 요청을 고치세요(402는 충전, 429는 잠시 대기). 5xx 오류와 시간 초과는 그대로 재시도해도 되며, 지수 백오프로 간격을 두고 `Retry-After`가 있으면 그 시간만큼 기다리세요.

[재시도 전략과 예제 코드 →](https://nezhagate.com/ko/docs/guide/rate-limits#retry)
