DART 재무제표를 API로 받는 방법 - 인증키부터 계정 목록까지
DART 재무제표를 API로 받아보기
일반적으로 기업의 재무제표는 DART(https://dart.fss.or.kr/)에서 기업 이름을 조회하고, 필요한 파일을 PDF로 받아서 열어볼 수 있습니다. 한두개의 기업을 조회하는 일은 어렵지 않지만, 여러 기업을 주기적으로 조회해야 할 일이 있을 때에는 꽤나 번거로운 일이 됩니다.
금융감독원 전자공시시스템은 Open API를 제공하는데, 이를 이용하면 기업의 재무정보 조회를 자동화 할 수 있습니다. Open API를 이용해서 필요한 자료를 받는 일은 자재 창고에 납품요청서를 제시하는 일에 비유할 수 있습니다. 납품요청서에 품번과 수량을 정확히 적어내면 해당 물건을 받을 수 있듯이, 기업의 고유 코드와 필요한 사항을 일정 규칙에 맞게 작성해서 청구하면 해당 기업의 재무정보를 통째로 받을 수 있습니다.
금융감독원 전자공시의 OpenDART API를 사용하여 기업의 고유번호와 사업연도, 보고서 코드를 정확하게 적어서 청구하면 해당 계정의 내용 전체를 한번에 받을 수 있습니다. 처음 API 인증키를 받아서 필요한 데이터를 얻는데까지 순서대로 정리해 보겠습니다.
적용 사례는 전부 공개 공시인 농심 사업보고서(2025.12, 접수번호 20260311003892)입니다.

사전 준비 : 인증키 발급과 보관
[ Dart 페이지에서 OpenDART로 접근 ]
DART(https://dart.fss.or.kr/) 페이지에서 아래와 같은 접근으로 OpenDART 이동이 가능합니다.

[ OpenDART 페이지 (https://opendart.fss.or.kr/) ]

OpenDART 사이트의 [인증키신청/관리 > 인증키 신청] 메뉴에서 신청합니다. 개인회원은 별도 증빙서류 없이 계정신청과 동시에 즉시 발급됩니다. 사업자등록번호가 있는 법인이 쓰는 기업회원은 담당자 승인을 거쳐 1~2 영업일이 걸립니다. 개인이라면 이 글을 읽는 중에 받아 그대로 따라 할 수 있습니다. 키는 40자리 문자열입니다.
받은 인증키는 보관에 유의해야 합니다. 인증키는 요청 주소에 그대로 들어가기 때문에 화면 공유, 블로그, 스크린샷, 코드 저장소로 새어 나가기 가장 쉬운 종류의 비밀번호입니다. 키는 계정과 같은 것이니 코드나 프롬프트, 공유 문서에 그대로 남기지 않는 편이 안전합니다. 이 글의 예시도 실제 키 대신 <발급키>로 적었습니다.
단계별 실행 : 고유번호에서 218개 계정까지
1단계 — 회사 고유번호 찾기
여기서 한 번 걸리는 사람이 많습니다. 재무제표 API는 우리가 흔히 아는 종목코드(농심은 004370)가 아니라 DART 고유번호 8자리를 요구하고, 둘은 다른 체계이기 때문입니다. 고유번호는 전용 API로 받습니다.
GET https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key=<발급키> # 고유번호 전체 목록
이 API의 특이한 점은 요청 인자가 인증키 하나뿐이라는 것입니다. 회사명으로 검색하는 파라미터가 아예 없어서 전체 목록을 통째로 받은 후에 직접 찾아야 합니다. 받아 보면 응답은 약 3.6MB의 ZIP이고, 풀면 CORPCODE.xml 한 개가 약 30MB 나옵니다. 그 안에 119,313개 회사가 들어 있습니다. 레코드 하나는 고유번호(corp_code), 정식명칭(corp_name), 영문 정식명칭(corp_eng_name), 상장회사면 종목코드(stock_code), 최종변경일자(modify_date) 다섯 개로 되어 있습니다.
그러면 30MB짜리 파일에서 어떻게 찾을까요. 메모장으로 열면 버거우니 브라우저에서 Ctrl+F로 찾거나, 엑셀로 불러오거나, 편집기 검색을 쓰는 편이 낫습니다. 다만 ChatGPT에 통째로 올리는 것은 권하지 않습니다. 무료 계정이라면 업로드 한도를 이 파일 하나로 써 버리게 되고, 고유번호 하나를 찾자고 치를 값은 아닙니다. 이렇게 농심을 찾으면 레코드가 이렇게 나옵니다.
<list>
<corp_code>00108241</corp_code>
<corp_name>농심</corp_name>
<corp_eng_name>NONGSHIM CO.,LTD</corp_eng_name>
<stock_code>004370</stock_code>
<modify_date>20260323</modify_date>
</list>
찾아낸 00108241이 앞으로 쓸 고유번호입니다. 이 단계에서 더 조심할 것이 두 가지 있습니다. 첫째, 119,313개 가운데 종목코드가 있는 회사는 3,990개뿐입니다. 나머지는 전부 비상장회사라, 회사명으로만 검색하면 동명 법인이나 계열사가 섞여 나옵니다. 종목코드를 함께 확인해 원하는 법인인지 확정해야 합니다. 둘째, 크롬이나 엣지는 이 응답을 ZIP이 아니라 XML 파일로 저장하므로 받은 파일의 확장자를 zip으로 바꿔야 정상적으로 열립니다. 개발가이드에도 같은 주의가 적혀 있습니다.
2단계 — 재무제표 호출하기
고유번호를 얻었으니 이제 재무제표를 청구합니다. 요청에는 네 가지를 넣습니다.
corp_code: 앞에서 찾은 회사 고유번호 여덟 자리입니다.bsns_year: 사업연도 네 자리이고, 2015년 이후부터 제공됩니다.reprt_code: 어느 보고서인지를 가리키는 코드입니다.11011: 사업보고서입니다.11012: 반기보고서입니다.11013: 1분기보고서입니다.11014: 3분기보고서입니다.
fs_div: 연결로 볼지 별도로 볼지를 정합니다. 연결재무제표가 CFS, 재무제표(별도)가 OFS입니다.
어느 보고서를 받을지는 접수번호가 아니라 사업연도와 보고서 코드로 지정한다는 점이 처음에는 낯설 수 있습니다. 농심의 2025년 사업보고서를 연결 기준으로 청구하면 이렇게 됩니다.
GET https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json
?crtfc_key=<발급키> # 발급받은 40자리 키
&corp_code=00108241 # 농심
&bsns_year=2025 # 사업연도
&reprt_code=11011 # 사업보고서
&fs_div=CFS # 연결 기준
응답은 status 000, 즉 정상으로 돌아옵니다. 돌려받은 계정 수는 연결(CFS)이 218개, 별도(OFS)가 164개이고, 응답 크기는 각각 106,725바이트와 75,910바이트입니다. 한글은 유니코드 이스케이프 없이 그대로 오고, 같은 요청을 연결과 별도로 두 번 보내면 한 회계연도의 두 기준을 모두 확보하게 됩니다. 필드와 코드값의 정의는 OpenDART 개발가이드에 정리돼 있습니다.
호출 한도도 알아 두는 편이 좋습니다. 개인 인증키는 특정 서비스가 아니라 오픈API 전체 서비스를 합쳐 하루 20,000건이고, 일일 한도를 지키더라도 분당 1,000회 이상의 과도한 접속은 이용이 제한될 수 있습니다. 한도를 넘기면 문서상 오류코드 020이 돌아옵니다. 재무제표 한두 건을 받는 작업은 호출이 두어 번이면 끝나므로 한도를 걱정할 일은 거의 없습니다.
3단계 — 무엇이 왔는지 보기
응답의 list에는 계정 하나가 레코드 하나로 들어 있습니다. 주요 필드는 이렇습니다.
| 필드 | 뜻 | 실제로 확인한 것 |
rcept_no |
접수번호 14자리 | 원 공시로 되돌아가는 열쇠입니다. |
sj_div·sj_nm |
재무제표 구분과 이름 | BS·IS·CIS·CF·SCE로 나뉩니다. |
account_id |
계정ID | 표준계정ID가 아니면 "-표준계정코드 미사용-"이 옵니다. |
account_nm |
계정명 | 사람이 읽는 이름입니다. |
account_detail |
계정 상세 | 자본변동표 외에는 빈 문자열이 아니라 "-"로 옵니다. |
thstrm_amount |
당기 금액 | 분기·반기 보고서의 (포괄)손익계산서에서는 3개월 금액입니다. |
thstrm_add_amount |
당기 누적 금액 | 사업보고서 응답에서는 51건에만 있고, 그 51건이 전부 빈 문자열입니다. |
frmtrm_amount·bfefrmtrm_amount |
전기·전전기 금액 | 사업보고서라 둘 다 정상 출력됩니다. |
ord·currency |
정렬 순서와 통화 | ord는 번호가 연속되지 않고, currency는 전부 KRW입니다. |
연결과 별도 382건을 전부 열어 보면 가이드에 없는 필드는 하나도 없습니다. 다만 가이드만 보고 짐작한 것과 다른 점이 세 가지 있었습니다.
첫째, 분기 관련 세 필드는 값이 비어서 오는 것이 아니라 키 자체가 없습니다. frmtrm_q_nm과 frmtrm_q_amount, frmtrm_add_amount는 사업보고서 응답에 아예 나오지 않으므로, 코드에서 r["frmtrm_add_amount"]처럼 바로 접근하면 오류가 납니다. .get()으로 받아야 합니다.
둘째, 필드가 있다고 값이 있는 것도 아닙니다. thstrm_add_amount는 51건에 존재하지만 51건 모두 빈 문자열입니다.
셋째, 해당 없음의 표시가 통일돼 있지 않습니다. account_detail은 빈 문자열이 아니라 "-"로 옵니다.
확인 : 값이 어떻게 생겼는지 본다
실제 데이터를 보겠습니다. 응답에서 매출액 레코드를 그대로 잘라 왔습니다.
{"rcept_no":"20260311003892","reprt_code":"11011","bsns_year":"2025","corp_code":"00108241",
"sj_div":"IS","sj_nm":"손익계산서","account_id":"ifrs-full_Revenue","account_nm":"매출액",
"account_detail":"-","thstrm_nm":"제 62 기","thstrm_amount":"3514315500097",
"thstrm_add_amount":"","frmtrm_nm":"제 61 기","frmtrm_amount":"3438730857998",
"bfefrmtrm_nm":"제 60 기","bfefrmtrm_amount":"3410559367259","ord":"25","currency":"KRW"}
여기서 눈여겨볼 것이 하나 있습니다. 한 레코드 안에 당기(제62기)와 전기(제61기), 전전기(제60기)가 모두 들어 있습니다. 3개년을 비교하겠다고 연도를 바꿔 가며 세 번 호출할 필요가 없다는 뜻이고, 사업보고서 한 건이면 3개년이 한 번에 확보됩니다.
값의 생김새도 이 한 건에 다 드러납니다. 금액은 전부 문자열로 오고 콤마는 붙지 않으며, 빈 값은 빈 문자열입니다. 가이드 예시에 보이는 9,999,999,999는 자릿수를 보여 주는 표기일 뿐 실제 형식이 아닙니다. 문자열이라는 점은 그 자체로 함정이어서, 엑셀에 그대로 붙이면 텍스트로 들어가고 계산이 되지 않습니다. 통화는 currency에 담기는데 382건이 전부 KRW입니다.
음수는 괄호가 아니라 -를 앞에 붙인 문자열로 옵니다. 현금흐름표의 운전자본의 조정이 그 예로, 당기 금액이 -87775716836이고 전기도 -51570626430으로 음수인데 전전기만 692805873으로 부호가 뒤집혀 양수입니다. 3개년이 한 줄에 들어 있다는 편리함의 이면이라, 한 줄을 훑으며 읽을 때 부호 전환을 놓치기 쉬운 자리입니다. 이 레코드에는 thstrm_add_amount 키 자체가 없다는 점도 매출액 레코드와 다릅니다. 레코드마다 필드 구성이 같지 않다는 것을 두 건만 나란히 놓아도 알 수 있습니다.
계정 목록 확인 : 손익계산서 19건은 이렇게 온다
218개가 한 덩어리로 오는 것은 아닙니다. sj_div로 나뉘어 있고, 연결 기준으로 자본변동표(SCE)가 98건, 재무상태표(BS)가 52건, 현금흐름표(CF)가 40건, 손익계산서(IS)가 19건, 포괄손익계산서(CIS)가 9건입니다. 가장 작은 손익계산서 19건은 가감 없이 전부 실어도 한눈에 들어옵니다.
ord |
account_id |
account_nm |
| 6 | dart_OperatingIncomeLoss |
영업이익(손실) |
| 7 | dart_OtherGains |
기타수익 |
| 8 | dart_OtherLosses |
기타비용 |
| 9 | dart_TotalSellingGeneralAdministrativeExpenses |
판매비와관리비 |
| 10 | -표준계정코드 미사용- |
총매출액 |
| 11 | -표준계정코드 미사용- |
매출에누리등 |
| 12 | ifrs-full_CostOfSales |
매출원가 |
| 14 | ifrs-full_BasicEarningsLossPerShare |
기본주당이익(손실) |
| 15 | ifrs-full_DilutedEarningsLossPerShare |
희석주당이익(손실) |
| 16 | ifrs-full_FinanceCosts |
금융비용 |
| 17 | ifrs-full_FinanceIncome |
금융수익 |
| 18 | ifrs-full_GrossProfit |
매출총이익 |
| 19 | ifrs-full_IncomeTaxExpenseContinuingOperations |
법인세비용 |
| 20 | ifrs-full_ProfitLoss |
당기순이익 |
| 22 | ifrs-full_ProfitLossAttributableToNoncontrollingInterests |
비지배지분 |
| 23 | ifrs-full_ProfitLossAttributableToOwnersOfParent |
지배기업 소유주지분 |
| 24 | ifrs-full_ProfitLossBeforeTax |
법인세비용차감전순이익 |
| 25 | ifrs-full_Revenue |
매출액 |
| 26 | ifrs-full_ShareOfProfitLossOfAssociatesAccountedForUsingEquityMethod |
지분법손익 |
이 목록을 처음 받아 보면 세 가지가 바로 눈에 띕니다. 첫째, 순서가 재무제표를 읽는 순서가 아닙니다. ord=6이 영업이익이고 매출액은 ord=25입니다. 계정ID 알파벳순에 가깝게 늘어서 있어서, 위에서부터 차례로 읽으면 매출에서 영업이익으로 내려가는 손익 구조가 보이지 않습니다. 번호도 연속되지 않아 19건이 6번부터 26번 사이에 흩어져 있습니다.
둘째, -표준계정코드 미사용-이 2건 있습니다. 총매출액과 매출에누리등인데, 19건 중 2건은 계정ID가 없다는 뜻입니다. 계정ID를 기준으로만 작업하는 코드를 짜면 이 둘은 영영 잡히지 않습니다. 셋째, 주당이익이 금액 계정과 같은 목록에 섞여 있습니다. ord=14의 기본주당이익은 29,342원인데 위아래 계정은 조 단위입니다. 목록만 보고 전부 더하는 식으로 다루면 조 단위 숫자 사이에 2만 원짜리가 끼어듭니다.
나머지 재무제표도 성격이 비슷합니다. 재무상태표는 자산총계부터 시작해 유동자산, 배출권, 기타채권, 매출채권처럼 52건이 이어지고, 현금흐름표 40건에도 -표준계정코드 미사용-으로 오는 재무활동 현금유입액과 유출액이 들어 있습니다. 가장 큰 것은 자본변동표로, 218건 가운데 98건, 전체의 45%를 차지합니다. 자본변동표는 한 계정을 자본 구성요소별로 쪼개 반복하기 때문에 건수가 이렇게 불어납니다. account_detail이 채워지는 곳도 자본변동표뿐입니다.
정리 : 데이터는 도착했다
여기까지가 청구의 전부입니다. 인증키를 받고, 30MB짜리 목록에서 고유번호 여덟 자리를 찾아내고, 사업연도와 보고서 코드와 연결범위를 지정해 한 번 호출하면 계정 218개가 옵니다. 한 계정에 3개년이 함께 실려 오므로 숫자로 세면 600개가 넘습니다. 별도 기준까지 받으면 382개 계정이 됩니다.
창고 비유로 돌아가면, 납품요청서를 정확히 적는 일은 규칙만 지키면 되는 일이고 실제로 어렵지 않습니다. 고유번호가 종목코드와 다르다는 것, 보고서를 접수번호가 아니라 연도와 코드로 지정한다는 것, 키를 주소에 실어 보낸다는 것만 알면 첫 호출은 성공합니다. 어려움은 그다음에 있습니다. 데이터가 왔다는 사실과 데이터 덩어리 안에 무엇이 들었는지 아는 것은 다른 일이고, 후자는 목록을 직접 펼쳐 봐야만 알 수 있습니다. 218개가 왔다는 것을 아는 것이 그 시작입니다.