AlgoLab Blog · 실전 가이드 · 2026 최신

KIS API 발급부터 첫 주문까지 30분 — 한국투자증권 자동매매 풀가이드

KIS · 한국투자 2026-05-19 · 약 10분 읽기 / 실습 30분 · 알고랩 AlgoLab
한 줄 요약 KIS API 발급은 KIS Developers 가입 → 앱 등록 → APP KEY 발급 → 토큰 발급 4단계로 끝납니다. 다만 토큰 24시간 자동 만료, 초당 호출 제한, 모의/실전 환경 분리 같은 KIS만의 함정 4가지를 모르면 봇이 24시간 후 멈춥니다. 이 글은 알고랩이 40건+ KIS 봇을 제작하며 정리한 발급+첫 주문 풀가이드입니다.

KIS API(한국투자증권 Developers API)는 키움 OpenAPI+의 단점(Windows 32bit 종속, 신청서 제출, 인증서 갱신)을 모두 제거한 REST 기반 API입니다. Mac, Linux, Windows 어디서나 Python 한 줄로 호출할 수 있고, 발급 절차도 10분이면 끝납니다.

그런데 발급 후 운영을 시작하면 "왜 24시간 지나면 봇이 멈추지?", "왜 분당 호출이 막히지?" 같은 KIS만의 특이사항에 막힙니다. 이 가이드는 발급 → 토큰 → 첫 주문까지 30분 안에 마치되, 함정 4가지를 미리 피하는 방법을 함께 정리했습니다.

30분 안에 끝낼 것

  1. 0단계 — 사전 준비 (계좌 + 인증)
  2. 1단계 — KIS Developers 가입 + 본인 인증 (5분)
  3. 2단계 — 앱 등록 → APP KEY/SECRET 발급 (5분)
  4. 3단계 — 모의투자 vs 실전 환경 선택 (3분)
  5. 4단계 — Python으로 토큰 발급 (5분)
  6. 5단계 — 첫 시세 조회 (5분)
  7. 6단계 — 첫 모의 매수 → 즉시 취소 (5분)
  8. 7단계 — 토큰 자동 갱신 코드 (2분)
  9. 모두 놓치는 KIS 함정 4가지
  10. 자주 묻는 질문 (FAQ)

0사전 준비 — 5분 안에 점검

💡 키움 OpenAPI+와의 결정적 차이: 키움은 Windows 32bit Python에 OCX 컴포넌트를 설치해야 하지만, KIS는 그냥 requests 한 줄이면 됩니다. Mac/Linux 사용자, 클라우드 운영자에게는 KIS가 압도적으로 편합니다. 자세한 비교는 키움·KIS·LS·대신 자동매매 API 비교 참고.

1KIS Developers 가입5분

apiportal.koreainvestment.com 접속 → 우상단 로그인회원가입.

가입 시 요구되는 것:

가입 직후 메일로 인증 링크가 옵니다. 클릭해서 활성화하면 1단계 완료.

2앱 등록 → APP KEY/SECRET 발급5분

로그인 후 상단 메뉴 "개인 → 인증키 발급" 이동. 화면에서 다음을 입력:

등록 완료 시 APP KEY(약 36자)와 APP SECRET(약 180자)이 표시됩니다.

⚠️ APP SECRET은 발급 직후 즉시 복사해서 안전한 곳에 저장하세요. KIS Developers는 SECRET을 평문으로 다시 보여주지 않습니다(마스킹 표시). 분실 시 앱 삭제 후 재발급이 필요하고, 그러면 봇 코드의 KEY도 모두 교체해야 합니다.

3모의투자 vs 실전 환경3분

KIS는 두 환경의 도메인이 다릅니다. 코드에서 base_url 한 줄만 바꾸면 전환됩니다.

환경도메인 (HTTPS)계좌번호 시작특징
모의투자 openapivts.koreainvestment.com:29443 4444xxxxxxxx 또는 50xxxxxxxx 가상 자산, 실거래 동일 API, 일부 종목 제한
실전투자 openapi.koreainvestment.com:9443 본인 계좌번호 실제 자산 거래, 모든 종목, 실수=손실

✅ 권장 순서: 무조건 모의투자 1주일 → 실전 환경 10만원 이하로 1주일 → 본격 운영. 알고랩 의뢰자 80%가 이 단계를 건너뛰고 실전 직행해서 첫 주에 사고 납니다.

4Python으로 토큰 발급5분

먼저 환경 세팅:

python -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate
pip install requests python-dotenv

.env 파일에 발급받은 KEY 저장:

# .env (.gitignore 필수)
KIS_APP_KEY=발급받은_APP_KEY
KIS_APP_SECRET=발급받은_APP_SECRET
KIS_ACCOUNT_NO=44440000-01   # 8자리-2자리 형식 (모의)
KIS_BASE_URL=https://openapivts.koreainvestment.com:29443

토큰 발급 코드:

import os, requests
from dotenv import load_dotenv

load_dotenv()
BASE_URL = os.getenv("KIS_BASE_URL")
APP_KEY = os.getenv("KIS_APP_KEY")
APP_SECRET = os.getenv("KIS_APP_SECRET")

def get_token():
    """KIS 액세스 토큰 발급 (유효기간 24시간)"""
    url = f"{BASE_URL}/oauth2/tokenP"
    body = {
        "grant_type": "client_credentials",
        "appkey": APP_KEY,
        "appsecret": APP_SECRET
    }
    res = requests.post(url, json=body)
    res.raise_for_status()
    data = res.json()
    print(f"토큰 발급 성공. 만료: {data.get('access_token_token_expired')}")
    return data["access_token"]

if __name__ == "__main__":
    token = get_token()
    print(f"Bearer {token[:30]}...")

성공 출력 예시: 토큰 발급 성공. 만료: 2026-05-20 14:23:45

⚠️ 토큰 발급 호출은 1분에 1회 이상 시도하면 차단됩니다. 매 호출마다 토큰 재발급하지 말고 반드시 파일 또는 메모리에 캐싱해서 23시간 사용 후 갱신하세요. 7단계에 자동 갱신 코드를 정리합니다.

5첫 시세 조회5분

삼성전자(005930) 현재가를 조회합니다. KIS는 모든 호출에 tr_id(거래 ID)를 헤더로 명시해야 합니다.

def get_price(token, stock_code="005930"):
    """국내 주식 현재가 조회"""
    url = f"{BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-price"
    headers = {
        "Content-Type": "application/json; charset=utf-8",
        "authorization": f"Bearer {token}",
        "appkey": APP_KEY,
        "appsecret": APP_SECRET,
        "tr_id": "FHKST01010100"   # 주식 현재가 시세 조회 tr_id
    }
    params = {
        "FID_COND_MRKT_DIV_CODE": "J",   # J = 주식
        "FID_INPUT_ISCD": stock_code
    }
    res = requests.get(url, headers=headers, params=params)
    res.raise_for_status()
    return res.json()["output"]

if __name__ == "__main__":
    token = get_token()
    price = get_price(token, "005930")
    print(f"삼성전자 현재가: {price['stck_prpr']}원")
    print(f"전일 대비: {price['prdy_vrss']}원 ({price['prdy_ctrt']}%)")

출력이 정상이면 4단계까지 완벽 작동입니다. 에러 발생 시:

6첫 모의 매수 → 즉시 취소5분

가장 안전한 첫 거래 테스트 패턴: 현재가 -10%로 지정가 매수 → 즉시 취소. 체결될 가능성 없이 주문 흐름만 확인합니다.

import json

ACCOUNT_NO = os.getenv("KIS_ACCOUNT_NO")  # "44440000-01"
CANO, ACNT_PRDT_CD = ACCOUNT_NO.split("-")

def order_cash(token, stock_code, qty, price, side="buy", paper=True):
    """국내 현금 주문 (지정가). paper=True면 모의투자 tr_id 사용"""
    url = f"{BASE_URL}/uapi/domestic-stock/v1/trading/order-cash"
    # tr_id: 모의 매수 VTTC0802U / 매도 VTTC0801U
    #        실전 매수 TTTC0802U / 매도 TTTC0801U
    if side == "buy":
        tr_id = "VTTC0802U" if paper else "TTTC0802U"
    else:
        tr_id = "VTTC0801U" if paper else "TTTC0801U"
    headers = {
        "Content-Type": "application/json; charset=utf-8",
        "authorization": f"Bearer {token}",
        "appkey": APP_KEY,
        "appsecret": APP_SECRET,
        "tr_id": tr_id
    }
    body = {
        "CANO": CANO,
        "ACNT_PRDT_CD": ACNT_PRDT_CD,
        "PDNO": stock_code,
        "ORD_DVSN": "00",   # 00=지정가, 01=시장가
        "ORD_QTY": str(qty),
        "ORD_UNPR": str(price)
    }
    res = requests.post(url, headers=headers, data=json.dumps(body))
    return res.json()

if __name__ == "__main__":
    token = get_token()
    cur = int(get_price(token, "005930")["stck_prpr"])
    # 현재가 -10% 지정가로 1주 매수 시도 (체결 안 됨)
    res = order_cash(token, "005930", 1, int(cur * 0.9), "buy", paper=True)
    print(json.dumps(res, indent=2, ensure_ascii=False))

성공 시 응답에 output.ODNO(주문번호)가 옵니다. 이 번호로 즉시 취소:

def cancel_order(token, order_no, stock_code, qty, paper=True):
    url = f"{BASE_URL}/uapi/domestic-stock/v1/trading/order-rvsecncl"
    tr_id = "VTTC0803U" if paper else "TTTC0803U"
    headers = {
        "Content-Type": "application/json; charset=utf-8",
        "authorization": f"Bearer {token}",
        "appkey": APP_KEY,
        "appsecret": APP_SECRET,
        "tr_id": tr_id
    }
    body = {
        "CANO": CANO,
        "ACNT_PRDT_CD": ACNT_PRDT_CD,
        "KRX_FWDG_ORD_ORGNO": "",
        "ORGN_ODNO": order_no,
        "ORD_DVSN": "00",
        "RVSE_CNCL_DVSN_CD": "02",   # 02=취소
        "ORD_QTY": str(qty),
        "ORD_UNPR": "0",
        "QTY_ALL_ORD_YN": "Y"
    }
    res = requests.post(url, headers=headers, data=json.dumps(body))
    return res.json()

주문 흐름이 정상이라면 발급 완료입니다. 이제 실제 전략을 붙이면 봇이 됩니다.

7토큰 자동 갱신 코드2분

KIS 토큰은 정확히 24시간 후 만료됩니다. 갱신을 안 하면 봇이 그 시점에 401 에러로 멈춥니다. 캐시 파일 기반 자동 갱신 패턴:

import os, json, time, requests
from datetime import datetime, timedelta
from dotenv import load_dotenv

load_dotenv()
TOKEN_CACHE = ".kis_token.json"

def _fetch_new_token():
    url = f"{os.getenv('KIS_BASE_URL')}/oauth2/tokenP"
    body = {
        "grant_type": "client_credentials",
        "appkey": os.getenv("KIS_APP_KEY"),
        "appsecret": os.getenv("KIS_APP_SECRET")
    }
    res = requests.post(url, json=body)
    res.raise_for_status()
    d = res.json()
    return {
        "token": d["access_token"],
        "expires_at": time.time() + 23 * 3600   # 23시간 후 갱신 (안전 마진)
    }

def get_token_cached():
    """캐시된 토큰을 반환. 만료 임박 시 자동 갱신."""
    if os.path.exists(TOKEN_CACHE):
        with open(TOKEN_CACHE) as f:
            data = json.load(f)
        if data["expires_at"] > time.time():
            return data["token"]
    # 만료됐거나 없음 → 새로 발급
    data = _fetch_new_token()
    with open(TOKEN_CACHE, "w") as f:
        json.dump(data, f)
    print(f"[Token] 갱신 완료. 만료 예정: {datetime.fromtimestamp(data['expires_at'])}")
    return data["token"]

if __name__ == "__main__":
    # 봇 전체에서 이 함수만 호출하면 자동으로 24시간 운영 가능
    token = get_token_cached()
    print(f"Token ready: {token[:30]}...")

이제 봇 코드 어디서든 get_token_cached()만 부르면 토큰이 알아서 관리됩니다. 24시간 무중단 운영의 핵심.

모두 놓치는 KIS 함정 4가지

함정 1. 토큰 발급을 매 호출마다 시도

발급 직후 가장 흔한 실수. 매 시세 조회마다 토큰을 새로 받으면 1분 내 차단(EGW00133)됩니다. 위 7단계의 캐시 패턴 필수.

함정 2. 모의/실전 tr_id 혼동

매수/매도/취소의 tr_id가 환경별로 다릅니다. 실전 코드 그대로 모의 환경에서 돌리면 거의 모든 호출이 실패합니다.

위 6단계 코드처럼 paper=True/False 플래그로 자동 전환하세요.

함정 3. 초당 호출 제한 (실전 20건, 모의 1건)

실전은 초당 20건, 모의는 초당 1건입니다. 모의에서 잘 돌던 봇이 실전에서 다르게 동작하거나, 모의에서 빠르게 다중 종목 시세 받으면 EGW00201(호출량 초과)이 납니다.

해결: time.sleep(0.05) 같은 단순 대기보다, 토큰 버킷(Token Bucket) 패턴이 안전합니다.

import time

class RateLimiter:
    def __init__(self, calls_per_sec):
        self.interval = 1 / calls_per_sec
        self.last_call = 0
    def wait(self):
        elapsed = time.time() - self.last_call
        if elapsed < self.interval:
            time.sleep(self.interval - elapsed)
        self.last_call = time.time()

# 실전: limiter = RateLimiter(15)  # 안전 마진
# 모의: limiter = RateLimiter(0.8)

함정 4. 해외주식 API 사용 동의 누락

같은 APP KEY/SECRET으로 해외주식도 호출 가능하지만, 한투 홈페이지에서 "해외주식 API 사용 동의"를 별도 체크해야 합니다. 안 하면 모든 해외주식 호출이 40570000(권한 없음)으로 떨어집니다. 미국 프리마켓 자동매매 등 해외 자동화 사례는 → KIS API 미국 프리마켓 자동손절 실전 제작기 참고.

발급 후 첫 운영 체크리스트

자주 묻는 질문

Q. KIS API 발급에 한국투자증권 계좌가 꼭 있어야 하나요?

네, 본인 명의 한국투자증권 위탁계좌가 필요합니다. 비대면 개설 가능. 가족 명의는 불가.

Q. 발급 비용이 있나요?

무료입니다. 거래 수수료는 일반 거래와 동일.

Q. 모의투자와 실전은 어떻게 분리하나요?

도메인이 다릅니다(openapivts vs openapi). APP KEY/SECRET도 환경별 별도 발급. 코드에서 base_urltr_id만 바꾸면 됩니다.

Q. 토큰 24시간 만료가 봇 운영에 영향이 큰가요?

자동 갱신 로직(위 7단계)만 들어가면 영향 없습니다. 갱신을 빠뜨리면 정확히 발급 24시간 후 멈춥니다.

Q. 키움 OpenAPI+와 KIS API 중 어느 게 더 좋나요?

Mac/Linux, 클라우드 운영 → KIS. Windows + 풍부한 자료 → 키움. 자세한 비교는 → 키움·KIS·LS·대신 자동매매 API 비교

Q. 해외주식 API도 같은 KEY로 쓰나요?

같은 KEY로 가능하나, 한투 홈페이지에서 "해외주식 API 사용 동의" 별도 체크 필요.

다음 단계 — 실제 봇으로

발급은 끝났습니다. 이제 자동매매 봇으로 만들려면 다음이 필요합니다:

알고랩이 1~3주 안에 위 발급 골격 + 안전장치 + 모니터링 + 무중단 운영까지 통합된 KIS 봇을 만들어드립니다.

KIS 자동매매 봇 맞춤 제작

API 발급부터 토큰 자동 갱신, 전략 구현, 24시간 무중단 운영까지 — 알고랩이 통합 패키지로 제작합니다.
24시간 빠른 답변 가능합니다.

무료 상담 시작하기