AlgoLab Blog · KIS API 트러블슈팅 · 2026

KIS API 계좌번호 오류 — CANO·ACNT_PRDT_CD

한국투자증권 · 계좌 2026-08-12 · 약 6분 읽기 · 알고랩 AlgoLab
한 줄 요약 KIS API는 계좌번호를 통째로 받지 않습니다. 앱에 12345678-01로 표시되는 계좌번호라면 앞 8자리 12345678CANO(종합계좌번호), 뒤 2자리 01ACNT_PRDT_CD(계좌상품코드)입니다. 하이픈은 빼고, 둘 다 문자열로 넣습니다. 값을 맞게 적었는데도 계속 실패한다면 원인은 대개 값이 아니라 타입입니다 — 설정 파일에 따옴표 없이 적어 01이 정수 1로 읽히면서 앞자리 0이 사라진 것이 가장 흔합니다.

KIS API 발급 가이드대로 appkey·appsecret을 받고 access_token까지 정상 발급됐는데, 막상 잔고를 조회하거나 주문을 넣으면 실패하는 경우가 있습니다. 토큰은 멀쩡하니 인증 문제는 아닌 것 같고, 에러코드 목록을 봐도 딱 떨어지는 항목이 안 보입니다.

이럴 때 가장 먼저 의심할 곳이 계좌 파라미터입니다. 이 글은 CANOACNT_PRDT_CD 하나만 다룹니다.

이 글의 순서

  1. 증상 — 응답이 어떻게 오나
  2. 정답 — 8자리와 2자리로 나눈다
  3. 실제로 나는 실수 5가지
  4. 설정 파일에 쓰는 법
  5. 검증 함수 — 눈으로 보지 말고 코드로 센다
  6. 어느 요청에 들어가나 · 체크리스트 · FAQ

1. 증상 — 응답이 어떻게 오나

KIS API의 모든 응답에는 rt_cd·msg_cd·msg1 세 필드가 있습니다. 계좌 파라미터가 잘못됐을 때는 HTTP 상태코드는 200인데 rt_cd0이 아닌 형태로 옵니다. 즉 파이썬에서 예외가 안 납니다. res.raise_for_status()만 걸어 둔 코드는 이 실패를 그냥 통과시킵니다.

res = requests.get(url, headers=headers, params=params, timeout=5)
print(res.status_code)          # 200  ← 여기서 통과해 버린다
data = res.json()
print(data["rt_cd"])            # "0"이 아니다
print(data["msg_cd"], data["msg1"])   # 메시지에 '계좌' 관련 문구가 담겨 온다

먼저 할 일. 모든 호출을 아래처럼 감싸 두면 계좌 문제인지 다른 문제인지가 로그 한 줄로 갈립니다. msg_cd는 호출한 tr_id와 상황에 따라 달라지므로, 코드를 외우지 말고 그때그때 찍어서 확인하는 편이 빠릅니다.

def check(res):
    """KIS 응답 표준 검증 — rt_cd가 0이 아니면 즉시 중단"""
    data = res.json()
    if data.get("rt_cd") != "0":
        raise RuntimeError(
            f"KIS 에러 [{data.get('msg_cd')}] {data.get('msg1','').strip()}"
        )
    return data

2. 정답 — 8자리와 2자리로 나눈다

한국투자증권 계좌번호는 종합계좌번호 8자리 + 상품코드 2자리 구조입니다. KIS API는 이 둘을 별도 파라미터로 받습니다.

파라미터무엇자릿수예시
CANO종합계좌번호 (앞부분)8자리"12345678"
ACNT_PRDT_CD계좌상품코드 (뒷부분)2자리"01"
12345678 - 01 앱·HTS에 보이는 계좌번호 CANO = "12345678" ACNT_PRDT_CD = "01" 하이픈은 버린다
계좌번호 하나가 파라미터 두 개로 나뉜다
ACCOUNT = "12345678-01"          # 앱에 보이는 그대로

cano, acnt_prdt_cd = ACCOUNT.split("-")
print(repr(cano), repr(acnt_prdt_cd))    # '12345678' '01'

params = {
    "CANO":          cano,           # 문자열
    "ACNT_PRDT_CD":  acnt_prdt_cd,   # 문자열 — "1"이 아니라 "01"
    # ... 나머지 조회·주문 파라미터
}

3. 실제로 나는 실수 5가지

① 하이픈을 그대로 넣는다

"12345678-01"CANO에 통째로 넣는 경우입니다. 가장 단순하지만 가장 흔합니다. 하이픈은 표시용이고 API로는 보내지 않습니다.

② 정수로 변환해 앞자리 0이 날아간다

이 글에서 가장 중요한 항목입니다. 상품코드 01을 숫자로 다루는 순간 1이 됩니다. 종합계좌번호가 0으로 시작하는 계좌라면 8자리가 7자리가 됩니다.

>>> str(int("01"))
'1'          # ← 두 자리가 한 자리로. 이대로 보내면 계좌를 못 찾는다

>>> str(int("01234567"))
'1234567'    # ← 8자리가 7자리로

따옴표 하나 때문에 생깁니다. YAML·JSON 설정 파일에서 acnt_prdt_cd: 01처럼 따옴표 없이 적으면 파서가 숫자로 읽습니다. 코드는 멀쩡하고 값도 맞게 적었는데 실패하므로 원인을 찾는 데 제일 오래 걸리는 종류입니다. 반드시 acnt_prdt_cd: "01"따옴표를 붙이십시오.

③ 상품코드를 01로 고정해 버린다

예제 코드 대부분이 "01"로 되어 있어서 그대로 상수로 박아 두는 경우입니다. 상품코드는 계좌 종류를 구분하는 값이라 계좌마다 다를 수 있습니다. 한 사람이 계좌를 여러 개 가지고 있으면 특히 그렇습니다. 자기 앱에 표시된 계좌번호의 뒤 두 자리를 그대로 쓰십시오.

④ 실전 계좌번호로 모의투자를 호출한다

모의투자는 계좌번호·appkey·appsecret이 실전과 완전히 별개입니다. 실전 계좌번호를 넣고 모의 도메인으로 부르면 형식은 멀쩡한데 계좌를 못 찾습니다. tr_id도 세트로 바뀝니다 — 자세한 전환 목록은 모의투자 실전 전환 6가지에 정리해 두었습니다.

구조로 막는 법. 계좌번호·도메인·tr_id환경 하나에 묶어 두고, 코드 어디에서도 개별로 바꾸지 못하게 하십시오. 세 값이 따로 놀 수 있는 구조면 언젠가 반드시 어긋납니다.

⑤ 공백·줄바꿈이 섞여 있다

설정 파일이나 .env에서 값을 읽을 때 뒤에 공백이나 줄바꿈이 붙어 오는 경우입니다. "12345678 "은 눈으로는 정상이고 길이만 9입니다. 항상 .strip()을 거치십시오.

4. 설정 파일에 쓰는 법

한국투자증권 공식 예제는 설정을 별도 파일로 분리하는 방식을 씁니다. 같은 형태로 쓰되 계좌 값 두 개를 반드시 따옴표로 감쌉니다.

# kis_config.yaml
# ── 실전 ─────────────────────────────
prod_appkey:      "PSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
prod_appsecret:   "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
prod_cano:        "12345678"     # ← 따옴표 필수
prod_acnt_prdt:   "01"           # ← 따옴표 없으면 1로 읽힌다

# ── 모의 ─────────────────────────────
paper_appkey:     "PSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
paper_appsecret:  "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
paper_cano:       "50123456"
paper_acnt_prdt:  "01"

이 파일은 절대 저장소에 올리지 마십시오. appkey·appsecret과 계좌번호가 한 파일에 같이 들어 있습니다. .gitignore 등록과 키 관리 원칙은 API 키 보안에 정리해 두었습니다.

5. 검증 함수 — 눈으로 보지 말고 코드로 센다

계좌 파라미터 문제는 보면 정상으로 보이기 때문에 눈으로 찾기 어렵습니다. 봇 기동 시점에 길이와 타입을 한 번 검사하고, 어긋나면 그 자리에서 멈추게 하십시오. 잘못된 계좌 값으로 주문 루프에 진입하는 것보다 훨씬 낫습니다.

def validate_account(cano, acnt_prdt_cd):
    """봇 기동 시 1회 — 여기서 못 잡으면 주문 루프에서 터진다"""
    for name, val, size in (("CANO", cano, 8), ("ACNT_PRDT_CD", acnt_prdt_cd, 2)):
        if not isinstance(val, str):
            raise ValueError(f"{name}: 문자열이 아님 ({type(val).__name__}) "
                             f"— 설정 파일에 따옴표를 빠뜨렸을 가능성")
        if val != val.strip():
            raise ValueError(f"{name}: 앞뒤 공백/줄바꿈 포함 → {val!r}")
        if len(val) != size:
            raise ValueError(f"{name}: {size}자리가 아님 (현재 {len(val)}자리) → {val!r}")
        if not val.isdigit():
            raise ValueError(f"{name}: 숫자 외 문자 포함(하이픈?) → {val!r}")
    return True

validate_account("12345678", "01")   # OK
validate_account("12345678", 1)      # ValueError: 문자열이 아님 (int)
validate_account("12345678-01", "01")# ValueError: 8자리가 아님 (11자리)

효과. 이 함수 하나가 실수 ①·②·⑤를 전부 기동 3초 안에 잡아냅니다. 남은 ③·④는 값이 형식상 정상이라 코드로는 못 잡으니, 실행 환경 이름을 로그에 찍어 사람이 보게 하는 편이 낫습니다.

💬
여기서 며칠째 막혀 있다면

계좌 연결부터 주문·잔고까지 동작하는 상태로 만들어 드립니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기 →

6. 어느 요청에 들어가나

계좌를 특정해야 하는 요청은 거의 전부입니다. 시세 조회처럼 계좌와 무관한 요청에는 들어가지 않습니다.

반대로 현재가 조회 같은 시세 API에는 계좌가 필요 없습니다. "시세는 되는데 잔고만 안 된다"면 계좌 파라미터를 의심하라는 신호로 읽으면 됩니다.

체크리스트

자주 묻는 질문

Q. CANO와 ACNT_PRDT_CD는 각각 무엇인가요?

계좌번호를 나눈 것입니다. CANO는 앞 8자리 종합계좌번호, ACNT_PRDT_CD는 뒤 2자리 계좌상품코드입니다. 하이픈은 넣지 않고 둘 다 문자열로 보냅니다.

Q. 값은 맞는데 계속 오류가 납니다.

값보다 타입을 보십시오. 설정 파일에 따옴표를 빠뜨려 "01"이 정수 1로 읽히는 경우가 가장 흔합니다. 그다음이 하이픈 포함, 공백·줄바꿈 혼입입니다.

Q. ACNT_PRDT_CD는 항상 01인가요?

아닙니다. 예제에 자주 쓰이는 값일 뿐 계좌마다 다를 수 있습니다. 앱에 표시된 계좌번호의 뒤 두 자리를 그대로 쓰고, 코드 체계는 한국투자증권 공식 안내에서 확인하십시오.

Q. 모의투자와 실전투자는 계좌번호가 다른가요?

다릅니다. 앱키·앱시크릿도 별개이고 tr_id와 도메인까지 세트로 바뀝니다. 셋을 한 환경으로 묶어 관리하십시오.

확인 캐치. 이 글의 파라미터명과 자릿수 규칙은 2026년 8월 12일 기준 한국투자증권 KIS Developers의 국내주식 주문·잔고 계열 API가 계좌를 받는 방식에 근거합니다. 상품코드 값 체계와 API 스펙은 증권사 사정으로 변경될 수 있으므로 구현 전 KIS Developers 공식 문서와 본인 계좌 정보에서 실제 값을 확인하십시오. 본 글은 특정 종목이나 수익률에 대한 어떠한 예측이나 권유도 담고 있지 않습니다.

마무리

정리하면 한 줄입니다. 계좌번호는 앞 8자리 CANO와 뒤 2자리 ACNT_PRDT_CD로 나누고, 둘 다 문자열로 보낸다. 그리고 실패의 대부분은 값이 아니라 따옴표 하나에서 옵니다.

인증 쪽이 함께 흔들리고 있다면 토큰 만료·재발급 규칙도 같이 확인해 보십시오.

계좌 연결에서 막혔다면

인증·계좌·주문까지 실제로 도는 상태로 만들어 드립니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기