오류 처리
실패한 요청은 HTTP 상태 코드와, 오류 유형 및 메시지가 담긴 JSON 본문을 반환합니다. 이 페이지는 모든 상태를 나열하고, API 형식별 오류 본문을 보여 주며, 어떤 오류를 다시 보낼 가치가 있는지 설명합니다.
SDK는 상태 코드와 본문 필드를 담은 오류 객체를 발생시킵니다. 이 요청은 존재하지 않는 모델을 지정합니다:
import openai
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com/v1",
)
try:
client.chat.completions.create(
model="no-such-model",
messages=[{"role": "user", "content": "Hello"}],
)
except openai.APIStatusError as error:
print(error.status_code) # 400
print(error.type) # invalid_request_error
print(error.body["message"]) # unknown model: no-such-model
print(error.response.headers.get("x-request-id")) # the id of this request import OpenAI from "openai";
const client = new OpenAI({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com/v1",
});
try {
await client.chat.completions.create({
model: "no-such-model",
messages: [{ role: "user", content: "Hello" }],
});
} catch (error) {
if (!(error instanceof OpenAI.APIError)) throw error;
console.log(error.status); // 400
console.log(error.type); // invalid_request_error
console.log(error.error.message); // unknown model: no-such-model
} curl -i https://api.shannon-ai.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "no-such-model",
"messages": [{"role": "user", "content": "Hello"}]
}' 상태 400과 다음 본문을 반환합니다:
{
"error": {
"type": "invalid_request_error",
"message": "unknown model: no-such-model"
}
} 오류 본문
본문은 두 가지입니다. /v1/messages는 Anthropic 형식으로 응답하고, 그 외 모든 엔드포인트는 OpenAI 형식으로 응답합니다. 두 형식 모두 세부 내용은 error.type과 error.message에 있으므로 하나의 리더로 모든 엔드포인트를 처리할 수 있습니다.
{
"error": {
"type": "authentication_error",
"message": "Invalid API key",
"code": "invalid_api_key",
"param": null
}
} {
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key"
}
} | 필드 | 유형 | 설명 |
|---|---|---|
error.type | string | 오류의 종류입니다. 아래의 오류 유형을 참조하세요. |
error.message | string | 사람이 읽는 문장입니다. 아래 표의 텍스트는 <id>, <n>, …로 표시된 가변 부분을 제외하고는 일정합니다. |
error.code | string | null | OpenAI 형식의 일부 오류에 있습니다: 401에는 invalid_api_key, 413에는 request_too_large, 대화가 너무 길 때는 context_length_exceeded이며, 그 외에는 null이거나 없습니다. 먼저 상태와 error.type으로 판단하고 code는 부가 정보로 취급하세요. |
error.param | null | code와 같은 오류에 있습니다. 항상 null입니다. |
type | string | Anthropic 형식에만 있습니다. 항상 error입니다. |
오류 유형
| 유형 | 상태 | 설명 |
|---|---|---|
invalid_request_error | 400, 413, 415, 422 | 요청 자체가 잘못되었습니다: 필드, 본문, 모델 id, 또는 모델이 받지 않는 입력. 다시 보내기 전에 요청을 수정하세요. |
authentication_error | 401 | API 키가 없거나, 알 수 없거나, 폐기되었습니다. |
not_found_error | 404 | 경로가 존재하지 않습니다. |
rate_limit_error | 429 | 잔액이 요청을 감당하지 못하거나, 플러드 보호가 차단했거나, 모델이 사용 중입니다. 메시지에 어느 경우인지 나와 있습니다. |
api_error | 500, 502, 503, 504 | 요청은 정상이었고 실패는 저희 쪽에 있습니다. 다시 보내세요. |
server_error | 500 | 요청은 정상이었고 실패는 저희 쪽에 있습니다. 다시 보내세요. |
상태 코드
| 상태 | 유형 | 메시지 | 발생 시점 |
|---|---|---|---|
400 | invalid_request_error | Failed to parse the request body as JSON: … | 본문이 유효한 JSON이 아닙니다. |
400 | invalid_request_error | unknown model: <id> | model이 공개된 id가 아닙니다. /v1/responses에서는 model이 없을 때도 해당됩니다. |
400 | invalid_request_error | No user message provided | Shannon 티어: 요청에 사용자 텍스트가 없습니다. |
400 | invalid_request_error | <id> does not accept image input | 이미지 입력을 지원하지 않는 호스팅 오픈 웨이트 모델에 이미지를 보냈습니다. |
400 | invalid_request_error | audio and video input are not supported by any hosted model | 호스팅 오픈 웨이트 모델에 오디오 또는 비디오 파트를 보냈습니다. |
400 | invalid_request_error | <id> does not accept response_format | 구조화된 출력을 지원하지 않는 호스팅 오픈 웨이트 모델에 구조화된 출력을 요청했습니다. |
400 | invalid_request_error | <id> does not accept response_format json_schema; use json_object | json_object만 받는 호스팅 오픈 웨이트 모델에 JSON 스키마를 보냈습니다. |
400 | invalid_request_error | unknown reasoning effort '<value>'; expected off, low, medium or high | 추론 effort가 나열된 값 중 하나가 아닙니다. 호스팅 오픈 웨이트 모델. |
400 | invalid_request_error | count_tokens is available for the hosted open models; unknown model: <id> | 호스팅 오픈 웨이트 모델이 아닌 모델에 대해 토큰 계산을 요청했습니다. |
401 | authentication_error | Missing authentication | API 키를 보내지 않았습니다. |
401 | authentication_error | Invalid API key | 키를 알 수 없거나, 더 새로운 키로 대체되었거나, 폐기되었습니다. |
404 | not_found_error | Not Found | 경로가 존재하지 않습니다. 예: /v1/embeddings. |
405 | api_error | Method Not Allowed | 경로는 존재하지만 이 HTTP 메서드로는 사용할 수 없습니다. |
413 | invalid_request_error | Failed to buffer the request body: … | 요청 본문이 32 MiB보다 큽니다. |
415 | invalid_request_error | Expected request with `Content-Type: application/json` | Content-Type 헤더가 없거나 application/json이 아닙니다. |
422 | invalid_request_error | Failed to deserialize the JSON body into the target type: … | messages 같은 필수 필드가 없거나, 필드의 JSON 타입이 잘못되었습니다. |
429 | rate_limit_error | Quota exceeded. Upgrade your plan at shannon-ai.com/plan | 요청의 출력 한도가 남은 잔액보다 큽니다. |
429 | rate_limit_error | Too many requests. Retry in <n>s. | 플러드 보호: 계정에서 분당 120건을 초과하는 요청이 있었습니다. <n>은 그 분이 끝날 때까지 남은 초입니다. |
429 | rate_limit_error | Shannon Coder call quota reached. Upgrade your plan at shannon-ai.com/plan | /v1/chat/completions와 /v1/messages의 shannon-coder-1: 현재 4시간 구간에 대한 플랜의 Shannon Coder 호출 횟수를 모두 사용했습니다. |
429 | rate_limit_error | Shannon routes are temporarily busy. Please retry. | 지금 모델이 사용 중입니다. |
500 | server_error | The model backend failed to answer. Please retry. | 모델이 응답을 생성하지 못했습니다. |
500 | server_error | An internal error occurred. Please retry. | 요청을 준비하는 중에 내부 오류가 발생했습니다. |
502 | api_error | The model backend failed to answer. Please retry. | 모델이 응답을 생성하지 못했습니다. Shannon 3 제품군과 호스팅 오픈 웨이트 모델. |
503 | api_error | Could not verify your quota right now. Please retry. | 지금은 잔액을 읽을 수 없습니다. |
503 | api_error | token counting is temporarily unavailable for this model | 지금은 이 모델의 토큰 계산을 사용할 수 없습니다. |
504 | api_error | upstream timed out | 호스팅 오픈 웨이트 모델이 스트리밍이 아닌 요청의 제한 시간보다 오래 걸렸습니다. |
확인은 API 키, 요청 본문, 모델 id, 플러드 보호, 잔액 순서로 진행됩니다. 따라서 키도 본문도 잘못된 요청은 401을 반환합니다. 4xx 상태로 거부된 요청에는 비용이 청구되지 않습니다.
잔액과 플러드 보호
모든 요청은 동등하게 처리됩니다. 속도 티어도, 별도의 API 할당량도 없습니다. 그래도 두 가지 경우에 429로 응답할 수 있으며, 둘 다 유형은 rate_limit_error입니다. 메시지로 구분합니다.
잔액
잔액은 00:00 UTC에 초기화되는 오늘의 플랜 할당량과, 그 뒤에 사용하는 구매 크레딧입니다. 채팅과 API는 같은 잔액에서 차감됩니다.
각 요청은 먼저 출력 한도만큼을 확보해 둡니다. max_tokens이며, /v1/responses에서는 max_output_tokens, 둘 다 보내지 않으면 4,096입니다. 응답이 완료되면 확보해 둔 양은 요청이 실제로 사용한 양으로 대체됩니다. 확보할 양이 남은 잔액에 맞지 않으면 모델이 시작하기 전에 요청이 Quota exceeded를 반환합니다.
동시에 실행되는 요청은 각자 자기 몫을 확보합니다. 따라서 max_tokens가 큰 병렬 요청이 많으면, 각 응답이 짧더라도 잠시 잔액이 소진될 수 있습니다. 더 낮은 max_tokens는 더 적게 확보합니다.
플러드 보호
플러드 보호는 계정당 분당 120건의 요청을 허용합니다. 키가 아니라 계정을 기준으로 /v1/chat/completions, /v1/responses, /v1/messages를 합쳐 집계합니다. 분은 첫 요청부터 시작됩니다.
그 분 안의 121번째 요청은 Too many requests. Retry in <n>s.를 반환하며 비용이 들지 않습니다. 거부된 요청도 집계되므로 기다리지 않고 다시 보내는 클라이언트는 그 분이 끝날 때까지 계속 거부됩니다. 메시지에 나온 <n>초만큼 기다리세요.
병렬로 보낸 요청이 병렬이라는 이유로 거부되지는 않습니다. 대기열에서 기다렸다가 차례로 응답을 받습니다.
GET /v1/models와 토큰 계산 엔드포인트는 집계되지 않습니다.
한도와 잔액에 대한 자세한 내용, 그리고 자신의 잔액과 사용량을 확인할 수 있는 곳: 한도 및 잔액 Keys & usage
재시도할 항목
같은 요청이 성공할 수 있을 때만 다시 보내세요. OpenAI와 Anthropic SDK는 429와 5xx 응답을 이미 자체적으로 두 번 재시도합니다. 이를 바꾸려면 max_retries(JavaScript에서는 maxRetries)를 설정하세요.
| 오류 | 다시 보낼까요? | 방법 |
|---|---|---|
400, 404, 405, 413, 415, 422 | 아니요. 먼저 요청을 수정하세요. | 메시지에 잘못된 필드, 모델 또는 입력이 나와 있습니다. |
401 | 아니요. 유효한 키를 사용하세요. | Keys & usage 페이지에서 키를 만들거나 교체하세요. |
429 Too many requests | 예, 대기 후에. | 메시지에 나온 초만큼 기다린 뒤 같은 요청을 보내세요. |
429 Quota exceeded | 그대로는 안 됩니다. | max_tokens를 낮추거나, 00:00 UTC의 일일 초기화를 기다리거나, 크레딧을 추가하세요. |
429 Shannon Coder call quota reached | 예, 다음 구간에. | 구간은 00:00 UTC부터 4시간마다 시작됩니다. 다른 모델은 즉시 응답합니다. |
429 … temporarily busy | 예. | 몇 초 기다린 뒤, 시도할 때마다 더 오래 기다리세요. |
500, 502, 503 | 예. | 1초를 기다린 뒤 2, 4, 8초 순으로 기다리세요. 몇 번 시도한 뒤에는 멈추고 요청 id를 보고하세요. |
504 | 예. | stream을 true로 설정해 보내세요. 제한 시간은 스트리밍이 아닌 응답에 적용됩니다. |
이 표를 따르는 재시도 루프:
import re
import time
import openai
from openai import OpenAI
# max_retries=0: this loop does the retrying, the SDK does not add its own.
client = OpenAI(api_key="YOUR_API_KEY", base_url="https://api.shannon-ai.com/v1", max_retries=0)
def create_with_retry(attempts=5, **request):
for attempt in range(attempts):
try:
return client.chat.completions.create(**request)
except openai.RateLimitError as error:
text = str(error)
flood = re.search(r"Retry in (\d+)s", text)
if flood:
time.sleep(int(flood.group(1))) # flood protection: wait out the minute
elif "temporarily busy" in text:
time.sleep(2 ** attempt)
else:
raise # balance: a wait of seconds does not help
except (openai.InternalServerError, openai.APIConnectionError):
time.sleep(2 ** attempt) # 1, 2, 4, 8 seconds
raise RuntimeError("the request failed after all attempts")
response = create_with_retry(
model="shannon-3",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content) import OpenAI from "openai";
// maxRetries: 0 — this loop does the retrying, the SDK does not add its own.
const client = new OpenAI({ apiKey: "YOUR_API_KEY", baseURL: "https://api.shannon-ai.com/v1", maxRetries: 0 });
const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));
async function createWithRetry(request, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.chat.completions.create(request);
} catch (error) {
const text = String(error.message);
const flood = text.match(/Retry in (\d+)s/);
if (error.status === 429 && flood) {
await sleep(Number(flood[1])); // flood protection: wait out the minute
} else if (error.status === 429 && text.includes("temporarily busy")) {
await sleep(2 ** attempt);
} else if (error.status >= 500 || error instanceof OpenAI.APIConnectionError) {
await sleep(2 ** attempt); // 1, 2, 4, 8 seconds
} else {
throw error; // wrong request, wrong key or balance
}
}
}
throw new Error("the request failed after all attempts");
}
const response = await createWithRetry({
model: "shannon-3",
messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content); 스트림 안의 오류
스트림은 첫 바이트부터 상태 200입니다. 그 이후의 실패는 스트림 안의 이벤트로 도착합니다. /v1/chat/completions에서는 error 멤버가 있는 청크, /v1/messages에서는 error 이벤트, /v1/responses에서는 response.failed 또는 error 이벤트입니다. 스트림의 마무리 이벤트는 그대로 뒤따릅니다. 이런 응답은 실패로 처리하고, 5xx 상태와 마찬가지로 요청을 다시 보내세요.
모델의 컨텍스트 윈도우보다 긴 대화는 이런 방식으로 보고되며, 유형은 invalid_request_error, 코드는 context_length_exceeded입니다. 다시 보내기 전에 대화를 줄이세요.
정확한 프레임은 스트리밍 페이지에서 설명합니다. 스트리밍
요청 id
오류를 포함한 모든 응답에는 x-request-id 헤더가 있습니다. 이 값은 사용자가 자신의 x-request-id 헤더로 보낸 값이며, 보내지 않았다면 십육진수 문자 12개입니다. 기록해 두었다가 요청에 대한 도움을 요청할 때 함께 알려 주세요.