한국투자증권 REST API 시작하기 — 토큰 발급부터 잔고 조회까지
증권사 API 연동을 하나 끝내고 나면 다음 증권사는 쉬울 줄 알았다. 실제로는 절반만 맞다. 인증-토큰-조회로 이어지는 큰 골격은 대체로 비슷하지만, 세부 사양은 증권사마다 다르고 그 차이를 무시하면 첫날부터 막힌다. 이번 글은 한국투자증권 오픈API(KIS Developers)로 접근토큰을 발급받고 잔고를 조회하는 기본 흐름을 개념 수준에서 정리한다.
이 글의 범위
정확한 엔드포인트 URL, 정확한 요청 파라미터명, 정확한 토큰 유효시간 같은 구체적인 스펙은 이 글에서 밝히지 않는다. 이런 값은 시점에 따라 바뀔 수 있고, 잘못 기억한 값을 그대로 옮기면 오히려 독자를 잘못된 방향으로 이끈다. 이 글이 다루는 건 "대체로 이런 순서로 진행된다"는 개념과 흐름이고, 실제 신청·연동 시에는 반드시 KIS Developers 공식 문서에서 최신 사양을 확인해야 한다.
KIS Developers란 무엇인가 — 신청 흐름 개관
KIS Developers는 한국투자증권이 제공하는 오픈API 플랫폼의 명칭이다. 증권사 계좌를 가진 이용자가 신청 절차를 거쳐 앱키(App Key)와 앱시크릿(App Secret)이라는 한 쌍의 인증 정보를 발급받고, 이 값으로 REST API를 호출해 시세 조회·잔고 조회·주문 같은 기능을 프로그램에서 직접 쓸 수 있게 해주는 구조다.
신청 흐름은 대체로 이런 단계를 거친다.
- 한국투자증권 계좌 개설(이미 계좌가 있다면 생략)
- KIS Developers 포털에서 개발자 등록·이용 신청
- 앱키/앱시크릿 발급
- 모의투자용 앱키와 실전투자용 앱키를 구분해서 관리(둘은 별개로 발급된다는 개념만 알아두면 된다)
이 신청 단계에서 화면에 어떤 항목이 뜨는지, 승인까지 며칠이 걸리는지는 시점마다 달라질 수 있어 여기서 단정하지 않는다. 키움 REST API 접근토큰 발급과 만료·재발급 관리에서 다룬 키움증권도 비슷하게 앱키/시크릿 발급이 먼저 있고 그다음 토큰 발급이 이어지는 구조였다. 증권사가 바뀌어도 "앱키/시크릿 → 토큰 → API 호출"이라는 큰 골격 자체는 반복된다는 걸 확인할 수 있다.
접근토큰 발급 흐름 — 키움과의 개념적 비교 (의사코드)
앱키/앱시크릿을 받았다고 바로 잔고를 조회할 수 있는 건 아니다. 대부분의 국내 증권사 오픈API가 그렇듯, 먼저 이 한 쌍의 값으로 접근토큰을 발급받고, 이후 요청에는 앱키/시크릿 대신 이 토큰을 실어 보내는 OAuth2 계열 인증 패턴을 쓴다.
이 흐름은 키움 REST API와 개념적으로 거의 같다. 앞서 키움 편에서 정리했던 것처럼 "고정된 인증정보로 짧은 유효기간의 토큰을 발급받고, 매 호출마다 그 토큰을 헤더에 싣는다"는 구조 자체는 증권사를 가리지 않는 공통 패턴이라고 봐도 된다. 다만 토큰 발급 요청을 어떤 경로로 보내는지, 요청 본문의 필드명이 무엇인지는 증권사마다 다르므로 그 부분만큼은 반드시 공식 문서로 개별 확인해야 한다.
아래는 실제 API 스펙을 흉내 낸 코드가 아니라, 개념을 보여주기 위한 일반화된 의사코드다.
python 코드 보기
class BrokerAuthClient:
def __init__(self, app_key: str, app_secret: str, is_paper_trading: bool):
self.app_key = app_key
self.app_secret = app_secret
self.is_paper_trading = is_paper_trading
self._token = None
self._expires_at = 0
def get_access_token(self) -> str:
"""캐시된 토큰이 유효하면 재사용, 만료 임박이면 재발급."""
if self._token and self._is_still_valid():
return self._token
self._issue_token()
return self._token
def _is_still_valid(self) -> bool:
# 여유 시간을 두고 미리 재발급을 트리거하는 게 안전하다
SAFETY_MARGIN_SECONDS = 60
return now() < self._expires_at - SAFETY_MARGIN_SECONDS
def _issue_token(self) -> None:
"""실제로는 문서에 명시된 토큰 발급 엔드포인트에 요청.
모의투자/실전투자 도메인이 분리돼 있다는 점만 개념적으로 기억해두자."""
response = self._request_new_token()
self._token = response["access_token"]
self._expires_at = now() + response["expires_in"]
def _request_new_token(self) -> dict:
raise NotImplementedError # 실제 요청 로직은 공식 문서 기준으로 구현여기서 눈여겨볼 부분은 두 가지다. 하나는 토큰을 매 호출마다 새로 받지 않고 캐싱해서 재사용한다는 것, 다른 하나는 만료 시각 자체가 아니라 그보다 여유를 둔 시점에 미리 재발급을 트리거한다는 것이다. 정확히 만료 시점에 맞춰 재발급을 시도하면 네트워크 지연 때문에 "토큰이 이미 죽은 채로 요청이 나가는" 경계 상황이 생길 수 있다. 이 방어적 습관은 증권사를 가리지 않고 유효하다.
토큰 캐싱과 만료·재발급
접근토큰에는 유효기간이 있고, 만료되면 같은 절차로 재발급받아야 한다는 게 이 구조의 기본 전제다. 정확한 유효시간이 몇 시간인지는 이 글에서 단정하지 않는다. 시점에 따라 정책이 바뀔 수 있는 값이므로, 정확한 수치는 반드시 공식 문서 기준으로 확인해야 한다.
중요한 건 정확한 숫자가 아니라 설계 원칙이다.
- 토큰을 매 요청마다 새로 발급받지 말고, 발급받은 토큰과 만료 시각을 어딘가에 캐싱해서 재사용한다.
- 만료 임박을 감지하면 다음 요청 전에 선제적으로 재발급한다.
- API 호출 중 "인증 실패/토큰 만료" 류의 에러 응답을 받으면, 토큰을 강제로 재발급받고 1회만 재시도한다.
- 재발급 자체가 반복해서 실패하면(앱키/시크릿 문제 등) 무한 재시도 대신 알림을 보내고 중단한다.
이 원칙은 키움 편에서 정리했던 토큰 관리 원칙과 사실상 동일하다. 증권사가 바뀌어도 "인증은 한 번 통과하면 끝나는 관문이 아니라, 프로세스가 살아있는 내내 관리해야 하는 상태"라는 점은 변하지 않는다.
잔고 조회 요청 구조 — 개념 수준
토큰을 확보했으면, 이후 API 호출에는 이 토큰을 인증 헤더에 실어 보내는 게 공통 패턴이다. 잔고 조회도 마찬가지로, 대체로 아래와 같은 요소가 요청에 포함된다는 개념만 짚는다.
- 인증 헤더 (
Authorization: Bearer {토큰}형태가 일반적) - 앱키를 함께 헤더나 파라미터로 요구하는 경우가 많다
- 조회 대상 계좌 구분(모의투자/실전투자, 계좌번호 — 여기서는 마스킹된 형태로만 다룬다)
- 국내주식/해외주식 등 상품 구분에 따라 요청 경로나 파라미터가 달라질 수 있다는 점
python 코드 보기
class AccountClient:
def __init__(self, auth_client, base_url: str):
self.auth_client = auth_client
self.base_url = base_url
def get_balance(self, account_no_masked: str) -> dict:
headers = {
"Authorization": f"Bearer {self.auth_client.get_access_token()}",
"appkey": self.auth_client.app_key,
# 실제로는 문서에 명시된 추가 헤더(거래ID 구분값 등)가 더 필요할 수 있다
}
params = {"account_no": account_no_masked}
raw = self._call_balance_endpoint(headers, params)
return self._normalize(raw)
def _call_balance_endpoint(self, headers, params) -> dict:
raise NotImplementedError
def _normalize(self, raw: dict) -> dict:
"""증권사마다 다른 응답 필드를, 내부에서 쓸 표준 형태로 변환."""
return {
"cash": raw.get("cash", 0),
"holdings": raw.get("holdings", []),
"total_eval_amount": raw.get("total_eval_amount", 0),
}응답에 담기는 정보는 대체로 예수금(현금), 보유 종목 목록, 종목별 평가금액, 계좌 전체 평가금액 같은 범주로 나뉜다. 다만 정확한 필드명과 단위는 이 글에서 단정하지 않는다. 임의로 필드명을 지어내면, 그 이름을 그대로 믿고 코드를 짠 독자가 조용히 틀린 값을 참조하게 될 수 있다. 이 부분은 반드시 공식 문서로 확인해야 하는 영역이다. 증권사별 API 성격 자체를 더 넓게 비교하고 싶다면 증권사 자동매매 API, 뭘 골라야 하나 — 키움·한국투자·토스 실무 비교에서 선택 기준을 따로 정리해뒀다.
시작할 때 흔한 함정
- 모의투자와 실전투자 도메인을 혼동하는 것: 한국투자증권 오픈API는 모의투자용과 실전투자용이 개념적으로 분리돼 있다. 앱키도, 요청을 보내는 대상도 다를 수 있다는 걸 인지하지 못하면, 테스트 중인 줄 알았던 요청이 실전 계좌를 향해 나갈 수도 있고 반대로 실전을 테스트한다고 착각할 수도 있다. 시작 단계에서 반드시 어느 환경을 쓰고 있는지 코드와 로그에 명시적으로 남겨야 한다.
- 앱키/앱시크릿을 코드에 하드코딩하거나 저장소에 커밋하는 것: 앱키/시크릿이 노출되면 토큰 재발급을 통해 API 전체가 뚫릴 수 있다. 환경변수나 별도의 비밀 관리 방식으로 다루고, 로그에도 값 자체를 남기지 않는다.
- 요청 헤더를 빠뜨리는 것: 인증 토큰만 넣으면 끝이라고 생각하기 쉽지만, 실제로는 앱키나 거래 구분값처럼 헤더에 함께 실어야 하는 값이 더 있는 경우가 많다. 이 값이 하나라도 빠지면 인증 관련 에러인지 요청 형식 문제인지 구분하기 어려운 애매한 에러 응답을 받게 된다. 처음 연동할 때는 요청 하나하나를 문서와 대조하며 확인하는 편이 시간을 아낀다.
- 토큰 만료 시간을 임의로 추정해 하드코딩하는 것: 정확한 유효시간을 확인하지 않고 "대략 이 정도겠지"라고 상수를 박아두면, 정책이 바뀌었을 때 조용히 깨진다. 응답에 담긴 만료 관련 값을 그대로 쓰는 편이 안전하다.
오늘의 정리
- 한국투자증권 오픈API(KIS Developers)도 앱키/앱시크릿으로 먼저 접근토큰을 발급받고, 이후 모든 요청에 그 토큰을 싣는 OAuth2 계열 구조를 따른다.
- 이 흐름은 키움 REST API와 개념적으로 동일하다 — 증권사가 바뀌어도 "앱키/시크릿 → 토큰 → API 호출"이라는 골격은 반복된다.
- 정확한 엔드포인트, 파라미터명, 토큰 유효시간은 이 글에서 다루지 않으며 반드시 공식 문서로 확인해야 한다.
- 모의투자/실전투자 도메인 혼동, 앱키 노출, 요청 헤더 누락은 시작 단계에서 특히 자주 걸리는 함정이다.
- 토큰 캐싱, 만료 전 선제 재발급, 재발급 실패 시 중단이라는 방어 원칙은 증권사와 무관하게 그대로 적용된다.
이 글은 한국투자증권 오픈API의 공개된 일반적인 흐름을 개념 수준으로 정리한 것이며, 실제 신청·연동 시에는 반드시 공식 개발자 문서의 최신 사양을 확인해야 합니다. 투자 조언이 아니고 특정 종목이나 매매기법을 추천하지 않으며, 수익을 보장하지 않고 투자 손실의 책임은 투자자 본인에게 있습니다.