업무 자동화
홈택스 안 들어가고 세금계산서 발행하기: 볼타 + Claude 스킬 설치 가이드
지금 다 못 읽겠다면 · 이 가이드 링크를 메일로 받기
목차 · 12개 섹션
세금계산서는 잘못 발행하면 되돌릴 수 없다. 국세청 수정발행 절차를 밟아야 하고 기록이 남는다.
그런데 홈택스에서 한 장 끊는 데 걸리는 시간이 길다. 인증서를 찾고, 팝업을 세 번 닫고, 그 사이 로그인이 풀린다. 월말에 거래처가 열 곳이면 이걸 열 번 반복한다.
이 글은 그 과정을 Claude에게 넘기는 방법이다. 설치부터 첫 발행까지 따라 하면 된다.
무엇을 만드나
"고객사 주식회사에 9월 이용료 50만원 세금계산서 발행해줘"
이렇게 말하면 Claude가 검증하고, 내용을 보여주고, 확인을 받은 뒤 발행한다. 국세청 전송까지 간다.
중간에 승인 단계가 있다는 게 핵심이다. 확인 없이 바로 나가는 구조였다면 쓰지 않았을 것이다.
준비물
| 항목 | 설명 |
|---|---|
| 볼타(Bolta) 계정 | 전자세금계산서 ASP. 가입 후 API 키 발급 |
| Claude Code 또는 Hermes | 스킬을 읽고 스크립트를 실행할 도구 |
| Node.js 18+ | 라이브러리 빌드용 |
볼타는 국세청에 전송해주는 중개 사업자다. 홈택스를 직접 치는 게 아니라 이쪽 API를 쓴다.
1. 설치
MIT 라이선스, 공개 레포다.
# Claude Code
git clone https://github.com/sdkfile/korean-tax-invoice \
~/.claude/skills/korean-tax-invoice
# Hermes
git clone https://github.com/sdkfile/korean-tax-invoice \
~/.hermes/skills/korean-tax-invoice
2. 빌드 — 건너뛰면 동작하지 않는다
cd ~/.claude/skills/korean-tax-invoice
npm install && npm run build
스크립트가 dist/를 참조하기 때문에 이 단계가 필수다. 잊으면 발행과 상태 조회가 실패한다.
3. 키 설정 — test_로 시작하라
export BOLTA_API_KEY=test_xxxxx # 볼타 개발자센터 > API 키
키 접두사가 환경을 가른다.
| 접두사 | 동작 |
|---|---|
test_ | 가상 발행. 국세청에 가지 않는다 |
live_ | 실제 발행. 되돌릴 수 없다 |
처음에는 무조건 test_로 시작한다. .env 파일에 넣어도 되고, 리포의 .gitignore가 .env를 제외한다.
4. 점검
node scripts/preflight.mjs
“발행 준비 완료”가 나오면 끝이다. 이제 Claude에게 이렇게 말하면 된다.
볼타로 세금계산서 발행하려는데 준비됐는지 봐줘
5. 첫 발행
사용자: 고객사 주식회사에 9월 이용료 50만원 세금계산서 발행해줘
Claude: (preflight → 요청 파일 작성 → 검증 실행)
공급받는자 고객사 주식회사 (220-81-62517)
받는사람 tax@client.co.kr
공급가액 500,000원
세액 50,000원
합계 550,000원
환경 테스트 (가상)
이 내용으로 발행할까요?
사용자: 응
Claude: (--confirm 으로 발행 → issuanceKey 확인 → 상태 조회)
터미널에서 직접 하면 이렇다.
node scripts/issue.mjs examples/request.json # 검증만 (발행 안 함)
node scripts/issue.mjs examples/request.json --confirm # 실제 발행
node scripts/check-status.mjs <issuanceKey> # 결과 확인
--confirm이 없으면 아무것도 발행되지 않는다. 실수로 실행됐을 때 아무 일도 일어나지 않아야 하기 때문이다.
실제로 터지는 세 가지
이 라이브러리를 만든 이유다. 일반 API 호출처럼 다루면 아래에서 사고가 난다.
타임아웃 후 재시도 → 중복 발행
ASP가 응답을 안 주면 보통 재시도한다. 그런데 요청은 이미 접수됐을 수 있다. 같은 계산서가 두 장 나간다.
그래서 “거절됨”과 “모름”을 구분한다. 후자는 재요청하지 않고 조회부터 한다.
try {
const { issuanceKey } = await provider.submit(request, refId)
} catch (e) {
if (e instanceof ProviderUncertainError) {
// 접수 여부 불명. 재요청하면 중복 발행이다.
const status = await provider.getStatusByReferenceId(refId)
// SUBMITTED → 이미 접수됨 / UNKNOWN → 접수 안 됨
}
}
작성일자 하루 밀림 → 부가세 신고 어긋남
서버가 UTC면 KST 자정 근처에서 전날이 찍힌다. 작성일자는 과세기간을 가르는 값이라, 월말 발행분이 전월로 넘어가면 신고가 틀어진다.
필수값 누락을 빈 문자열로
대표자명이 없다고 ""를 보내면 ASP가 받아주는 경우가 있다. 그대로 국세청에 간다.
validateIssueRequest()가 발행 전에 막는다. 사유는 한국어 배열로 오니 승인 화면에 그대로 띄울 수 있다.
라이브로 넘어갈 때
⚠️ 라이브는 테스트와 완전히 별도 환경이다. 키만 바꾸면 안 된다. 세 가지를 다시 해야 한다.
- 발급자 등록
- 공동인증서 등록
- 웹훅 URL 등록
references/bolta-setup.md에 절차가 있다.
첫 실전 발행은 자기 회사끼리 소액으로 해보길 권한다. 잘못돼도 남에게 가지 않는다.
코드로 붙일 때
SaaS 월 청구나 정산 자동화에 넣는 경우다. 핵심은 프로바이더를 부르기 전에 잠그는 것이다.
const check = validateIssueRequest(request, { verified: company.verifiedAt !== null })
if (!check.ok) {
return { blocked: check.issues.map((i) => i.message) }
}
const provider = new BoltaProvider()
const refId = buildClientReferenceId('myapp', invoice.id)
await db.markSubmitted(invoice.id) // 프로바이더 호출 *전에* 잠근다
const { issuanceKey } = await provider.submit(request, refId)
await db.saveIssuanceKey(invoice.id, issuanceKey)
접수(SUBMITTED)와 발행(ISSUED)은 다른 상태다. 섞으면 아직 안 끝난 건을 완료로 처리하게 된다.
설계 패턴은 references/integration.md에 정리해 뒀다.
다른 프로바이더
| 프로바이더 | 상태 |
|---|---|
| 볼타 (Bolta) | 구현됨. 실제 발행 검증 완료 |
| 팝빌 | 인터페이스만 |
| 바로빌 | 인터페이스만 |
팝빌·바로빌은 인터페이스만 있고 구현이 없다. 있는 척하지 않으려고 표에 그대로 적었다.
새로 붙이려면 src/core/types.ts의 TaxInvoiceProvider를 구현하면 된다. 검증·사업자번호·KST 날짜 같은 핵심 로직은 프로바이더와 무관하게 동작한다.
세무 판단은 대신해주지 않는다
이 도구는 발행 API 연동을 돕는다. 과세 유형, 작성일자, 수정발행 사유 같은 세무 판단은 사용자 책임이다. 확실하지 않으면 세무 전문가에게 확인하라.
자동화가 대체하는 건 클릭과 대기 시간이지 판단이 아니다.
FOR TEAMS
이 가이드, 우리 팀에 적용하려면?
회사명과 업무 한 줄만 남겨주시면 널포인터스튜디오가 직접 검토해 회신드립니다.