AlgoLab Blog · 키움 종목정보 실무 · 2026

키움 REST API 전종목 조회 — ka10099 필드 함정

키움증권 · 유니버스 2026-08-23 · 약 7분 읽기 · 알고랩 AlgoLab
한 줄 요약 코스피·코스닥 전 종목 코드는 키움 REST API의 ka10099(종목정보 리스트) 한 번으로 받습니다. POST /api/dostk/stkinfo에 요청 항목은 mrkt_tp 하나뿐이고, 코스피 0 · 코스닥 10 · ETF 8입니다. 가장 많이 걸리는 지점은 응답 필드 표기법입니다 — 키움의 다른 TR은 stk_cd처럼 snake_case인데 ka10099code·name·listCount처럼 camelCase입니다. 그리고 listCount·lastPrice0으로 채워진 문자열로 옵니다.

봇을 만들 때 아무도 처음에 묻지 않지만 반드시 걸리는 질문이 있습니다. "그래서 무슨 종목을 감시하나?"

삼성전자 하나만 돌릴 거라면 코드를 상수로 박아 두면 됩니다. 그런데 조건검색이든 스캐너든 대상이 여럿인 순간 종목 목록이 필요해집니다. 키움 REST API 자동매매 완전 가이드가 다루는 여러 축 중 이 글은 "감시 대상 목록을 어디서 받아 어떻게 거르나" 하나만 떼어 다룹니다.

목차

  1. ka10099 — 요청 항목은 mrkt_tp 하나
  2. mrkt_tp 코드 16종 — 순서대로 세면 틀린다
  3. 함정 ① 혼자만 camelCase
  4. 함정 ② 0-padding 문자열
  5. 함정 ③ orderWarning — 봇이 빼야 할 종목
  6. 함정 ④ nxtEnable과 ka10100의 6자리 제약
  7. 실전 코드 — 유니버스 만들기
  8. 자주 묻는 질문

1. ka10099 — 요청 항목은 mrkt_tp 하나

키움증권이 배포하는 공식 Postman 컬렉션(운영 PRD 기준)에서 국내주식 > 종목정보 아래 네 개의 TR이 같은 URL을 씁니다.

api-id공식 이름요청 항목쓰임
ka10099종목정보 리스트mrkt_tp (필수)시장 하나를 통째로
ka10100종목정보 조회stk_cd (필수·6자리)한 종목만
ka10101업종코드 리스트mrkt_tp (필수)업종 코드표
ka10001주식기본정보요청stk_cd (필수)PER·PBR·시총 등 상세

넷 다 POST /api/dostk/stkinfo이고, 갈리는 것은 차트 TR 3종과 마찬가지로 헤더 api-id입니다. 요청은 이렇게 단순합니다.

POST https://api.kiwoom.com/api/dostk/stkinfo
Content-Type:  application/json;charset=UTF-8
authorization: Bearer {ACCESS_TOKEN}
api-id:        ka10099

{ "mrkt_tp": "0" }

토큰 발급과 만료 규칙은 접근토큰 유효기간·폐기에 정리돼 있습니다.

2. mrkt_tp 코드 16종 — 순서대로 세면 틀린다

ka10099유일한 요청 항목인데 값이 16종입니다. 그리고 연속된 번호가 아닙니다.

시장시장
0코스피2인프라투융자
10코스닥3ELW
30K-OTC4뮤추얼펀드
50코넥스5신주인수권
60ETN6리츠종목
70손실제한 ETN7신주인수권증서
80금현물8ETF
90변동성 ETN9하이일드펀드

가장 흔한 사고코스닥이 1이 아니라 10입니다. for i in range(2) 같은 반복문으로 코스피·코스닥을 돌리면 두 번째 호출이 코스닥이 아니라 존재하지 않는 값이 됩니다. 그리고 ETF는 8이라 한 자리 코드 그룹에 섞여 있습니다. ETF만 골라 돌리는 봇을 만든다면 이 값을 씁니다 — ETF가 개별종목과 무엇이 다른지는 ETF 자동매매 — 개별종목 봇과 다른 5가지에 있습니다.

3. 함정 ① 혼자만 camelCase

키움 REST API를 며칠 붙여 본 사람은 응답 필드가 stk_cd·cur_prc·trde_qty·oso_qty처럼 밑줄로 이어진 snake_case라는 것에 익숙해집니다. ka10099는 다릅니다.

{
  "list": [
    {
      "code":       "005930",
      "name":       "삼성전자",
      "listCount":  "0000000005969782550",   // 상장주식수 · 0-padding 문자열
      "auditInfo":  "정상",                   // 감리구분
      "regDay":     "19750611",              // 상장일 YYYYMMDD
      "lastPrice":  "00071800",              // 전일종가 · 0-padding 문자열
      "state":      "관리종목아님",
      "marketCode": "0",
      "marketName": "거래소",
      "upName":     "전기전자",              // 업종명
      "upSizeName": "대형주",                // 회사크기분류
      "orderWarning": "0",                   // 투자유의종목여부
      "nxtEnable":  "Y"                      // NXT 가능 여부
    }
  ],
  "return_code": 0,
  "return_msg":  "정상적으로 처리되었습니다"
}

요청은 mrkt_tp(snake_case), 응답은 listCount(camelCase)가 한 호출 안에 섞여 있습니다. 다른 TR용 파서를 재사용하면 모든 필드가 None으로 잡히고 빈 목록을 받은 것처럼 보입니다. 에러도 안 나고 return_code0이라 "왜 종목이 하나도 안 나오지"를 한참 헤매게 되는 자리입니다. 참고로 단건 조회 ka10100도 같은 camelCase이고, ka10001(주식기본정보요청)은 stk_nm·per·pbr처럼 다시 snake_case입니다.

4. 함정 ② 0-padding 문자열

공식 명세의 항목 설명을 그대로 옮기면 이렇습니다.

항목한글명명세 설명
listCount상장주식수단위: 1주, 좌측 0-padding 처리된 부호 포함 16자리 숫자
lastPrice전일종가단위: 원, 좌측 0-padding 처리된 부호 포함 8자리 숫자
regDay상장일YYYYMMDD
orderWarning투자유의종목여부코드값 (아래 5절)

ka10099의 응답 항목 타입은 전부 String입니다. 그래서 lastPrice를 문자열 그대로 비교하면 "00071800" < "00500000"처럼 사전순 비교가 되어 자릿수가 맞을 때만 우연히 맞습니다. 시가총액을 listCount × lastPrice로 구하려면 둘 다 정수 변환이 먼저입니다.

def to_int(v) -> int:
    """0-padding·부호 포함 문자열 → 정수. 빈 값은 0."""
    s = str(v or "").strip().replace(",", "")
    if not s or s in ("+", "-"):
        return 0
    return int(s)          # "0000000005969782550" → 5969782550, "-00001200" → -1200

이 성질은 이 TR만의 특징이 아닙니다. 잔고조회 kt00018이나 차트 TR에서도 값은 전부 문자열로 옵니다. 응답 직후 숫자로 바꾸는 계층을 한 번 두고 그 아래 전략 코드는 숫자만 다루게 하는 구조가 안전합니다.

5. 함정 ③ orderWarning — 봇이 빼야 할 종목

이 항목이 ka10099단순한 코드 목록 이상으로 만듭니다. 공식 명세의 orderWarning(투자유의종목여부) 코드값은 이렇습니다.

의미봇 관점
0해당없음정상 후보
2정리매매제외 — 상장폐지 절차 구간
3단기과열제외 권장 — 매매 방식이 달라질 수 있음
4투자위험제외
5투자경과제외 권장
1ETF투자주의요망ETF인 경우에만 전달

자동매매에서 이 필터가 없으면 어떤 일이 생기냐면 — 봇은 거래량이 튀는 종목을 좋아합니다. 그런데 거래량이 갑자기 튀는 종목 중 상당수가 정리매매나 단기과열 지정 구간에 있습니다. 사람이라면 종목명을 보는 순간 손을 떼지만 봇은 숫자만 보고 들어갑니다. 알트코인 봇의 유니버스 필터에서 다룬 "무엇을 후보에서 빼는가"가 국내주식에서 구체적인 필드로 존재하는 것이 orderWarning입니다.

이것만으로 충분하지는 않습니다. orderWarning지정 여부를 알려 줄 뿐이고, 각 지정의 기준·효과·해제 조건은 한국거래소 공시와 증권사 안내로 확인해야 합니다. 장중에 발동되는 변동성완화장치(VI)처럼 실시간으로 바뀌는 상태는 별도로 감시해야 하고, 목록은 최소 하루 1회 갱신하는 것을 전제로 설계하십시오.

6. 함정 ④ nxtEnable과 ka10100의 6자리 제약

nxtEnable(NXT가능여부)은 Y이면 가능이라고만 적혀 있습니다. 짧지만 중요한 필드입니다 — 대체거래소가 생긴 뒤 같은 종목이라도 어디로 주문을 보낼 수 있는지가 갈리기 때문입니다. 주문 경로 설계는 KRX·NXT·SOR 주문 라우팅에 있습니다.

여기서 미묘한 제약이 하나 더 붙습니다. 차트·주문 계열 TR의 stk_cd는 길이 20에 KRX:039490 / NXT:039490_NX / SOR:039490_AL처럼 거래소 접미사를 붙일 수 있다고 명세에 적혀 있습니다. 그런데 ka10100(종목정보 조회)의 stk_cd는 길이 6, 설명도 "종목코드 6자리"입니다. 접미사를 붙인 코드를 그대로 넘기면 안 된다는 뜻이므로, 코드 문자열을 여러 TR에 돌려 쓰는 구조라면 접미사를 떼는 지점을 명시해 두어야 합니다.

7. 실전 코드 — 유니버스 만들기

코스피·코스닥을 받아 유의종목을 걸러 봇이 감시할 목록을 만드는 최소 코드입니다.

import time, requests, pandas as pd

URL = "https://api.kiwoom.com/api/dostk/stkinfo"
EXCLUDE = {"2", "3", "4", "5"}          # 정리매매·단기과열·투자위험·투자경과

def fetch_market(token: str, mrkt_tp: str) -> list:
    rows, cont, nkey = [], "N", ""
    while True:
        headers = {
            "Content-Type":  "application/json;charset=UTF-8",
            "authorization": f"Bearer {token}",
            "api-id":        "ka10099",
        }
        if cont == "Y":                  # 2회차부터만 채운다
            headers["cont-yn"], headers["next-key"] = "Y", nkey

        r = requests.post(URL, headers=headers, json={"mrkt_tp": mrkt_tp}, timeout=10)
        r.raise_for_status()
        data = r.json()
        if data.get("return_code") not in (0, "0"):
            raise RuntimeError(data.get("return_msg"))

        rows.extend(data.get("list") or [])
        cont = r.headers.get("cont-yn", "N")
        nkey = r.headers.get("next-key", "")
        if cont != "Y":
            break
        time.sleep(0.3)                  # 호출 제한 여유
    return rows

def build_universe(token: str) -> pd.DataFrame:
    rows = fetch_market(token, "0") + fetch_market(token, "10")   # 코스피 + 코스닥
    df = pd.DataFrame(rows)
    df["listCount"] = df["listCount"].map(to_int)
    df["lastPrice"] = df["lastPrice"].map(to_int)
    df["mktCap"]    = df["listCount"] * df["lastPrice"]           # 개략 시가총액(원)
    df = df[~df["orderWarning"].isin(EXCLUDE)]                    # 유의종목 제외
    df = df[df["lastPrice"] > 0]                                  # 거래 불가 상태 제외
    return df.sort_values("mktCap", ascending=False).reset_index(drop=True)

cont-yn·next-key를 쓰는 이유는 시장 전체 목록이 한 번에 다 오지 않을 수 있기 때문입니다. 연속조회 헤더 규칙은 cont-yn·next-key에 따로 정리해 두었습니다.

한 가지만 더 — 여기서 만든 mktCaplistCount × lastPrice로 계산한 개략값입니다. 우선주·자기주식 등의 처리에 따라 공시 시가총액과 차이가 날 수 있습니다. 정확한 시가총액이 필요하면 ka10001(주식기본정보요청)의 mac(시가총액·억원 단위) 항목을 쓰십시오. 단위가 억원이라는 점에 주의해야 합니다.

자주 묻는 질문

Q. 마스터 파일을 내려받는 방식과 뭐가 다른가요?

한국투자증권 계열에서 흔히 쓰는 방식은 압축된 종목코드 마스터 파일을 내려받아 고정폭으로 잘라 읽는 것입니다. 키움 ka10099인증된 REST 호출 한 번으로 JSON을 돌려주므로 압축 해제·인코딩·고정폭 파싱 단계가 없습니다. 대신 호출 제한을 함께 고려해야 하고, 마스터 파일에 있는 일부 상세 항목은 없습니다.

Q. 얼마나 자주 갱신해야 하나요?

신규 상장·상장폐지·orderWarning 지정은 날짜 단위로 바뀝니다. 장 시작 전 1회 갱신해 파일이나 데이터베이스에 적재하고, 장중에는 그 스냅샷을 쓰는 구조가 무난합니다. 매 루프마다 전 종목을 다시 받는 구조는 호출 제한에 걸립니다.

Q. 여기서 거른 종목으로 바로 매매해도 되나요?

ka10099가 걸러 주는 것은 "거래하면 안 되는 상태의 종목"이지 "수익이 나는 종목"이 아닙니다. 유니버스 필터는 손실 가능성을 줄이는 위생 조치이지 성과를 보장하지 않으며, 과거 데이터로 확인한 결과가 미래를 보장하지도 않습니다. 이 글은 특정 종목을 추천하지 않습니다.

마무리

ka10099는 요청 항목이 하나뿐이라 가장 쉬운 TR처럼 보입니다. 실제로 사람을 잡는 것은 네 군데였습니다 — mrkt_tp가 연속 번호가 아니라는 것(코스닥 10·ETF 8), 혼자만 camelCase인 응답, 0으로 채워진 문자열, 그리고 orderWarning을 안 걸러서 정리매매 종목에 봇이 들어가는 것입니다. 받은 목록으로 무엇을 할지 — 차트를 붙이는 다음 단계는 키움 차트 데이터 ka10081로 이어집니다.

※ 본문의 요청·응답 항목명과 코드값은 키움증권이 배포하는 공식 Postman 컬렉션(운영 PRD) 기준이며 2026-08-23 확인한 내용입니다. 투자유의종목 지정 기준과 시장 제도는 변경될 수 있으므로 한국거래소 공시와 키움 REST API 공식 가이드를 함께 확인하십시오.

종목 선별부터 주문까지, 통째로 맡기시겠어요?

유니버스 필터·시세 수집·주문 실행을 하나로 묶은 봇을 제작합니다.
24시간 빠른 답변 가능합니다.

제작 상담하기