KIS 주식기본조회 CTPF1002R — 봇이 쓸 필드 9개
한국투자증권 KIS API의 주식기본조회는 GET /uapi/domestic-stock/v1/quotations/search-stock-info에 tr_id CTPF1002R, 쿼리 PRDT_TYPE_CD=300·PDNO=005930 두 개만 넣으면 종목 1개의 '기본 정보' 67개 필드를 돌려줍니다. 봇에 쓸 것은 거래정지(tr_stop_yn)·관리종목(admn_item_yn)·NXT 거래가능(cptt_trad_tr_psbl_yn)·상장일·상장주수 등 9개입니다. 공식 저장소 legacy/README.md의 모의투자 제공 칸이 비어 있어 실전 앱키로만 부를 수 있고, 공식 kis_auth.py는 모의 모드에서 C로 시작하는 tr_id를 VTPF1002R로 바꿔 보내므로 모의 키로는 실패합니다.
목차
- 스펙 한 장 — tr_id·URL·모의 지원
- 요청·응답 실물
- 67개 중 봇이 쓸 필드 9개
- 함정 5가지
- 현재가 플래그·마스터파일과 어떻게 나눠 쓰나
- 자주 묻는 질문
현재가 조회 한 번으로 주문 전 안전장치를 만드는 법은 허브 글 KIS API 현재가 조회 — 관리종목·VI까지 한 번에에서, 관리종목·투자경고를 거르는 플래그는 mang_issu_cls_code 관리종목 필터에서 다뤘습니다. 이 글은 그 둘과 짝을 이루면서 사이트에서 한 번도 다루지 않았던 주식기본조회(CTPF1002R) 하나만 떼어 냅니다. 출처는 한국투자증권 공식 GitHub 저장소 koreainvestment/open-trading-api의 examples_llm/domestic_stock/search_stock_info/, MCP/KIS Code Assistant MCP/data.csv, MCP/Kis Trading MCP/configs/domestic_stock.json, legacy/README.md·legacy/postman/입니다(2026-10-04 zipball 기준).
1. 스펙 한 장 — tr_id·URL·모의 지원
| 항목 | 값 | 출처 |
|---|---|---|
| API 이름 | 주식기본조회 [v1_국내주식-067] | search_stock_info.py |
| 메서드·URL | GET /uapi/domestic-stock/v1/quotations/search-stock-info | 같은 파일 API_URL |
| tr_id | CTPF1002R (실전) | 같은 파일 · Postman J_주식기본조회 |
PRDT_TYPE_CD | 300 주식·ETF·ETN·ELW / 301 선물옵션 / 302 채권 / 306 ELS | data.csv args |
PDNO | 종목번호 6자리, ETN은 Q로 시작(예: Q500001) | data.csv args |
| 모의투자 | 미제공 (제공 여부 칸 공란 · 실전 Postman 컬렉션에만 존재) | legacy/README.md · legacy/postman/README.md |
| 응답 | output 단일 객체, 필드 67개 | chk_search_stock_info.py COLUMN_MAPPING |
2. 요청·응답 실물
헤더는 다른 KIS 시세 API와 같습니다(authorization·appkey·appsecret·tr_id·custtype). 공식 Postman 샘플의 요청 줄은 아래와 같습니다.
GET {{PROD}}/uapi/domestic-stock/v1/quotations/search-stock-info?PDNO=005930&PRDT_TYPE_CD=300
content-type: application/json
authorization: Bearer {{PROD_TOKEN}}
appkey: {{PROD_APPKEY}}
appsecret: {{PROD_APPSECRET}}
tr_id: CTPF1002R
custtype: P
파이썬으로는 이렇게 부릅니다. 앱키는 환경 변수로만 읽고 코드에 적지 않습니다.
import os, requests
BASE = "https://openapi.koreainvestment.com:9443" # 실전 도메인 — 모의 도메인에선 이 API가 없다
URL = BASE + "/uapi/domestic-stock/v1/quotations/search-stock-info"
def stock_info(code, token):
headers = {
"content-type": "application/json",
"authorization": f"Bearer {token}",
"appkey": os.environ["KIS_APPKEY"],
"appsecret": os.environ["KIS_APPSECRET"],
"tr_id": "CTPF1002R",
"custtype": "P",
}
r = requests.get(URL, headers=headers,
params={"PRDT_TYPE_CD": "300", "PDNO": code}, timeout=5)
body = r.json()
if body.get("rt_cd") != "0":
raise RuntimeError(f"{body.get('msg_cd')} {body.get('msg1')}")
return body["output"] # dict 하나 — 리스트가 아니다
응답 output의 키는 공식 매핑 기준으로 아래처럼 생겼습니다(값은 종목마다 다르므로 생략, 67개 중 일부).
{
"rt_cd": "0", "msg_cd": "...", "msg1": "...",
"output": {
"pdno": "...", "prdt_abrv_name": "...", "mket_id_cd": "...", "excg_dvsn_cd": "...",
"lstg_stqt": "...", "kospi200_item_yn": "...",
"scts_mket_lstg_dt": "...", "kosdaq_mket_lstg_dt": "...", "lstg_abol_dt": "...",
"etf_dvsn_cd": "...", "etf_chas_erng_rt_dbnb": "...", "etf_etn_ivst_heed_item_yn": "...",
"tr_stop_yn": "...", "admn_item_yn": "...",
"std_idst_clsf_cd_name": "...", "idx_bztp_lcls_cd_name": "...",
"cptt_trad_tr_psbl_yn": "...", "nxt_tr_stop_yn": "...",
"...": "..."
}
}
3. 67개 중 봇이 쓸 필드 9개
67개 필드의 상당수(ocr_no·frbd_mket_lstg_dt 프리보드 상장일·mfnd_opng_dt 뮤추얼펀드 개시일 등)는 자동매매와 무관합니다. 실제로 쓸모 있는 것만 추리면 아래 9개입니다. 한글 뜻은 공식 COLUMN_MAPPING 원문입니다.
| 필드 | 공식 뜻 | 봇에서 쓰는 곳 |
|---|---|---|
tr_stop_yn | 거래정지여부 | 주문 전 차단 |
admn_item_yn | 관리종목여부 | 유니버스 제외 |
etf_etn_ivst_heed_item_yn | ETFETN투자유의종목여부 | ETF·ETN 전략의 제외 필터 |
cptt_trad_tr_psbl_yn | NXT 거래종목여부 | 프리·애프터마켓 주문 라우팅 판단 |
nxt_tr_stop_yn | NXT 거래정지여부 | NXT로 보낼 주문 차단 |
lstg_stqt | 상장주수 | 시가총액 직접 계산·유동성 필터 |
scts_mket_lstg_dt / kosdaq_mket_lstg_dt | 유가증권/코스닥시장 상장일자 | 신규 상장 N일 미만 제외 |
kospi200_item_yn | 코스피200종목여부 | 대형주 유니버스 |
etf_chas_erng_rt_dbnb | ETF추적수익율배수 | 레버리지·인버스 ETF 구분 |
업종 분산이 필요하면 std_idst_clsf_cd_name(표준산업분류코드명)이나 idx_bztp_lcls_cd_name(지수업종대분류코드명)으로 한 업종 쏠림을 막을 수 있습니다. 레버리지 ETF를 다룰 때의 별도 주의점은 ETF 자동매매 — 괴리율·유동성에 있습니다.
4. 함정 5가지
함정 1 — 모의 앱키로 부르면 tr_id가 바뀐다
공식 examples_user/kis_auth.py의 _url_fetch()는 tr_id 첫 글자가 T·J·C이고 모의 모드이면 "V" + ptr_id[1:]로 바꿉니다. CTPF1002R은 C로 시작하므로 모의 키로는 VTPF1002R이 나가는데, 이 API는 모의 제공 목록에 없습니다. 반대로 FHKST01010100(현재가)은 F로 시작해 모의·실전이 같습니다. 그래서 '현재가는 모의에서 되는데 기본조회만 안 된다'는 현상이 나옵니다. 모의 환경에서 전략을 검증할 때 종목정보는 실전 앱키(조회 전용)로 받고 주문만 모의로 보내는 식으로 키를 나누는 것이 일반적입니다 — KIS 모의투자 한계 — 안 되는 API 10가지 참고.
함정 2 — 이름이 비슷한 '상품기본조회' CTPF1604R
같은 종목정보 묶음에 상품기본조회가 따로 있습니다. tr_id CTPF1604R, URL은 /quotations/search-info(뒤에 stock-가 없음)이고, Postman 설명상 PRDT_TYPE_CD에 512(미국 나스닥)·515(일본) 같은 해외 상품 코드까지 받는 범용 조회입니다. 국내주식의 거래정지·관리종목·NXT 필드가 필요하면 search-stock-info(CTPF1002R) 쪽입니다. URL 한 단어 차이라 복사하다 섞이기 쉽습니다.
함정 3 — Postman 헤더 설명에 다른 tr_id가 적혀 있다
공식 실전계좌_POSTMAN_샘플코드_v2.6.json의 J_주식기본조회 요청을 보면 tr_id 헤더의 값은 CTPF1002R인데 설명란에는 [실전투자] CTOS5011R이 적혀 있습니다. 다른 API 설명이 복사된 것으로 보입니다. 값과 examples_llm 예제가 모두 CTPF1002R이므로 그쪽을 따르되, 의심되면 KIS Developers 포털의 원문 명세를 확인하십시오.
함정 4 — 종목 1개 = 1콜, 전 종목에 돌리면 느리다
이 API는 종목 코드 하나를 받아 한 행을 돌려줍니다. 공식 예제는 tr_cont == "M"일 때 다음 페이지를 재귀 호출(max_depth=10)하도록 짜여 있지만, 단일 종목 조회라 실질적으로 한 번에 끝납니다. 문제는 코스피·코스닥 2천여 종목 전체에 돌릴 때입니다. 공식 kis_auth.py의 실전 대기값 0.05초에 응답 시간까지 더하면 수 분이 걸리고, 같은 앱키로 다른 봇이 돌고 있으면 EGW00201(초당 거래건수 초과)을 만나기 쉽습니다(EGW00201 해결). 전 종목 1차 필터는 종목코드 마스터파일(kospi_code.mst)로 하고, 이 API는 후보 수십 개에만 쓰는 것이 맞습니다.
함정 5 — 'Y/N' 문자열과 빈 값, 그리고 갱신 시점
KIS 응답은 숫자도 문자열로 옵니다. lstg_stqt는 int()로, 상장일은 YYYYMMDD 문자열로 보고 변환해야 하며, 코스피 종목은 kosdaq_mket_lstg_dt가, 코스닥 종목은 scts_mket_lstg_dt가 쓸모없는 값이므로 mket_id_cd로 어느 쪽을 읽을지 먼저 정합니다. 더 중요한 것은 공식 문서에 이 필드들이 장중 언제 갱신되는지 적혀 있지 않다는 점입니다. 장중에 걸리는 거래정지·VI는 이 값보다 현재가 응답의 temp_stop_yn·iscd_stat_cls_code로 직전에 다시 확인하십시오.
from datetime import date, datetime
def pass_filter(o, min_days=60):
if o["tr_stop_yn"] == "Y" or o["admn_item_yn"] == "Y":
return False, "거래정지/관리종목"
if o.get("etf_etn_ivst_heed_item_yn") == "Y":
return False, "ETF·ETN 투자유의"
lstg = o["kosdaq_mket_lstg_dt"] or o["scts_mket_lstg_dt"] # 시장별로 한쪽만 의미 있음
if lstg and (date.today() - datetime.strptime(lstg, "%Y%m%d").date()).days < min_days:
return False, f"상장 {min_days}일 미만"
return True, "ok"
def nxt_ok(o):
# 프리·애프터마켓(NXT)에 주문을 보내기 전 확인
return o.get("cptt_trad_tr_psbl_yn") == "Y" and o.get("nxt_tr_stop_yn") != "Y"
위 코드의 or 분기는 해당 시장이 아닐 때 빈 문자열이 온다는 가정입니다. 실제 형식은 첫 호출에서 print(o)로 확인한 뒤 확정하십시오.
5. 현재가 플래그·마스터파일과 어떻게 나눠 쓰나
| 경로 | 범위 | 강점 | 언제 |
|---|---|---|---|
마스터파일 kospi_code.mst | 전 종목 | API 호출 0회 | 하루 1번 |
주식기본조회 CTPF1002R | 종목 1개 | 상장일·상장주수·NXT 거래가능·ETF 배수 | 장 전 후보만, 캐시 |
현재가 FHKST01010100 | 종목 1개 | temp_stop_yn·mang_issu_cls_code 등 장중 상태 | 주문 직전 매번 |
NXT 거래가능 여부는 이 API에서만 한 필드로 바로 나오는 정보라, NXT·KRX 주문 라우팅에서 SOR·NXT로 주문을 보내는 봇이라면 장 전 캐시에 꼭 넣어 두는 것이 좋습니다. 시가총액을 직접 계산할 때는 lstg_stqt × stck_prpr로 시가총액 상위 API의 stck_avls를 대조할 수 있습니다. 에러 코드 전반은 KIS API 에러코드 정리에 있습니다.
이 글의 필터는 종목 추천이 아니라 '사면 안 되는 종목을 거르는 안전장치'입니다. 필터를 통과했다고 수익이 나는 것은 아니며, 과거 성과가 미래를 보장하지 않습니다. KIS API 명세는 예고 없이 바뀔 수 있으니 KIS Developers 공식 문서를 확인하십시오.
6. 자주 묻는 질문
KIS API 주식기본조회의 tr_id와 URL은 무엇인가요?
tr_id는 CTPF1002R이고 GET /uapi/domestic-stock/v1/quotations/search-stock-info 로 호출합니다. 쿼리 파라미터는 PRDT_TYPE_CD(주식·ETF·ETN·ELW는 300)와 PDNO(종목번호 6자리, ETN은 Q로 시작) 두 개입니다.
모의투자 앱키로 주식기본조회를 호출할 수 있나요?
공식 저장소 legacy/README.md의 모의투자 제공 여부 칸이 비어 있고 Postman 샘플도 실전 컬렉션에만 있습니다. 공식 kis_auth.py는 모의 모드에서 C로 시작하는 tr_id를 VTPF1002R로 바꿔 보내므로 실패합니다. 종목정보는 실전 앱키로 조회하고 주문만 모의로 보내는 식으로 키를 나눠 쓰는 것이 일반적입니다.
주식기본조회와 상품기본조회(CTPF1604R)는 무엇이 다른가요?
상품기본조회는 /quotations/search-info 경로에 tr_id CTPF1604R을 쓰며 해외 상품 코드(512 미국 나스닥 등)까지 받는 범용 조회입니다. 국내주식의 거래정지여부·관리종목여부·NXT 거래종목여부 같은 필드가 필요하면 search-stock-info(CTPF1002R)를 써야 합니다.
주식기본조회만으로 장중 거래정지를 판단해도 되나요?
권하지 않습니다. 공식 문서에 이 필드들의 장중 갱신 시점이 적혀 있지 않습니다. 주식기본조회는 장 전 후보 필터로 쓰고, 주문 직전에는 현재가 조회(FHKST01010100) 응답의 temp_stop_yn·iscd_stat_cls_code로 다시 확인하는 2단 구조가 안전합니다.