화면은 멀쩡한데 숫자가 안 바뀌었다 — 키움 API 8005 에러

거래대금 목록이 텅 비었다. 에러 메시지도 없었다. 엉뚱한 데를 하루 팠고, 원인은 살아 있는 줄 알았던 출입증이 이미 무효가 된 것이었다.

  • 키움증권
  • API
  • 토큰
  • 삽질

어느 날 아침 대시보드를 켰더니 거래대금 상위 목록이 텅 비어 있었다. 에러 메시지는 없었다. 그냥 아무것도 없었다.

화면 위에는 mode=NXT라고 떠 있었다. 오전 8시부터 9시 50분까지는 NXT라는 시장을 조회하도록 만들어 뒀기 때문이다. 그래서 나는 당연히 NXT 조회가 잘못됐구나 하고 생각했다.

그게 하루를 날린 시작이었다.

화면에 보이는 걸 의심했다

먼저 거래소를 구분하는 설정값을 의심했다. 내 프로그램은 증권사에 데이터를 요청할 때 “어느 시장을 볼 거냐”를 숫자로 지정한다. 그 숫자를 이리저리 바꿔가며 시험했다. 전부 0건이었다.

그다음엔 시간을 의심했다. 아직 장이 열리기 전이라 데이터가 없나? 아니었다. 9시 30분이 넘었는데도 0건이었다.

여기서 막혔다. 그런데 막힌 방식이 좀 특이했다.

확인할 수단이 바닥났다

나는 코드를 직접 못 읽는다. 뭔가를 확인하려면 AI에게 물어봐야 한다. 그런데 설정값을 이리저리 바꿔보는 사이에 그날 쓸 수 있는 AI 사용량을 다 써버렸다.

내가 쓰는 요금제는 사용량이 다섯 시간 단위로 초기화된다. 다 쓰면 그 시간이 지날 때까지 기다려야 한다. 그래서 원인을 못 찾은 채로 몇 시간을 그냥 앉아 있었다.

이게 개발자와 나의 결정적인 차이다. 개발자는 막히면 스스로 다음 수를 둘 수 있다. 나는 다음 수를 두려면 매번 도구를 써야 하고, 그 도구에는 한도가 있다.

그래서 틀린 추측의 비용이 다르다. 개발자에게 헛다리는 시간 낭비지만, 나에게는 그날 남은 확인 기회를 태우는 일이다. 설정값을 세 번 바꿔보는 데 쓴 사용량은, 원래 다른 곳에 써야 했던 것이었다.

한도가 돌아온 뒤 제일 먼저 한 일이 그거였다. 증권사가 실제로 뭐라고 답했는지 그대로 찍어보기.

이게 그 답이다.

{
  "return_code": "3",
  "return_msg": "8005 : 인증키가 유효하지 않습니다."
}

읽는 법은 간단하다. 아래쪽 return_msg가 증권사가 보낸 한 줄 설명이다. “인증키가 유효하지 않습니다.”

NXT는 아무 죄가 없었다. 내 프로그램이 신분 확인을 통과하지 못하고 있었다.

배달은 왔는데 상자가 비어 있었다

내가 놓친 첫 번째 지점이 여기다.

프로그램이 외부에서 데이터를 받아올 때는 두 가지를 확인해야 한다. 하나는 “연락이 닿았느냐”, 다른 하나는 “받아온 내용이 쓸 만하냐” 다.

택배로 비유하면 이렇다. 초인종이 울리고 상자가 도착했다 — 연락은 닿았다. 그런데 상자를 열어보니 물건 대신 “재고 없음” 쪽지가 들어 있다.

내 코드는 초인종만 확인하고 있었다. 상자가 도착했으니 성공이라고 판단하고 그냥 넘어갔다. 안에 든 “인증키가 유효하지 않습니다”를 아무도 읽지 않았다.

그러니 종목 목록은 빈 채로 화면에 전달됐고, 화면은 에러 하나 없이 조용히 비었다.

외부에서 데이터를 받아올 때 “연결됐다”는 신호만 보고 성공이라 판단하면 안 된다. 받아온 내용 안에 실패 사유가 적혀 있을 수 있다. 국내 금융 회사의 데이터 창구는 대체로 이 방식이다.

출입증이 이미 무효였다

더 이상한 게 있었다. 내 프로그램은 신분 확인을 제대로 관리하고 있었다.

증권사 데이터를 받으려면 먼저 출입증 같은 것을 발급받아야 한다. 전문 용어로는 토큰이라고 부르는데, 건물 출입증과 거의 같다. 한 번 발급받아 목에 걸고 다니면 문이 열리고, 유효기간이 적혀 있다.

내 프로그램은 그 유효기간을 지키고 있었다. 만료 3분 전부터 새로 발급받도록 해뒀다. 매번 새로 발급받으면 증권사가 차단한다고 해서 일부러 아껴 쓰기도 했다.

그런데 키움에는 내가 모르던 규칙이 있었다. 같은 계정으로 새 출입증을 발급하면, 이전 출입증이 그 자리에서 무효가 된다. 유효기간이 얼마나 남았든 상관없다.

그리고 그날 나는 작업하던 곳을 옮겼다. 원래 VS Code에서 하고 있었는데, 도중에 Claude 쪽으로 옮겨서 이어서 했다.

문제는 VS Code에서 켜둔 프로그램을 끄지 않은 것이다. 그 상태로 다른 쪽에서 또 띄웠다. 같은 계정을 쓰는 프로그램이 두 개가 됐다.

나중에 띄운 쪽이 출입증을 새로 받는 순간, 먼저 떠 있던 쪽의 출입증이 즉시 죽었다. 그리고 내가 화면으로 보고 있던 건 먼저 떠 있던 쪽이었다.

  1. VS Code에서 프로그램을 띄웠다 키움에서 출입증 A를 발급받는다
  2. 끄지 않은 채, Claude에서 또 띄웠다 같은 계정으로 출입증 B를 발급받는다
  3. 키움이 출입증 A를 즉시 무효로 만든다 유효기간이 얼마나 남았든 상관없다
  4. A를 든 쪽은 그걸 모른다 계속 요청하고 계속 거절당한다 — 8005
같은 계정으로 두 번 발급하면, 먼저 받은 출입증이 그 자리에서 죽는다.

여기서 최악은, 죽은 쪽이 자기가 죽은 걸 모른다는 것이다. 목에 걸린 출입증에 적힌 유효기간은 아직 한참 남아 있다. 그러니 계속 그걸 들고 문 앞에 서서 계속 거절당한다. 적힌 기간이 끝날 때까지 몇 시간 동안 영영 안 되는 상태가 된다.

증상이 “가끔 되다 안 된다”가 아니라 “한번 죽으면 오래 완전히 안 된다”였던 이유다.

고친 방법

유효기간을 믿는 걸 포기했다. 대신 규칙을 하나 바꿨다.

“증권사가 무효라고 답하면, 그때 새 출입증을 받아서 딱 한 번 다시 요청한다.”

적어놓으면 한 줄인데 이게 핵심이다. 시계를 믿는 방식에서, 상대방의 대답을 믿는 방식으로 바뀌었다.

여기서 중요한 건 “딱 한 번” 이다. 무한히 다시 시도하게 만들었다면, 계정 자체가 잘못됐을 때 출입증을 끝없이 발급하려 들다가 진짜로 차단당했을 것이다.

그리고 이 규칙을 모든 데이터 요청이 지나가는 한 군데에 넣었다. 원래는 거래대금 목록, 업종 지수, 일봉 차트, 매매 동향… 요청하는 곳마다 신분 확인 절차가 똑같이 복사돼 있었다. 그걸 다 걷어내고 한 곳으로 모았다.

결과가 재미있었다. 이 수정에서 71줄이 추가되고 104줄이 지워졌다. 기능을 늘렸는데 코드가 33줄 줄었다.

이제 새로 데이터를 받아올 곳을 추가할 때 신분 확인을 신경 쓸 필요가 없다. 그 한 군데를 지나가게만 하면 자동으로 따라온다. 처음부터 이렇게 만들었어야 했는데, 그걸 알려면 한 번 깨져봐야 했던 것 같다.

남은 교훈

  1. “연결됐다”와 “제대로 받았다”는 다른 이야기다. 받아온 내용 안에 실패 사유가 적혀 있는지 봐야 한다.
  2. 유효기간이 남았다고 유효한 게 아니다. 다른 곳에서 같은 계정으로 발급하면 죽을 수 있다. 시간으로 관리하는 것과, 거절당했을 때 복구하는 것을 둘 다 갖춰야 한다.
  3. 화면이 가리키는 곳을 의심하지 마라. NXT가 떠 있었기 때문에 나는 NXT를 팠다. 실제 원인은 신분 확인이었다. 증권사가 보낸 답을 처음에 읽어봤으면 하루를 아꼈다.
  4. 작업하던 곳을 옮길 때는 이전 것을 끄고 옮겨라. 이 버그의 진짜 방아쇠는 코드가 아니라 내 작업 습관이었다.
  5. AI로 개발한다면 확인의 순서가 곧 비용이다. 아무 데나 찔러볼 수 없다. 가장 확실한 답을 주는 확인부터 해야 한다. 이번엔 그게 “증권사가 실제로 뭐라고 답했나”였고, 나는 그걸 마지막에 했다.

이 글은 개인 프로젝트의 개발 기록입니다. 투자 자문이나 종목 추천이 아니며, 글에 등장하는 종목명은 프로그램의 동작을 설명하기 위한 예시입니다. 투자 판단과 그 결과에 대한 책임은 투자자 본인에게 있습니다.