
AI 에이전트에게 영수증을 읽히기 — OCR × 인보이스 대조 API를 x402로 공개
요약
AI 에이전트의 업무 효율을 높이기 위해 일본의 청구서와 영수증을 OCR하고 인보이스 등록 번호의 실재 여부를 검증하는 API를 x402 결제 프로토콜로 공개했습니다. 단순 OCR을 넘어 경리 업무의 정답을 제공하는 특화된 문서 처리 서비스에 집중했습니다.
핵심 포인트
- 단순 OCR 래퍼가 아닌 특정 업무(인보이스 검증)에 특화된 서비스의 시장성 확인
- Azure AI Document Intelligence를 활용한 고정밀 데이터 추출 및 정규식 결합
- x402 프로토콜을 통한 에이전트용 종량제 결제 시스템 구현
- CDP paymentPayload 스키마 제한으로 인한 결제 오류 트러블슈팅 경험
지난번, AI 에이전트용으로 일본 법인 데이터를 종량제로 판매하는 API (Kaisha API)를 공개했던 개인 개발자입니다. 이번에는 그 제2탄으로서, 일본의 청구서·영수증을 OCR하고, 인보이스 등록 번호의 실재 여부를 대조하여 한 번의 호출(one call)로 반환하는 API를 만들었습니다. 구상부터 본 서비스 가동까지 단 하루가 걸렸습니다.
왜 「영수증 리더」인가 — 시장을 먼저 측정하다
만들기 전에, x402 에코시스템 (Coinbase 주도의 HTTP 402 결제. 현재는 Linux Foundation 산하)에 무엇이 출품되어 있고 무엇이 팔리고 있는지를 공개 데이터로 대략 실측했습니다. 알게 된 점은 세 가지입니다:
RAG 부품 (embedding·chunker)은 대량으로 존재하지만, 거의 아무도 사지 않는다. 에이전트는 스스로 할 수 있는 일에 돈을 지불하지 않습니다. OCR도 이미 40건 이상 존재하지만, 모두 범용적이고 얕은 래퍼(wrapper)일 뿐이며 눈에 띄는 매출이 없다.
- 반면,
특화된 문서 처리 (예: EU의 전자 인보이스 읽기)에는 구매자가 확실히 분산되어 있습니다.
즉, 「OCR을 파는 것」이 아니라, 「이 서류, 경리 처리를 해도 괜찮을까?」라는 업무의 해답을 파는 것이 정석입니다. 일본에는 인보이스 제도라는 아주 좋은 소재가 있습니다. 적격 청구서의 등록 번호 (T+13자리)가 실재하는지는 LLM이 원리적으로 판정할 수 없는 결정적인 검증 사항이며, 전작 Kaisha에서 국세청 공표 데이터 505만 건을 이미 D1에 보유하고 있었습니다. 이것을 연결합니다.
만든 것
POST /read/invoice
($0.04) — 청구서 PDF/이미지 → 발행자·수신인·품목·세율별 내역의 JSON + 등록 번호를 국세청 데이터와 대조 (실재/취소/미등록, 등록 명칭과의 명칭 일치, 법인 등기 정보까지)
POST /read/receipt
($0.03) — 영수증·레시트 사진 → 상점명·일시·품목·8%/10% 내역 + 동일한 대조. 매입세액 공제에 사용할 수 있는 레시트인지 한 번의 호출로 알 수 있음
GET /verify
($0.01) — 번호만 대조 (OCR 없음). 결제 전 거래처 체크용
OCR 엔진은 Azure AI Document Intelligence의 prebuilt 모델입니다. 일본어 청구서·레시트에서 테스트해 본 결과, 품목·세율·금액은 거의 완벽하게 추출됩니다. 한 가지 흥미로운 점은, 등록 번호가 VendorTaxId 필드에는 들어오지 않는다는 것입니다. 다만 생텍스트(raw text)에는 정확하게 적혀 있으므로, T\d{13} 정규 표현식으로 추출하는 것이 확실했습니다.
결제는 x402 v2 (USDC·Base/Solana 모두 대응)를 사용합니다. 해석에 실패한 경우에는 4xx를 반환하지만, x402 미들웨어는 4xx 응답 시 결제를 취소하므로 실패 시에는 과금되지 않습니다. 문서에 「Failed analyses are not charged」라고 적을 수 있는 것은 이 메커니즘 덕분입니다.
빠지기 쉬운 함정 (이것이 본론)
CDP의 paymentPayload 스키마는 「description 500자」 제한
이번에 발견한 가장 큰 지뢰입니다. 배포 후, GET 대조 엔드포인트는 결제가 통과되는데, POST 읽기 계열만 모든 결제가 402로 거부되는 현상이 발생했습니다. facilitator (Coinbase CDP)의 에러 메시지는 다음과 같습니다:
'paymentPayload' is invalid: must match one of [x402V2PaymentPayload, x402V1PaymentPayload]
이것뿐입니다. 어떤 필드가 잘못되었는지 전혀 나타나지 않습니다.
결제 클라이언트가 보내는 payment-signature 헤더를 후크(hook)로 포착하고, CDP의 verify 엔드포인트를 직접 호출하며 페이로드를 한 곳씩 변이시켜 조사한 결과——루트 설명문 (resource.description)이 500자를 초과하면 스키마 전체가 불일치하게 된다는 사실이 판명되었습니다. 실측 결과 500자=OK, 501자=거부였습니다. GET만 통과되었던 이유는 설명문이 438자였기 때문이었습니다.
에이전트에게 발견되고 싶은 마음에 설명문을 길게 작성하면, 그 루트는 단 1원도 팔 수 없게 됩니다. Bazaar를 위한 설명은 500자 이내로 작성해야 합니다.
bodyType: 'json'
이 필수
POST 루트의 discovery 선언은 @x402/extensions의 declareDiscoveryExtension을 사용하며 GET/POST 공용이지만, POST에서는 bodyType...
를 생략하면 GET 방식(queryParams) 선언으로 정형화되어 버리며, 실제 라우트와 모순된 선언이 402 챌린지(402 challenge)에 포함됩니다. 타입 정의상으로는 DeclareBodyDiscoveryExtensionConfig의 판별자(discriminator)이므로, TypeScript에서도 놓치기 쉽습니다. POST라면 bodyType: 'json'을 반드시 작성해야 합니다.
디버깅 타입: 페이로드(payload)를 포착하여 facilitator를 직접 호출하기
위의 두 가지를 특정할 수 있었던 것은 이 절차 덕분이었습니다.
wrapFetchWithPayment(capturingFetch, client)에 자체 fetch를 전달하여payment-signature헤더(Base64 인코딩된 JSON)를 포착HTTPFacilitatorClient에 CDP 키를 전달하여verify(payload, payload.accepted)를 로컬에서 직접 호출- 페이로드의 필드를 하나씩 변이시키며 이분 탐색(binary search)
서버 로그에도 결제 클라이언트의 에러에도 근본 원인이 나타나지 않는 경우에는, facilitator 사이에 직접 개입하는 것이 가장 빠릅니다.
"전작의 지뢰"는 밟지 않았다
Solana의 ATA 사전 생성, D1의 10만 바이트 상한, .well-known보다 /openapi.json 우선순위——지난 기사에 썼던 지뢰들은 템플릿화해 둔 덕분에 이번에는 공수(man-hour)가 전혀 들지 않았습니다. 두 번째 작업은 정말 빠릅니다. OCR 부분을 포함해도 시장 조사부터 E2E 결제 확인까지 단 하루입니다.
타이밍에 대하여
인보이스 제도(Invoice System)의 경과 조치(면세 사업자로부터의 매입 80% 공제)는 2026년 9월 말에 50%로 낮아집니다. 거래처가 등록 사업자인지 확인하는 작업은 이번 10월부터 금전적 임팩트가 한 단계 높아집니다. 경리 관련 에이전트 자동화를 만들고 계신 분들에게는 딱 적절한 부품이 될 것입니다.
에이전트에서 사용하는 방법
MCP 대응 클라이언트(Claude Code 등)라면 한 줄로 가능합니다:
claude mcp add denpyo -e EVM_PRIVATE_KEY=0x<USDC가 들어있는 Base 지갑> -- npx -y denpyo-mcp
Solana 파라면 SVM_PRIVATE_KEY(base58)로도 OK입니다. MCP 서버는 로컬에서 동작하므로, 가지고 있는 PDF나 영수증 사진의 경로를 그대로 전달할 수 있습니다. "이 폴더의 영수증 전부 경비 리스트로 만들어줘"가 작동합니다.
소스: https://github.com/vladic-corp/denpyo-api / 법인 데이터 측 전작: Kaisha API
질문 및 감상은 환영합니다. "에이전트에게 읽히고 싶은 일본의 서류" 아이디어도 모집하고 있습니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기