KIS API 현재가 조회 — inquire-price 함정 6가지
GET /uapi/domestic-stock/v1/quotations/inquire-price, tr_id는 FHKST01010100입니다.
필수 파라미터는 FID_COND_MRKT_DIV_CODE와 FID_INPUT_ISCD 두 개뿐이고,
모의투자에서도 tr_id가 같습니다(주문 TR처럼 V/T로 갈리지 않습니다).
응답 output은 리스트가 아니라 객체 하나이며 모든 값이 문자열입니다.
그리고 이 한 번의 호출에 현재가뿐 아니라 temp_stop_yn·vi_cls_code·mrkt_warn_cls_code 같은
주문 전 안전장치용 항목이 함께 들어 있습니다.
KIS API를 붙이는 사람이 가장 먼저 호출하는 것이 현재가입니다.
토큰을 받고, appkey·appsecret을 헤더에 넣고, 삼성전자 005930을 던져 보는 게 첫 관문이죠.
그런데 이 관문은 실패하기 어려운 대신, 조용히 틀리기 쉬운 API입니다.
호출은 200으로 성공하는데 값을 잘못 읽는 유형의 사고가 유독 많습니다.
이 글은 2026-08-26 시점 한국투자증권 공식 저장소(koreainvestment/open-trading-api)의
국내주식 기본시세 예제와 컬럼 매핑 파일에서 확인한 항목명을 기준으로,
inquire-price에서 실제로 사람들이 걸리는 함정 6가지를 코드와 함께 정리한 것입니다.
토큰 발급부터 막힌 상태라면 발급 가이드를 먼저 보고 오시는 편이 좋습니다.
목차
- 호출 자체는 30줄이면 끝난다
- 함정 1 — 모의투자도
tr_id가 같다 - 함정 2 —
J는 이제 KRX만이다 (NXT·통합) - 함정 3 — ETN은 종목코드 앞에
Q가 붙는다 - 함정 4 — 숫자가 전부 문자열로 온다
- 함정 5 —
output은 리스트가 아니다 - 함정 6 — 이건 실시간이 아니다
- 보너스: 호출 한 번으로 만드는 주문 전 안전장치
inquire-pricevsinquire-price-2
1. 호출 자체는 30줄이면 끝난다
먼저 되는 코드부터 봅니다. 토큰(access_token)은 이미 받아 두었다고 가정합니다.
import requests
BASE = "https://openapi.koreainvestment.com:9443" # 실전
# BASE = "https://openapivts.koreainvestment.com:29443" # 모의
def inquire_price(token, appkey, appsecret, code, mrkt="J"):
url = f"{BASE}/uapi/domestic-stock/v1/quotations/inquire-price"
headers = {
"content-type": "application/json; charset=utf-8",
"authorization": f"Bearer {token}",
"appkey": appkey,
"appsecret": appsecret,
"tr_id": "FHKST01010100", # 실전·모의 동일
"custtype": "P", # 개인
}
params = {
"FID_COND_MRKT_DIV_CODE": mrkt, # J:KRX, NX:NXT, UN:통합
"FID_INPUT_ISCD": code, # 005930
}
r = requests.get(url, headers=headers, params=params, timeout=5)
r.raise_for_status()
body = r.json()
if body["rt_cd"] != "0":
raise RuntimeError(body["msg_cd"] + " " + body["msg1"])
return body["output"] # dict 하나 (리스트 아님)
out = inquire_price(TOKEN, APPKEY, APPSECRET, "005930")
print(out["stck_prpr"], out["prdy_ctrt"], out["acml_vol"])
GET이라 hashkey가 필요 없습니다. hashkey는 주문처럼 본문(body)을 보내는
POST에서만 쓰는 것이고, 시세 조회는 쿼리스트링이라 해당 사항이 없습니다.
여기까지는 대부분 문제없이 통과합니다. 문제는 그다음입니다.
2. 함정 1 — 모의투자도 tr_id가 같다
KIS를 조금 다뤄 본 사람일수록 이 함정에 걸립니다.
주문 계열 TR은 실전 TTTC0012U / 모의 VTTC0012U처럼 앞글자가 T와 V로 갈립니다.
그래서 "환경에 따라 tr_id 앞글자를 바꾸는" 헬퍼를 만들어 두는 경우가 많습니다.
그런데 국내주식 기본시세 계열은 실전과 모의의 tr_id가 동일합니다.
공식 저장소의 inquire_price 함수를 보면 env_dv를 real로 주든 demo로 주든
tr_id에 FHKST01010100을 그대로 씁니다.
앞글자를 기계적으로 V로 바꾸는 헬퍼를 태우면 존재하지 않는 TR이 되어 실패합니다.
정리: KIS에서 모의와 실전을 가르는 것은 tr_id가 아니라 도메인입니다.
실전 openapi.koreainvestment.com:9443, 모의 openapivts.koreainvestment.com:29443.
토큰도 각 도메인에서 따로 발급받아야 하고, 한쪽 토큰으로 다른 쪽을 때리면 서버 불일치 오류가 납니다.
자세한 대응표는 모의투자 실전 전환에서 바꿀 것 6가지에 정리해 두었습니다.
3. 함정 2 — J는 이제 KRX만이다
FID_COND_MRKT_DIV_CODE를 예제에서 본 대로 "J"로 박아 두는 경우가 대부분입니다.
예전에는 그래도 됐습니다. 하지만 넥스트트레이드(NXT)가 생긴 뒤로 이 값은 어느 시장의 가격을 볼 것인가를 고르는 스위치가 됐습니다.
공식 예제의 파라미터 설명은 J:KRX, NX:NXT, UN:통합으로 되어 있습니다.
| 값 | 의미 | 봇에서 뜻하는 것 |
|---|---|---|
J | KRX | 한국거래소 체결만 반영된 가격 |
NX | NXT | 넥스트트레이드 체결만 반영된 가격 |
UN | 통합 | 두 시장을 합친 통합 시세 |
주문은 SOR(최선주문집행)로 내면서 시세는 J로 보는 조합이 가장 흔한 불일치입니다.
체결은 NXT에서 났는데 봇이 참조한 현재가는 KRX 것이라, 손절 트리거가 늦게 걸리거나 조기에 걸립니다.
시세 조회의 시장 구분과 주문의 거래소 구분을 같은 설정값에서 뽑아 쓰는 구조로 잡아 두십시오.
라우팅 자체가 낯설다면 NXT·KRX 주문 라우팅 글을 참고하시면 됩니다.
4. 함정 3 — ETN은 종목코드 앞에 Q가 붙는다
공식 예제의 fid_input_iscd 설명에는 조용히 이런 단서가 붙어 있습니다.
"ETN은 종목코드 6자리 앞에 Q 입력 필수".
즉 ETN 종목은 여섯 자리 코드 그대로가 아니라 앞에 Q를 붙여 넣어야 합니다.
ETF는 J 시장 구분으로 일반 종목처럼 조회되지만 ETN은 접두어가 필요합니다.
종목코드 마스터파일을 파싱해 유니버스를 자동으로 만드는 봇이라면,
ETN 구간을 걸러 내거나 Q를 붙이는 처리를 넣어야
"왜 이 종목만 조회가 안 되지"로 반나절을 쓰지 않습니다.
5. 함정 4 — 숫자가 전부 문자열로 온다
이건 KIS 국내주식 시세 응답 전반에 해당하는 규칙입니다. 응답의 모든 항목이 문자열입니다.
{
"rt_cd": "0",
"msg_cd": "MCA00000",
"msg1": "정상처리 되었습니다.",
"output": {
"iscd_stat_cls_code": "55",
"stck_prpr": "71800", <- 숫자가 아니라 문자열
"prdy_vrss": "-900",
"prdy_vrss_sign": "5",
"prdy_ctrt": "-1.24",
"acml_vol": "12345678",
"acml_tr_pbmn": "889123456789",
"stck_oprc": "72600",
"stck_hgpr": "72900",
"stck_lwpr": "71600",
"stck_mxpr": "94300",
"stck_llam": "50900",
"stck_sdpr": "72700",
"aspr_unit": "100",
"hts_avls": "4286123",
"per": "13.42",
"temp_stop_yn": "N",
"vi_cls_code": "N",
"mrkt_warn_cls_code": "00"
}
}
실제로 사고가 나는 지점:
if out["stck_prpr"] > target: 처럼 문자열끼리 비교하면 사전순 비교가 됩니다.
파이썬에서 문자열 "9000"은 문자열 "71800"보다 크다고 나옵니다.
9,000원짜리가 71,800원보다 비싸다고 판정되는 겁니다.
예외도 안 나고 로그도 안 남습니다. 조건만 조용히 뒤집힙니다.
def to_int(v, default=0):
try:
return int(str(v).replace(",", "").strip())
except (TypeError, ValueError):
return default
price = to_int(out["stck_prpr"])
base = to_int(out["stck_sdpr"]) # 기준가(전일 종가 성격)
upper = to_int(out["stck_mxpr"]) # 상한가
lower = to_int(out["stck_llam"]) # 하한가
tick = to_int(out["aspr_unit"]) # 호가단위
chg_pct = float(out["prdy_ctrt"]) # 전일 대비율 (부호 포함)
prdy_vrss_sign(전일 대비 부호)도 +/- 기호가 아니라 코드값입니다.
상한·상승·보합·하락·하한이 숫자로 구분됩니다.
다만 부호 판단은 prdy_vrss_sign을 해석하기보다 prdy_vrss(전일 대비)의 실제 부호나
prdy_ctrt(전일 대비율)를 쓰는 편이 안전합니다. 코드값 해석을 잘못해도 티가 안 나기 때문입니다.
6. 함정 5 — output은 리스트가 아니다
KIS의 다른 TR을 먼저 붙여 본 사람이 걸리는 함정입니다.
일봉 조회 FHKST03010100이나 잔고 조회는 output1·output2로 나뉘고 리스트가 옵니다.
그래서 반사적으로 반복문을 씁니다.
# 잘못된 코드 — 예외도 안 나고 값만 이상해진다
for row in body["output"]:
print(row) # iscd_stat_cls_code, marg_rate ... 키 이름만 출력됨
# 맞는 코드
out = body["output"] # dict 하나
print(out["stck_prpr"])
현재가는 종목 하나의 스냅샷이라 행이 하나입니다. 그래서 output이 객체 하나입니다.
공식 예제 코드도 pd.DataFrame(res.getBody().output, index=[0])처럼
인덱스를 명시해 한 행짜리 데이터프레임을 만듭니다. 이 index=[0]을 빼면 pandas가 에러를 냅니다.
7. 함정 6 — 이건 실시간이 아니다
공식 예제의 함수 설명 첫 줄이 이렇게 시작합니다. "주식 현재가 시세 API입니다. 실시간 시세를 원하신다면 웹소켓 API를 활용하세요."
그런데 실제로는 반복문 안에 inquire-price를 넣고 0.2초 간격으로 돌리는 코드가 흔합니다.
종목 하나면 그럭저럭 버티지만, 종목이 20개, 50개로 늘어나는 순간 유량 제한에 걸려
EGW00201 초당 거래건수 초과가 쏟아집니다.
구조를 이렇게 나누는 게 맞습니다. 장중 가격 추적은 웹소켓,
inquire-price는 봇 기동 시 초기값을 채우거나 주문 직전에 한 번 확인하는 용도.
웹소켓 쪽 붙이는 방법은 KIS 웹소켓 실시간 시세에 정리해 두었고,
호가 잔량까지 봐야 한다면 호가 조회 쪽이 맞습니다.
호출 한도 값 자체는 정책에 따라 바뀌므로 개발자 포털의 현재 안내로 확인하십시오.
8. 보너스 — 호출 한 번으로 만드는 주문 전 안전장치
여기가 이 API의 진짜 쓸모입니다.
inquire-price 응답에는 가격만 있는 게 아닙니다.
공식 저장소의 컬럼 매핑 파일 기준으로, 종목의 상태를 알려 주는 항목이 같은 응답에 함께 들어 있습니다.
| 항목명 | 뜻 | 봇이 이걸로 하는 일 |
|---|---|---|
temp_stop_yn | 임시 정지 여부 | 거래정지 종목에 주문 안 내기 |
vi_cls_code | VI 적용 구분 코드 | 변동성완화장치 발동 중이면 대기 |
ovtm_vi_cls_code | 시간외단일가 VI 구분 | 시간외 구간 별도 판단 |
mrkt_warn_cls_code | 시장경고 코드 | 투자경고·위험 종목 제외 |
mang_issu_cls_code | 관리종목 여부 | 유니버스에서 배제 |
sltr_yn | 정리매매 여부 | 상장폐지 절차 종목 배제 |
short_over_yn | 단기과열 여부 | 단일가 전환 구간 회피 |
invt_caful_yn | 투자유의 여부 | 보수적 필터에 추가 |
ssts_yn | 공매도 가능 여부 | 숏 전략 사전 확인 |
iscd_stat_cls_code | 종목 상태 구분 코드 | 비정상 상태 조기 감지 |
aspr_unit | 호가단위 | 지정가를 호가단위로 반올림 |
hts_deal_qty_unit_val | 매매 수량 단위 | 주문 수량 단위 맞추기 |
BLOCK = {
"temp_stop_yn": "Y", # 임시 정지
"sltr_yn": "Y", # 정리매매
"mang_issu_cls_code": "Y", # 관리종목
}
def tradable(out):
for key, bad in BLOCK.items():
if str(out.get(key, "")).upper() == bad:
return False, key + " = " + str(out.get(key))
if str(out.get("vi_cls_code", "N")).upper() != "N":
return False, "VI 발동 중"
return True, "ok"
def round_to_tick(price, tick):
if tick <= 0:
return price
return (price // tick) * tick
ok, why = tradable(out)
if not ok:
log.warning("주문 스킵: %s (%s)", code, why)
이 구조가 왜 이득인가:
종목 상태를 따로 조회하는 TR을 붙이면 호출이 두 배가 되고, 그만큼 EGW00201에 가까워집니다.
어차피 주문 직전에 현재가는 한 번 봐야 하므로, 그 응답에서 상태 항목까지 같이 읽으면 호출을 늘리지 않고 안전장치가 생깁니다.
단, 코드값의 의미는 직접 확인하십시오.
iscd_stat_cls_code·mrkt_warn_cls_code·vi_cls_code는 숫자·문자 코드값이고,
값의 정의는 증권사 명세에 따르며 예고 없이 바뀔 수 있습니다.
실계좌에 넣기 전 개발자 포털의 현재 명세로 코드표를 대조하고,
모르는 값이 오면 안전한 쪽(주문 스킵)으로 떨어지게 기본값을 잡아 두십시오.
9. inquire-price vs inquire-price-2
공식 저장소에는 inquire-price-2라는 형제 API도 있습니다.
경로는 /uapi/domestic-stock/v1/quotations/inquire-price-2, tr_id는 FHPST01010000입니다.
파라미터는 FID_COND_MRKT_DIV_CODE·FID_INPUT_ISCD로 1번과 완전히 같습니다.
차이는 돌아오는 항목 구성입니다.
둘 다 output 객체 하나를 주지만 포함 항목이 다릅니다.
"1번에 원하는 필드가 없다"면 2번 쪽을 확인해 보는 게 순서입니다.
다만 두 TR을 다 호출하는 습관은 피하십시오 — 호출량이 두 배가 되는데,
대부분의 봇은 1번에 있는 항목만으로 충분합니다.
참고로 같은 이름의 항목이라도 TR이 다르면 의미가 다를 수 있습니다.
예를 들어 frgn_ntby_qty(외국인 순매수 수량)는 inquire-price에도 들어 있지만,
이것은 현재가 스냅샷에 붙은 값이고
투자자별 일별 수급을 시계열로 받는 FHKST01010900과는 다른 데이터입니다.
수급을 조건으로 쓰려면 그쪽 글을 같이 보셔야 합니다.
10. 실패했을 때 무엇부터 보나
| 증상 | 먼저 볼 곳 |
|---|---|
| 401 / 토큰 관련 오류 | access_token 만료·캐싱. 매 호출 재발급하면 발급 제한에 걸림 |
EGW00201 | 초당 호출 수. 폴링 간격과 종목 수를 함께 계산 |
| 서버·타겟 불일치 오류 | 토큰 발급 도메인과 호출 도메인이 다른지(실전 9443 / 모의 29443) |
rt_cd가 0이 아님 | msg_cd·msg1을 그대로 로그에 남길 것 |
| 특정 종목만 조회 실패 | ETN의 Q 접두어, 시장 구분(J/NX/UN) |
| 값이 이상함 | 문자열 비교, output 반복문, 시장 구분 불일치 |
에러코드 자체가 낯설다면 KIS API 에러코드 정리에 자주 나오는 것들을 모아 두었습니다.
자주 묻는 질문
현재가 조회에 계좌번호가 필요한가요?
필요 없습니다. CANO·ACNT_PRDT_CD는 잔고·주문처럼 계좌를 특정해야 하는 TR에서 씁니다.
시세 조회는 appkey·appsecret·access_token만 있으면 됩니다.
계좌번호를 헤더에 넣어도 무시되지만, 로그에 계좌번호가 남는 부작용이 있으니 빼 두는 편이 낫습니다.
장 마감 후에 호출하면 무엇이 오나요?
stck_prpr에는 마지막 체결가가 그대로 남아 있고 acml_vol도 그날 누적으로 고정됩니다.
즉 "지금 값"과 "마지막 값"이 응답만 봐서는 구분되지 않습니다.
장 운영 시간과 휴장일은 봇이 스스로 알고 있어야 합니다.
ETF도 Q를 붙이나요?
아닙니다. 공식 예제의 시장 구분 설명은 J를 주식·ETF·ETN을 포함하는 구분으로 안내하고,
Q 접두어는 ETN 종목코드에만 요구합니다. ETF는 일반 종목처럼 여섯 자리 코드를 그대로 넣습니다.
여러 종목을 한 번에 조회할 수 없나요?
inquire-price는 한 호출에 한 종목입니다. FID_INPUT_ISCD에 콤마로 나열해도 되지 않습니다.
다종목을 훑어야 한다면 순위·스캐너 계열 TR로 후보를 먼저 줄인 다음 현재가를 확인하는 2단 구조가 맞습니다.
2026-08-26 시점 한국투자증권 공식 저장소(koreainvestment/open-trading-api)의
국내주식 기본시세 예제 코드와 컬럼 매핑 파일에서 확인한 항목명·설명을 기준으로 썼고,
본문의 응답 예시 값은 구조 이해를 돕기 위한 것입니다.
증권사 명세와 호출 한도 정책은 예고 없이 바뀝니다.
운영에 넣기 전 개발자 포털의 현재 명세로 대조하시고,
도메인·tr_id·시장 구분은 코드에 상수로 박지 말고 설정으로 빼 두시기 바랍니다.
시세 조회부터 주문 전 안전장치까지 붙인 봇이 필요하다면
임시정지·VI·시장경고 필터, 호가단위 반올림, 웹소켓 전환까지 묶어서 실제로 돌아가는 자동매매 프로그램으로 만들어 드립니다. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기