문서 vs 실제 검증 노트
공식 문서와 실제 API 응답의 불일치(확정·의심)를 기록한다. 각 항목은 검증 하니스(#14, scripts/verify/*)로 실측 후 상태를 갱신하며, 타입은 실제 응답을 우선한다.
상태 범례: 🔴 미검증(의심) · 🟡 검증 보류(자격증명/조건 필요) · 🟢 검증 완료
문서 vs 실제 대조표
| # | 상태 | 대상 | 문서 | 기존 구현 관측 | 판단·조치 |
|---|---|---|---|---|---|
| 1 | 🟡 | 토큰 폐기 경로 | POST /auth/v1/token/revoke | POST /auth/v1/revoke 호출 (동작했던 것으로 추정) | SDK(#5)는 문서 경로로 구현 (AuthClient.revokeToken). 실자격 검증은 보류 — 하니스(#14)에서 두 경로 실호출 확인 후 확정 |
| 2 | 🟢 | 토큰 응답 expiresIn | Type: String ("86400") | 타입도 string으로 정의됨 | 실측(2026-07-22, pnpm verify:token): 실제는 number(86400) — 문서가 틀림. 토큰 엔드포인트도 공통 envelope {code,message,content} 사용 확인. SDK는 양쪽 수용 유지 |
| 3 | 🟡 | 토큰 발급 응답 scope | 발급(authorization_code) 응답엔 없음, 갱신(refresh) 응답엔 있음 | 기존 타입엔 항상 있음 | 갱신 응답 scope 존재 실측 확인(2026-07-22) — 공백 구분 스코프명 나열 문자열 (예: "채널 정보 조회 채팅 메시지 쓰기 …"). 발급 응답은 다음 로그인 시 확인. SDK는 optional 유지 |
| 4 | 🟢 | 세션 목록 disconnectedDate | disconnectedDate (정상 철자) | disconnedtedDate (오타)로 타입 정의 | 실측(2026-07-22): 끊긴 세션에서 disconnectedDate(정상 철자) 확인 — 기존 타입의 오타였음. 연결 중 세션에는 필드 자체가 없음 → SDK 타입 optional 처리 예정(#13) |
| 5 | 🟢(문서상) | 세션 이벤트 타입 | CHAT|DONATION|SUBSCRIPTION | CHAT|DONATION만 정의 (SUBSCRIPTION 누락) | SDK는 3종 전부 + subscribe/unsubscribe/subscription 포함. 실측으로 재확인 |
| 6 | 🟢 | CHAT 이벤트 userRoleCode, chatChannelId | 존재 (2025.07 / 2026.03 추가) | 타입에 둘 다 없음 (구버전) | 실측(2026-07-22, CHAT 이벤트 수신): chatChannelId(짧은 코드, 예 "N2dODq") 존재. userRoleCode는 존재하나 위치가 문서와 다름 — #27 참조. getChatRole 뱃지 추론은 userRoleCode로 대체 가능 |
| 7 | 🟢 | CHAT profile.badges | Type Object[]만 표기, 필드 구성 미기술 | { imageUrl: string }[] 실측 | 실측(2026-07-22): [{ imageUrl: string }] 확정 — 문서 공백을 실측으로 채움. Normalize(#16) 타입에 반영 |
| 8 | 🟢 | CHAT/DONATION emojis | Type Map (key: 식별자, value: URL) | { [key: string]: string }[] — 배열로 정의(모순) | 실측(2026-07-22): plain object(Record<string,string>) 확정 (빈 채팅은 {}) — 기존 배열 타입 정의가 오류 |
| 9 | 🔴 | DONATION payAmount | Type String | string | 문자열 맞는지 실측. 정규화 계층에서 number 파생 필드 제공 검토 |
| 10 | 🟢 | 이벤트 구독/취소 sessionKey 전달 위치 | "Request Param" (쿼리) | 쿼리 파라미터로 전송 (동작 확인됨) | 실측(2026-07-22): POST + 쿼리 sessionKey 동작 확인. SYSTEM(subscribed) 통지 형식도 문서 일치 |
| 11 | 🟢 | 채팅 설정 응답 필드명 | allowSubscriberInFollowerMode | allowSubscriberFollowerMode (In 없음) | 실측(2026-07-22): allowSubscriberInFollowerMode — 문서가 정확, 기존 타입이 오타였음. SDK 타입(#10)은 문서 표기 채택 |
| 12 | 🟢 | 채팅 설정 응답 누락 필드 | chatSlowModeSec, chatEmojiMode 존재 | 타입에 없음 (2025.07 이전 작성) | 실측(2026-07-22): chatSlowModeSec(number)·chatEmojiMode(boolean) 존재 확인. SDK 타입(#10)에 포함 |
| 13 | 🟢 | minFollowerMinute 허용 값 | 0~259200의 13개 값 (2025.12 확장) | 0~43200의 8개 값 | 실측(2026-07-22): 허용 외 값(7) PUT 시 서버가 HTTP 400 + "minFollowerMinute 값이 올바르지 않습니다."로 거부. 문서 제약이 실서버에서도 강제됨 — SDK 사전 검증과 이중 안전망 |
| 14 | 🟢 | 채널 정보 verifiedMark | 존재 (2025.07 추가) | 타입에 없음 | 실측(2026-07-22): boolean으로 존재 확인. SDK 타입(#7 Channel)에 포함 |
| 15 | 🟢 | 세션 목록 page 파라미터 타입 | String 표기 ("0부터 조회") | number로 사용 | 실측(2026-07-22): number로 전송해 정상 동작. Int로 확정 |
| 16 | 🟢 | 활동 제한 목록 조회 파라미터 위치 | GET인데 "Request Body" 표기 (size, next) | (기존 구현 없음) | 실측(2026-07-22): 쿼리 파라미터(size, next)로 정상 동작. 문서의 "Request Body" 표기가 오기 |
| 17 | 🟢 | 활동 제한 목록 응답 래핑 | data[]/page 래핑 표기 없음 (필드 나열만) | (기존 구현 없음) | 실측(2026-07-22): { data: [...], page: { next } } 래핑 확인 — 문서 미기재 구조를 실측으로 확정 |
| 18 | 🟡 | 드롭스 조회 page.from/page.size 쿼리 직렬화 | 중첩 Object로 표기 | (기존 구현 없음) | page.from/page.size(dotted) vs flat 실측 필요. 스코프 없이 호출 시 403 "드롭스 스코프가 필요합니다."만 반환(2026-07-22 실측) — 파라미터 검증 이전에 차단되어 판별 불가. SDK(#11)는 문서 중첩 표기(dotted) 채택, 스코프 확보 시 verify가 자동 실검증 |
| 19 | 🟡 | 드롭스 지급 갱신 응답 | status enum 중 UNAUTHORIZED 오타 포함 | (기존 구현 없음) | UNAUTHORIZED로 구현. 법인 자격 필요 → 검증 보류 |
| 20 | 🟢 | 팔로워/구독자/세션 목록 응답 페이지 메타 | data[] 외 메타 없음 | (부분 미구현) | 실측(2026-07-22): followers/subscribers·세션 목록 모두 page/totalCount/totalPages(number) 존재 — 문서 미기재. SDK 타입 optional 포함, 자동 순회는 totalPages로 조기 종료 |
| 21 | 🔴 | 429 Retry-After 헤더 | 미기술 | (미구현) | 실측 후 재시도 로직(#4)에 반영. 헤더 없으면 지수 백오프만 사용 |
| 22 | 🟢(문서상) | SUBSCRIPTION 이벤트 month 설명 | "사용된구독 기간치지직 이모티콘 정보" — 문서 자체 오타 | — | 의미는 "구독 개월 수"(Int)로 확정. 구현 영향 없음 |
| 23 | 🟢 | users/me 응답 nickname | 문서에 없음 (channelId, channelName만 기재) | (기존 타입에 없음) | 실측(2026-07-22, pnpm verify): 실제 응답에 nickname(string) 존재. SDK 타입(#6 UserMe)에 포함 — 문서 공백을 실측으로 채움 |
| 24 | 🟢 | categories/search 응답 | data[]: categoryType/categoryId/categoryValue/posterImageUrl | — | 실측(2026-07-22, pnpm verify): 문서와 일치 (query=게임, 5건 수신, 초과 필드 없음) |
| 25 | 🟢 | streaming-roles userRole 값 | STREAMING_CHANNEL_OWNER 등 4종 enum | (기존 구현 없음) | 실측(2026-07-22): 채널 소유자가 문서에 없는 STREAMER 값으로 내려옴 (소유자 본인도 목록에 포함). SDK는 known 5종 + string 확장 허용(StreamingRole) |
| 26 | 🟢 | Live 4종 (lives / streams/key / lives/setting GET·PATCH) | 문서 스키마 | (기존 관측과 대체로 일치) | 실측(2026-07-22, pnpm verify): 4종 모두 문서와 일치. lives page.next 커서 동작 확인, PATCH는 동일 제목 라운드트립으로 200 확인. streamKey는 32자 문자열(값 미출력) |
| 27 | 🟢 | CHAT 이벤트 userRoleCode 위치 | 최상위 필드로 표기 | (기존 구현 타입에 없음) | 실측(2026-07-22): 실제로는 profile 객체 내부에 위치 (profile.userRoleCode: "streamer") — 문서와 구조 불일치. Normalize(#16)에서 흡수 |
| 28 | 🟢 | CHAT 이벤트 eventSentAt | 문서에 없음 | (기존 구현 타입에 없음) | 실측(2026-07-22): 나노초 정밀 타임스탬프 문자열 존재 ("2026-07-22T22:48:28.525460090", 타임존 표기 없음). Normalize 타입에 optional 포함 |
| 29 | 🟢 | 채팅/세션과 라이브 상태의 관계 | 미기술 | (기존 구현은 라이브 중에만 사용) | 실측(2026-07-22): chats/send·chats/settings·sessions/auth·이벤트 구독·CHAT 수신 모두 라이브 OFF 상태에서 정상 동작. 채팅 채널은 상시 존재 |
| 30 | 🟡 | chats/blind-message | chatChannelId + messageTime + senderChannelId | (기존 구현 없음) | 파라미터는 세션 CHAT 이벤트에서만 획득 가능 → 실측은 Transport(#15) 구현 후. SDK(#10)는 문서 스펙으로 구현 |
| 31 | 🟢 | chats/notice 두 방식 | message(신규) 또는 messageId(기존) | (기존 구현은 둘 다 전송하는 형태) | 실측(2026-07-22): 두 방식 모두 200 확인 — message 직접 등록, chats/send 후 해당 messageId로 등록 모두 동작 |
| 32 | 🟢 | 설정 변경의 실반영 (뮤테이션 라운드트립) | — | — | 실측(2026-07-22): 방송 제목 PATCH·채팅 저속모드 PUT 모두 변경→재조회 반영→원복 확인. 쓰기 경로가 200만 반환하는 게 아니라 실제로 반영됨을 확정 |
| 33 | 🟢(부분) | 활동 제한 추가/해제 성공 경로 | targetChannelId로 등록/해제 | (기존 구현 없음) | 실측(2026-07-22): 임시제한의 실효 = 대상에게 채팅 1분 제한 (작성자가 대상 계정으로 직접 확인 — UI에 1분 제한 표시, 기존 채팅 블라인드 처리). DELETE로 제한 중 조기 해제 가능(200). 관리자 계정은 등록 거부(400) — 관리자 보호. 일반 활동 제한 추가는 위즈봇 대상 400 "BAD_REQUEST" 지속(앱 연동 계정 보호 추정, 요청 형식은 유효 확인) — 무관 계정 확보 시 재검증 |
| 34 | 🔴 | 활동 제한 추가(POST /restrict-channels) 서버 동작 | targetChannelId body로 등록 | (기존 구현 없음) | 실측 진단(2026-07-22): body 누락/오필드명은 400 "잘못된 값을 입력했습니다."(파라미터 에러), 올바른 {targetChannelId} body는 존재하지 않는 ID·본인·일반 계정 모두 400 "BAD_REQUEST"(generic). 파라미터 검증은 통과하나 내부 로직에서 전부 거부 → 서버측 결함 또는 미문서 선행 조건 의심. SDK 구현은 문서·파라미터 검증 실측에 부합 — 치지직 개발자 문의 권장 |
| 35 | 🟢 | 클라이언트 세션 + 유저 토큰 구독 조합 | 명시 없음 (세션 생성 주체별 설명만) | 기존 구현이 이 패턴 사용 | 실측(2026-07-22): 클라이언트 인증으로 만든 세션에 유저 Access Token으로 subscribe/chat 성공, SYSTEM(subscribed)·unsubscribed 통지 수신. 다중 채널 봇 아키텍처의 표준 패턴 확정. 클라이언트 세션 목록에도 page/totalCount/totalPages 메타 존재 |
실시간 연결 관련 중요 참고
- socket.io-client 지원 버전이 1.0.0+ ~ 2.0.3 로 문서에 명시됨 (socket.io v2 = EIO=3 프로토콜).
- 실측(2026-07-22) — 프로토콜/연결 흐름 전부 확인 완료:
- 세션 URL 형식:
https://ssioNN.nchat.naver.com+?auth=<128자 토큰> GET {origin}/socket.io/?EIO=3&transport=polling&auth=...핸드셰이크 성공 →sid발급,upgrades: ["websocket"]- 이후 폴링 GET만으로
40(네임스페이스 연결) →42["SYSTEM","{...connected, sessionKey...}"]수신 - 구독(POST + 쿼리) 후
SYSTEM(subscribed)→ 채팅 발생 시42["CHAT","{...}"]수신까지 순수 fetch로 재현됨 - #15 확정: 의존성 0의 자체 EIO=3 클라이언트 구현. WebSocket(
globalThis.WebSocket, Node 22+/브라우저) 우선 + 순수 fetch 롱폴링 폴백(Node 18~20). 두 전송 모두 실서버 e2e(연결→구독→CHAT 수신→종료) 통과(2026-07-22). socket.io-client@2는 구버전 의존성이라 배제 - 세션 등록은 핸드셰이크 시점에 발생 (라이브 여부 무관). 미유지 시 ping 타임아웃(~85초)으로 서버가 종료 처리
- 날짜 형식: 세션 목록의 connectedDate/disconnectedDate는
"2026-07-22 22:44:39"(공백 구분, 타임존 없음)
- 세션 URL 형식:
- 소켓 이벤트 payload는 JSON 문자열로 전달됨 (
JSON.parse필요) — 기존 관측과 문서 예제 모두 일치. - 구독 성공 통지는 HTTP 응답이 아니라 세션의
SYSTEM(subscribed)메시지로 온다. Transport는 이 메시지를 구독 완료 신호로 사용한다 (고정 지연(setTimeout) 방식이 불필요한 근거). - 세션 연결 상한: 클라이언트 세션 10개 / 유저 세션 3개 / 세션당 구독 30개 — SDK에서 초과 시 명확한 에러 안내.
검증 절차 (하니스 #14에서 표준화)
- 문서 스펙으로 요청/응답 zod 스키마(passthrough) 1차 정의
- 실제 자격증명으로 호출 → 스키마 파싱. 문서에 없는 필드 수신 시 경고 로그, 알려진 필드 누락 시 실패
- 불일치 발견 시 이 문서의 표를 갱신하고 타입은 실제 응답 기준으로 수정
- 자격증명·조건(법인 스코프 등) 미비로 호출 불가한 항목은 🟡 유지 + 코드에 문서 링크 주석