API를 호출할 때 401 Unauthorized, 403 Forbidden, 429 Too Many Requests는 가장 흔한 세 가지 오류입니다.
이들은 모두 4xx 클라이언트 오류에 속하지만, 해결 방법은 완전히 다릅니다:
- 401: 인증 실패——서버가 “당신이 누구인지” 확인할 수 없음
- 403: 접근 거부——서버가 요청을 이해하지만 실행을 허용하지 않음
- 429: 요청 과다——호출 속도 또는 사용량이 제한을 초과함
오류 유형을 구분하지 않고 바로 재시도하면 문제를 해결할 수 없을 뿐만 아니라, 재시도 폭풍을 만들 수 있으며 심지어 인터페이스가 추가로 제한될 수도 있습니다.
이 글에서는 점검 목록, curl 명령 및 Node.js 코드를 통해 이 세 가지 오류가 발생했을 때 어떻게 처리해야 하는지 설명합니다.
¶ 표 하나로 이해하는 401, 403 및 429
| 상태 코드 | 핵심 의미 | 일반적인 원인 | 즉시 재시도해야 하는지 |
|---|---|---|---|
401 Unauthorized |
유효한 인증 자격 증명이 없음 | API Key 오류, Token 만료, 인증 헤더 형식 오류 | 원래대로 재시도하면 안 됨 |
403 Forbidden |
서버가 요청 처리를 거부함 | 권한 부족, IP 또는 지역 제한, 리소스 정책, WAF 차단 | 일반적으로 하면 안 됨 |
429 Too Many Requests |
요청이 속도 또는 할당량 제한을 초과함 | 동시성 과다, QPS 초과, 할당량 소진 | 기다린 후 재시도 |
주의할 점은, 401의 영문 명칭은 Unauthorized이지만 실제 문제 해결 시에는 “인증을 통과하지 못함”으로 이해하는 것이 더 적절하며, 403이 일반적으로 권한 부여 또는 접근 정책과 관련됩니다.
¶ 1. 401 Unauthorized: 먼저 인증 자격 증명을 확인하세요

HTTP 401 Unauthorized는 요청에 대상 리소스에 필요한 유효한 인증 자격 증명이 없음을 의미합니다. 표준 401 응답은 일반적으로 WWW-Authenticate 응답 헤더를 통해 클라이언트에 서버가 기대하는 인증 방식을 알려 줍니다.
¶ 일반적인 원인
Authorization요청 헤더를 전송하지 않음- API Key 또는 Access Token 입력 오류
- Token이 만료되었거나 취소됨
- 인증 방식 오류, 예를 들어
Bearer누락 - 환경 변수가 올바르게 로드되지 않음
- 잘못된 환경의 키를 사용함
- 요청이 프록시 또는 게이트웨이를 통해 전달될 때 인증 헤더가 손실됨
- 클라이언트와 서버 간 시간 차이가 너무 커서 서명이 무효화됨
¶ 일반적인 오류
인터페이스에 Bearer Token이 필요합니다:
Authorization: Bearer YOUR_ACCESS_TOKEN
하지만 클라이언트가 다음과 같이 전송했습니다:
Authorization: YOUR_ACCESS_TOKEN
또는 잘못하여 다음과 같이 작성했습니다:
Authentication: Bearer YOUR_ACCESS_TOKEN
이 두 경우 모두 401을 반환할 수 있습니다.
¶ curl을 사용한 점검
처음부터 응답 본문만 보지 마세요. -i를 추가하여 상태 코드와 응답 헤더를 함께 확인하세요:
curl -i "https://api.example.com/v1/profile" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Accept: application/json"
중점적으로 확인할 사항:
- 실제로
Authorization을 전송했는지 - 인증 방식이 올바른지
- Token 앞뒤에 공백 또는 줄 바꿈이 있는지
WWW-Authenticate가 무엇을 반환하는지- 응답 본문에
invalid_token,expired_token등의 오류 정보가 있는지
더 자세한 요청 과정을 확인해야 하는 경우 다음을 사용할 수 있습니다:
curl -v "https://api.example.com/v1/profile" \
-H "Authorization: Bearer $API_TOKEN"
주의: 디버그 로그에는 Token이 포함될 수 있습니다. 전체 로그를 공개 Issue, 그룹 채팅 또는 티켓에 직접 보내지 마세요.
¶ 401은 재시도해야 하나요?
동일한 유효하지 않은 자격 증명으로 반복 재시도하지 마세요.
권장 절차:
- 요청에 인증 정보가 포함되어 있는지 확인합니다
- Token이 만료되었는지, 취소되었는지 또는 복사 오류가 있는지 확인합니다
- Refresh Token을 지원하는 경우, 액세스 토큰을 한 번 갱신합니다
- 새 Token으로 한 번 재시도합니다
- 여전히 401이 반환되면 재시도를 중지하고 오류를 기록합니다
고정 API Key를 사용하는 경우에는 키 상태를 확인하거나 유효한 키로 교체해야 하며, 반복 재시도해서는 안 됩니다.
¶ 2. 403 Forbidden: 인증은 성공했을 수 있지만, 요청은 여전히 거부됨

403 Forbidden은 서버가 이미 요청을 이해했지만 처리를 거부했음을 의미합니다. 일반적인 권한 오류의 경우, 다시 인증하더라도 문제가 자동으로 해결되지는 않습니다. 요청 또는 권한 구성을 수정하지 않으면 반복 요청은 일반적으로 계속 실패합니다.
¶ 일반적인 원인
- 현재 계정 또는 키에 필요한 권한이 없음
- Token의 Scope가 부족함
- 현재 프로젝트 또는 테넌트에 속하지 않은 리소스에 접근함
- 원본 IP가 허용 목록에 없음
- 요청 원본 지역이 제한됨
- WAF, 방화벽 또는 악용 방지 시스템에 의해 차단됨
- 계정, 프로젝트 또는 리소스가 비활성화됨
- 인터페이스가 특정 요금제 또는 역할에만 공개됨
- 서명은 유효하지만 서명 내 리소스 범위가 일치하지 않음
- 브라우저 환경에서 CSRF, Origin 또는 Referer 정책 제한을 받음
¶ 401과 403의 차이
두 문장으로 빠르게 판단할 수 있습니다:
- 401: 서버가 당신이 누구인지 확인할 수 없음
- 403: 서버가 당신의 신원과 요청을 알거나 이해하지만, 그렇게 하는 것을 허용하지 않음
예를 들어, 일반 구성원이 유효한 Token을 가지고 “조직 삭제” 인터페이스를 호출합니다:
curl -i -X DELETE \
"https://api.example.com/v1/organizations/org_123" \
-H "Authorization: Bearer $API_TOKEN"
Token은 유효하지만 계정이 조직 관리자가 아닌 경우, 서버는 다음을 반환할 수 있습니다:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "insufficient_permissions",
"message": "Administrator role is required."
}
¶ 403 점검 순서
¶ 1. 응답 본문 읽기
오류 코드, 누락된 역할 또는 권한, 필요한 Scope, 거부된 리소스 ID, 그리고 IP, 지역 또는 보안 정책 관련 안내를 우선적으로 찾으세요.
¶ 2. Token Scope 확인
예를 들어, 데이터를 읽는 데는 data:read만 필요할 수 있지만 데이터를 생성하는 데는 data:write가 필요할 수 있습니다. Token이 유효하다고 해서 모든 권한을 가지고 있다는 의미는 아닙니다.
¶ 3. 리소스 소속 확인
프로젝트 ID, 조직 ID, 테넌트 ID, 사용자 ID, 지역 또는 환경이 현재 자격 증명과 동일한 계정 또는 권한 범위에 속하는지 확인하세요.
¶ 4. 네트워크 및 보안 정책 확인
로컬에서 코드가 정상적으로 실행되지만 배포 후 403을 반환한다면, 서버의 출구 IP, IP 화이트리스트, 프록시 구성, CDN, WAF, 요청 출발 지역 및 관련 요청 헤더를 중점적으로 확인해야 합니다.
¶ 상태 코드만으로 결론 내리지 마세요
현실의 API 구현은 반드시 완전히 일치하지는 않습니다. 일부 플랫폼은 속도 제한이 발생했을 때 429 대신 403을 반환할 수 있습니다. 예를 들어, GitHub REST API는 속도 제한을 초과했을 때 403 또는 429를 반환할 수 있으며, x-ratelimit-remaining, x-ratelimit-reset 또는 retry-after 등의 응답 헤더를 통해 제한 상태를 설명합니다.
따라서 403을 만났을 때는 다음도 종합적으로 확인해야 합니다.
- 응답 본문의 비즈니스 오류 코드
Retry-AfterX-RateLimit-RemainingX-RateLimit-Reset- 플랫폼 공식 문서
¶ 403은 재시도해야 하나요?
권한 부족, IP 제한 또는 리소스 정책으로 인한 403의 경우, 그대로 재시도하는 것은 의미가 없습니다.
올바른 방법은 권한 또는 Token Scope를 수정하고, 올바른 계정 또는 프로젝트를 사용하며, 화이트리스트와 보안 정책을 조정하고, 리소스 ID를 수정한 뒤, 서비스 또는 요금제가 해당 인터페이스를 지원하는지 확인하는 것입니다. 플랫폼이 해당 403이 실제로 일시적인 속도 제한을 의미한다고 명확히 설명하는 경우에만 속도 제한 규칙에 따라 기다린 후 재시도해야 합니다.
¶ 3. 429 Too Many Requests: 먼저 속도를 줄이고, 그다음 재시도

429 Too Many Requests는 클라이언트가 일정 시간 동안 너무 많은 요청을 보냈다는 것, 즉 속도 제한이 발생했다는 것을 의미합니다. 서버는 Retry-After를 통해 클라이언트에게 재시도하기 전에 얼마나 기다려야 하는지 알릴 수 있습니다.
¶ 일반적인 원인
- 짧은 시간 내 요청 횟수가 너무 많음
- 동시 작업 수가 너무 많음
- 여러 서비스가 동일한 API Key를 공유함
- 재시도 코드에 대기 메커니즘이 없음
- 정기 작업이 같은 시점에 집중적으로 실행됨
- 요청에 캐시가 없어 같은 데이터를 반복적으로 가져옴
- 단일 계정, IP 또는 프로젝트의 할당량이 소진됨
- 요금제 사용량 또는 잔액 제한
¶ 먼저 응답 헤더를 확인하세요
curl -i "https://api.example.com/v1/resources" \
-H "Authorization: Bearer $API_TOKEN"
중점적으로 확인할 항목:
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1710000000
플랫폼마다 응답 헤더 이름이 다를 수 있으므로, 플랫폼 문서를 기준으로 해야 합니다.
¶ Retry-After를 올바르게 처리하기
Retry-After의 일반적인 두 가지 형식은 초 단위 또는 HTTP 날짜입니다.
Retry-After: 30
Retry-After: Wed, 21 Oct 2026 07:28:00 GMT
클라이언트는 두 형식을 모두 호환하는 것이 좋습니다.
¶ 왜 즉시 재시도할 수 없나요?
요청이 속도 제한된 후 즉시 재시도하면 새 요청도 계속 속도 제한됩니다. 여러 클라이언트가 동시에 재시도하여 “thundering herd” 현상이 발생할 수도 있습니다. 더 안정적인 방법은 다음과 같습니다.
- 우선
Retry-After를 따릅니다 - 명확한 대기 시간이 없으면 지수 백오프를 사용합니다
- 대기 시간에 무작위 지터를 추가합니다
- 최대 재시도 횟수를 설정합니다
- 상한에 도달한 후 명확한 오류를 반환합니다
¶ Node.js: 429 안전하게 처리하기
아래 예시는 Node.js 내장 fetch를 사용하며, Retry-After를 우선 파싱합니다. 서버가 해당 응답 헤더를 제공하지 않으면 무작위 지터를 포함한 지수 백오프를 사용합니다.
const sleep = (ms) =>
new Promise((resolve) => setTimeout(resolve, ms));
function parseRetryAfter(value) {
if (!value) return null;
const seconds = Number(value);
if (Number.isFinite(seconds) && seconds >= 0) {
return seconds * 1000;
}
const timestamp = Date.parse(value);
if (!Number.isNaN(timestamp)) {
return Math.max(0, timestamp - Date.now());
}
return null;
}
async function fetchWithRetry(url, options = {}, config = {}) {
const {
maxRetries = 3,
baseDelayMs = 1000,
maxDelayMs = 30_000,
} = config;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
if (attempt === maxRetries) {
throw new Error(
`请求持续受到限流,已达到最大重试次数:${maxRetries}`
);
}
const retryAfterMs = parseRetryAfter(
response.headers.get("retry-after")
);
const backoffCap = Math.min(
maxDelayMs,
baseDelayMs * 2 ** attempt
);
const jitterMs = Math.floor(Math.random() * backoffCap);
const delayMs = retryAfterMs ?? jitterMs;
console.warn(
`收到 429,第 ${attempt + 1} 次等待 ${delayMs}ms 后重试`
);
await sleep(delayMs);
}
throw new Error("请求失败");
}
호출 예시:
const response = await fetchWithRetry(
"https://api.example.com/v1/resources",
{
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: "application/json",
},
},
{
maxRetries: 3,
baseDelayMs: 1000,
maxDelayMs: 30_000,
}
);
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`API 请求失败:${response.status} ${errorBody}`);
}
const data = await response.json();
console.log(data);
¶ POST 요청을 재시도할 때는 멱등성도 고려해야 합니다
모든 요청이 자동 재시도에 적합한 것은 아닙니다. GET, HEAD 등의 읽기 요청은 일반적으로 안전하게 재시도하기가 더 쉽습니다. 주문 생성, 결제 차감, 메시지 전송 등의 POST 요청의 경우, 클라이언트가 이미 성공적으로 제출했지만 네트워크 중단으로 응답을 받지 못했을 수 있습니다. 이때 직접 재시도하면 중복 작업이 발생할 수 있습니다.
플랫폼이 지원한다면, 쓰기 작업에 멱등성 키를 설정해야 합니다.
curl -i -X POST "https://api.example.com/v1/orders" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-20260331-0001" \
-d '{
"product_id": "prod_123",
"quantity": 1
}'
멱등성 키의 요청 헤더 이름과 유효 기간은 구체적인 플랫폼에 따라 결정되므로, 해당 API 문서를 참고해야 합니다.
¶ 프로덕션 환경 문제 해결 체크리스트
¶ 401을 받은 경우
- 요청에 인증 헤더가 포함되어 있는가
-
Bearer등의 인증 방식이 올바른가 - Token이 만료되었거나, 취소되었거나, 잘못 복사되었는가
- 올바른 환경의 키를 사용하고 있는가
- 프록시 또는 게이트웨이가 인증 헤더를 제거했는가
- 클라이언트 시간이 정확한가
- Token을 한 번 안전하게 갱신할 수 있는가
¶ 403을 받은 경우
- Token에 필요한 Scope가 있는지
- 현재 계정에 필요한 역할이 있는지
- 리소스가 현재 프로젝트 또는 테넌트에 속하는지
- 출구 IP가 화이트리스트에 있는지
- 지역, WAF 또는 보안 정책 제한을 받는지
- 응답 본문에 권한 또는 속도 제한이 명확히 표시되는지
- 응답 헤더에 속도 제한 정보가 있는지
¶ 429를 받은 경우
-
Retry-After를 읽었는지 - 속도 제한 관련 응답 헤더를 확인했는지
- 지수 백오프와 무작위 지터를 사용하는지
- 최대 재시도 횟수를 설정했는지
- 동시성을 제한하는지
- 중복 조회 결과를 캐시하는지
- 요청을 병합하거나 일괄 전송할 수 있는지
- 요금제 할당량 또는 계정 사용량 한도에 도달했는지
¶ 어떤 진단 정보를 기록하는 것이 좋은가?
로그에 요청 시간, API 호스트명과 경로, HTTP 메서드, 상태 코드, 비즈니스 오류 코드, request-id 또는 trace-id, Retry-After, 속도 제한 잔여 할당량과 재설정 시간, 재시도 횟수와 실제 대기 시간을 기록하는 것이 좋습니다.
하지만 전체 API Key, 전체 Access Token, Refresh Token, 사용자 비밀번호 또는 요청 본문의 민감한 개인 정보는 기록하지 마세요.
¶ 최종 판단 요령
401: 먼저 자격 증명을 수정하고, 이전 Token으로 재시도하지 마세요.
403: 먼저 권한 또는 정책을 수정하고, 동일한 요청을 그대로 반복하지 마세요.
429: 대기 시간을 준수하고, 백오프, 속도 감소, 동시성 제한을 적용하세요.
상태 코드는 문제 해결의 시작점일 뿐, 모든 답은 아닙니다. 신뢰할 수 있는 처리 방식은 HTTP 상태 코드, 응답 본문의 비즈니스 오류 코드, 관련 응답 헤더, 플랫폼 문서, 요청 ID 및 서버 로그를 함께 결합하여 “인터페이스 오류”를 관찰 가능하고, 복구 가능하며, 유지보수 가능한 엔지니어링 메커니즘으로 바꾸는 것입니다.

