KIS 토큰 만료
한국투자증권(KIS) OpenAPI는 OAuth Access Token이 24h 만료입니다. quant-ai의
KISTokenManager가 만료 직전(보통 23h 시점)에 자동 재발급을 시도하지만,
다음 상황에서 갱신이 실패하고 401 / Invalid token 에러가 발생합니다.
사전 요구 사항
- KIS OpenAPI 신청 완료 + App Key / App Secret 발급됨
FEATURE_KIS_BROKER=true- 사용자별 키가
exchange_keys테이블에 등록됨 (asset_class='kr_equity')
KISTokenManager 동작
1. 첫 호출 시 token이 없거나 expired → POST /oauth2/tokenP
2. 응답 access_token + expires_in (24h, 86400초)
3. 메모리 캐시 + DB의 broker_token_cache (있을 경우)
4. 만료 1h 전부터 백그라운드 재발급 시도
5. 재발급 실패 시 다음 API 호출에서 fail-fast (401)
토큰은 사용자별이 아니라 (App Key, App Secret) 페어 단위로 발급됩니다. 즉 같은 키를 공유하는 두 사용자가 있으면 한쪽 갱신이 양쪽에 적용됩니다 — 운영상 사용자별로 별도 App Key를 권장.
1. 진단
# 1) 최근 KIS 401 이벤트
docker compose exec timescaledb psql -U quant -d quantai -c "
SELECT received_at, user_id, payload->>'broker' AS broker,
payload->>'http_status' AS code,
LEFT(payload->>'error', 100) AS err
FROM broker_order_events
WHERE payload->>'broker' = 'kis'
AND event_type IN ('error_4xx','auth_failed')
AND received_at > NOW() - INTERVAL '2 hours'
ORDER BY received_at DESC LIMIT 10;
"
# 2) API 로그에서 토큰 갱신 시도
docker compose logs api --tail=500 | grep -E "kis_token|tokenP"
# 3) 사용자별 KIS 키 활성 여부
docker compose exec timescaledb psql -U quant -d quantai -c "
SELECT id, user_id, is_active, created_at, last_used_at
FROM exchange_keys
WHERE exchange = 'kis' AND is_active = true;
"
2. 자주 마주치는 케이스
Case A: 자동 재발급이 정상 동작 중 (대기만 하면 OK)
증상: 1~2분 동안 401, 그 후 정상 복귀.
원인: 만료 직후 첫 요청에서 fail → 즉시 재발급 → 다음 요청부터 OK. 정상.
조치: 없음. 단, 연속 발생하면 Case B/C로 이동.
Case B: App Secret 회전 / KIS 측 키 무효화
증상: 갱신 시도가 401로 끝남. 로그에 KIS_INVALID_APP_KEY.
진단:
docker compose exec api python -c "
import requests, os
r = requests.post(
f'{os.environ[\"KIS_BASE_URL\"]}/oauth2/tokenP',
json={
'grant_type': 'client_credentials',
'appkey': os.environ['KIS_APP_KEY'],
'appsecret': os.environ['KIS_APP_SECRET'],
},
timeout=10,
)
print(r.status_code, r.text[:300])"
조치: KIS 콘솔에서 새 키 발급 → .env 또는 사용자 UI에서 갱신.
Case C: 모의투자 ↔ 실투자 URL 미스매치
증상: KIS_INVALID_APP_KEY 에러인데 키는 정상.
진단:
grep KIS_BASE_URL .env
# 모의: https://openapivts.koreainvestment.com:29443
# 실투자: https://openapi.koreainvestment.com:9443
조치: 사용자 의도(모의/실투)에 맞게 URL 변경 후 compose restart api.
Case D: 토큰 캐시 손상
증상: 갱신은 성공하는데 직후 호출이 401.
진단:
# DB 캐시 확인 (broker_token_cache 테이블이 있는 deployment 한정)
docker compose exec timescaledb psql -U quant -d quantai -c "
SELECT broker, expires_at, LENGTH(token) AS token_len
FROM broker_token_cache
WHERE broker = 'kis';
"
조치: 캐시 강제 무효화 후 다음 호출에서 재발급:
docker compose exec timescaledb psql -U quant -d quantai -c "
DELETE FROM broker_token_cache WHERE broker = 'kis';
"
docker compose restart api
Case E: hashkey 엔드포인트 분리
증상: 주문 요청만 401, 조회는 정상.
원인: KIS 일부 엔드포인트는 별도 hashkey 서명 필요. KIS_HASHKEY_URL 미설정.
조치: .env에 KIS_HASHKEY_URL 추가하거나 KIS 측 권장 기본 사용.
Case F: 동시 요청 폭주
증상: 1초에 수십 건 요청 후 일부가 401.
원인: KIS rate limit (초당 20건 등). 토큰은 정상이지만 거래소 측 제한.
조치: RECONCILER_MAX_CONCURRENT_USERS 또는 broker client의 동시 요청
한도 하향. 자세한 건 포지션 리컨실러.
3. 검증
# 단일 호출 성공 확인
docker compose exec api python -c "
from src.execution.brokers.kis_broker import KISBroker
b = KISBroker.from_env()
print(b.get_account_balance()) # 200 응답이어야 함
"
또는 사용자 UI에서 "한국투자증권 계좌 정보" 페이지 새로고침 → 잔고가 표시되면 정상.
4. 운영 팁
- 사용자별 키 권장 — 한 키를 다수 사용자가 공유하면 토큰 갱신이 race condition으로 중복 발급되어 KIS rate limit에 걸릴 수 있음.
- 모의투자 / 실투자 키는 별도 — 같은 App Key가 양쪽에서 작동하지 않음.
- 갱신 실패 시 큐 / 봇 매니저는 다음 사이클까지 기다리지 않고 즉시 fail — 사용자에게 빠른 피드백.
- KIS는 브로커 5xx 알림 트리거에서 제외할 가치 있음 — 자체 정비 시간이 잦아 false positive가 많음. 단, 룰을 영구 변경하기 전 디자인 리뷰.
트러블슈팅
| 증상 | 원인 / 조치 |
|---|---|
| 24h마다 정확히 1~2분 401 | 자동 재발급 정상. 무시 가능 |
| 매시간 401 | KIS 측 토큰 강제 만료 (정비) — KIS 공지 확인 |
| 영구 401 | 키 무효 / URL 미스매치 → Case B/C |
| 갱신 자체가 안 일어남 | API 컨테이너 정지 / 백그라운드 태스크 죽음. docker compose restart api |
관련 페이지
- 브로커 401
- 환경변수 카탈로그 — KIS_*
- 포지션 리컨실러
- 부록 — 지원 브로커