에러 코드
봇 API 는 실패를 HTTP 상태 코드와 detailCode 로 알려줘요. 같은 상태 코드라도
대처가 다른 경우가 있으니 detailCode 까지 보고 분기해요.
detailCode 가 없는 응답도 있어요. 토큰 · 권한 · 호출 한도는 앞단 게이트웨이가 먼저 검사해서,
여기서 막힌 401 · 403 · 429 에는 detailCode 가 없어요. 먼저 상태 코드로 가르고,
detailCode 는 있을 때만 보세요.
응답 형식
봇 런타임 API 는 RFC 7807 형식으로 응답해요.
{
"instance": "/v1/live",
"status": 404,
"title": "Not Found",
"detailCode": "OAPI_MNGR_0301",
"message": "The DJ is not broadcasting right now.",
"userId": null,
"extra": null,
"timestamp": 1788407119681
}
| 필드 | 설명 |
|---|---|
detailCode | 대처를 정하는 값이에요. 이 값이 계약이에요. |
message | 사람이 읽는 설명이에요. 문구는 바뀔 수 있으니 이 값으로 분기하지 마세요. 서비스 언어와 일치한다는 보장도 없으니 청취자에게 그대로 보여주지 말고, 노출 문구는 봇에서 만들어요. |
status | HTTP 상태 코드예요. |
title | HTTP 상태의 표준 문구예요 (Not Found · Forbidden …). 분기에 쓰지 마세요. |
instance | 요청한 경로예요. |
userId · extra | 사내 프레임워크가 채우는 자리예요. 봇 런타임 API 에서는 항상 null 로 실려 나가요. |
timestamp | 응답을 만든 시각이에요. epoch milliseconds 예요. |
userId · extra 는 봇이 쓸 값이 아니에요. 다만 null 로라도 응답에 들어 있으니,
스키마를 additionalProperties: false 로 잠그면 여기서 깨져요. 모르는 필드는 무시하도록 두세요.
이벤트 스트림의 오류는 SSE 프레임으로 와요. GET /v1/live/events 는 text/event-stream 으로 응답하기 때문에, 연결에 실패해도
본문이 data: 로 시작해요. 본문을 그대로 JSON 파싱하면 터져요.
HTTP 상태 코드로 판별하고, 본문이 필요하면 data: 를 떼고 파싱해요.
HTTP/2 404
content-type: text/event-stream;charset=UTF-8
data:{"status":404,"detailCode":"OAPI_MNGR_0301","message":"The DJ is not broadcasting right now."}
봇이 마주치는 코드
/v1/oauth 로 시작하는 둘을 뺀 모든 엔드포인트에서 나오는 코드예요. 어떤 엔드포인트가
있는지는 엔드포인트 목록 에 있어요.
400 — 요청을 고쳐야 해요
detailCode | 무슨 일인가요 | 어떻게 하나요 |
|---|---|---|
OAPI_MNGR_0103 | 요청이 잘못됐어요. 본문이 깨졌거나, 필수 필드·Content-Type 이 빠졌거나, 메서드가 잘못됐어요. | 요청 형식을 고쳐요. 같은 요청을 다시 보내도 결과가 같아요. |
OAPI_MNGR_0108 | 메시지가 비어 있어요. 공백만 있는 경우도 포함해요. | 보낼 내용을 확인해요. 같은 요청을 다시 보내도 결과가 같아요. |
OAPI_MNGR_0109 | 메시지가 200자를 넘었어요. 이모지는 2자로 세어질 수 있어요. | 길이를 줄여요. |
401 — 사람이 다시 연동해야 해요
detailCode | 무슨 일인가요 | 어떻게 하나요 |
|---|---|---|
| 없음 | 토큰이 없거나 무효해요. 만료 · 폐기 · DJ 의 연동 해제 · 앱 정지가 모두 여기예요. | refresh 를 한 번 시도하고, 그래도 401 이면 멈추고 DJ 에게 재연동을 안내해요. |
OAPI_MNGR_0203 | 위와 같아요. 토큰이 폐기된 직후 1분 안에는 이 코드로 올 수 있어요. | 위와 같아요. |
401 에 무한 재시도하지 마세요. 연동이 끊긴 상태에서는 몇 번을 다시 보내도 계속 실패하면서 호출 한도만 소진해요.
이유를 나눠서 알려주지 않는 이유는, 나누면 토큰이 존재하는지 여부가 밖으로 새기 때문이에요. 봇의 대처는 어느 경우든 재연동으로 같아요.
403 — 권한이나 상태 때문에 막혔어요
detailCode | 무슨 일인가요 | 어떻게 하나요 |
|---|---|---|
| 없음 | 이 엔드포인트에 필요한 권한이 없어요. | 재동의만 받으면 돼요. 연동 자체는 살아 있어요. |
OAPI_MNGR_0208 | 지금은 이 방송에 채팅을 보낼 수 없어요. 채팅이 동결됐거나 봇이 채팅 금지 상태예요. | 두 경우가 구분되지 않아요. 잠시 뒤 다시 시도하고, 계속 막히면 DJ 에게 안내해요. |
OAPI_MNGR_0209 | 방송에 입장할 수 없어요. DJ 가 봇을 차단했을 수 있어요. | 재동의로도 재연동으로도 풀리지 않아요. DJ 가 직접 풀어줘야 해요. |
403 은 detailCode 유무로 갈라요. 없으면 권한 부족, 있으면 방송 쪽 상태(0208 · 0209)예요.
401 과 403 을 뭉뚱그리지 마세요. 401 은 DJ 가 처음부터 다시 연동해야 하고, 권한 부족 403 은 권한만 추가로
받으면 돼요. 같이 처리하면 멀쩡한 연동을 끊게 돼요.
404 — 방송 중이 아니에요
detailCode | 무슨 일인가요 | 어떻게 하나요 |
|---|---|---|
OAPI_MNGR_0301 | DJ 가 지금 방송 중이 아니에요. | 오류가 아니라 상태예요. 이벤트 스트림이면 백오프 후 다시 연결하고, 조회면 잠시 뒤 다시 불러요. |
429 — 너무 자주 불렀어요
detailCode | 무슨 일인가요 | 어떻게 하나요 |
|---|---|---|
OAPI_MNGR_0302 | 채팅 전송이 너무 잦아요. | 잠시 기다렸다 다시 보내요. |
OAPI_MNGR_0302 는 봇이 과하게 보냈을 때뿐 아니라 그 방송의 채팅이 전체적으로 몰릴 때도
와요. 한도를 방송 전체가 나눠 쓰기 때문에, 봇 자신의 속도를 줄여도 바로 풀리지 않을 수 있어요.
detailCode 없이 429 만 오는 경우는 API 호출 한도예요. 이때는 본문이 비어 있어요.
본문을 파싱하면 터지니 상태 코드로만 판별하세요. 한도는 두 가지예요.
| 한도 | 넘으면 | 다시 부를 때 |
|---|---|---|
| 초당 | 429, 본문·Retry-After 없음 | 지수 백오프로 다시 시도해요. |
| 하루 | 429, 본문 없음 · Retry-After 있음 | Retry-After 초만큼 기다려요. 다음 리셋(자정)까지예요. |
하루 한도는 앱 총량과 권한별로 따로 세고, 둘 중 하나라도 넘으면 막혀요. 어느 쪽에 걸렸는지는
x-ratelimit-daily-scope 헤더로 와요 — 있으면 그 권한만, 없으면 앱 전체가 막힌 거예요.
응답 헤더로 미리 속도를 줄여요
한도 헤더는 정상 응답에도 붙어요. 429 를 맞기 전에 이 값을 보고 속도를 줄이세요.
| 헤더 | 뜻 |
|---|---|
x-ratelimit-remaining | 지금 남은 초당 토큰이에요. 0 이면 다음 요청이 429 예요. |
x-ratelimit-replenish-rate | 초당 채워지는 토큰 수예요. |
x-ratelimit-burst-capacity | 순간에 몰아 쓸 수 있는 상한이에요. |
x-ratelimit-daily-limit | 하루 호출 상한이에요. |
x-ratelimit-daily-remaining | 오늘 남은 호출 수예요. |
x-ratelimit-daily-reset | 다음 리셋 시각이에요. epoch seconds 예요. |
x-ratelimit-daily-scope | 하루 한도 429 에만 와요. 걸린 권한이에요. |
이벤트 스트림(GET /v1/live/events)에는 한도 헤더가 없어요 — 이 경로는 호출 한도를 세지 않아요.
초당 한도는 DJ 연동마다 따로이고, 하루 한도는 앱 합산이에요. 연동을 늘려도 하루 총량은 커지지 않아요.
오늘 사용량은 개발자 센터 콘솔의 앱 상세에서도 볼 수 있어요.
500 — 서버 오류예요
detailCode | 무슨 일인가요 | 어떻게 하나요 |
|---|---|---|
OAPI_MNGR_1002 | 서버 오류예요. | 지수 백오프로 재시도해요. 요청을 고쳐도 달라지지 않아요. |
어느 엔드포인트에서든 날 수 있어요. 502(OAPI_MNGR_2302 · OAPI_MNGR_2303)와 달리
어디서 났는지 알려주지 않으니, 봇이 할 일은 지수 백오프 재시도 하나예요.
502 — 잠시 후 다시 시도해요
detailCode | 무슨 일인가요 | 어떻게 하나요 |
|---|---|---|
OAPI_MNGR_2302 | 방송 서버 오류예요. | 지수 백오프로 재시도해요. |
OAPI_MNGR_2303 | 채팅 서버 오류예요. | 지수 백오프로 재시도해요. |
토큰 엔드포인트의 에러는 형식이 달라요
POST /v1/oauth/token 과 POST /v1/oauth/revoke 는 OAuth 표준(RFC 6749) 형식을 그대로 써요.
detailCode 가 아니라 error 예요. 전체 명세는 토큰 에 있어요.
{
"error": "invalid_grant",
"error_description": "code is expired"
}
error | HTTP | 무슨 일인가요 | 어떻게 하나요 |
|---|---|---|---|
invalid_request | 400 | 필수 파라미터가 빠졌어요. | 요청 형식을 확인해요. |
invalid_client | 401 | Client ID 나 Client Secret 이 맞지 않아요. | 자격 증명을 확인해요. |
invalid_grant | 400 | code 나 refresh_token 이 만료됐거나 이미 썼어요. | code 는 60초 안에 써야 해요. refresh_token 이 만료됐으면 DJ 에게 재연동을 안내해요. |
unsupported_grant_type | 400 | 지원하지 않는 grant_type 이에요. | authorization_code 또는 refresh_token 을 써요. |