Verify Korean business registration numbers (사업자등록번호) via the official NTS (국세청) public API. Operating-status lookup (계속/휴업/폐업), full authenticity check (b_n...
---
name: nts-bizno-cli
description: Verify Korean business registration numbers (사업자등록번호) via the official NTS (국세청) public API. Operating-status lookup (계속/휴업/폐업), full authenticity check (b_no + 개업일 + 대표자명), local checksum validation without API calls, and bulk file processing. Use when onboarding Korean businesses, validating B2B partners, building KYB pipelines, deduplicating supplier lists, or pre-filling 세금계산서/견적서/계약서 forms with NTS-confirmed data. Pairs with unified-invoice (form fill), opendart-cli (corporate filings), and juso-address-cli (address resolution) to complete the Korean B2B onboarding toolchain.
version: 0.1.0
license: MIT
---
# nts-bizno-cli
Command-line wrapper for the **NTS 사업자등록정보 진위확인 및 상태조회 서비스** (Korean National Tax Service business-registration API), exposed via `data.go.kr` / `api.odcloud.kr`.
Two official endpoints + two local conveniences:
| Command | Endpoint | API key? | Purpose |
|---|---|---|---|
| `scripts/status.sh` | `POST /v1/status` | yes | Look up operating status (계속사업자/휴업자/폐업자) for up to 100 b_no per call. |
| `scripts/validate.sh` | `POST /v1/validate` | yes | Authenticity check — does (b_no + 개업일 + 대표자명) match NTS records? |
| `scripts/format.sh` | local | no | Verify checksum + format `XXX-XX-XXXXX`. No network. |
| `scripts/bulk.sh` | `POST /v1/status` | yes | Read a file of b_no's, checksum-filter, batch-call status in groups of 100. |
All output is JSONL (one record per line) so it pipes straight into `jq`, `csvkit`, or downstream skills.
## When to use this skill
- **B2B onboarding / KYB** — verify a partner's business number is real and currently active before contract signing or payment release.
- **Supplier-list cleanup** — bulk-check thousands of b_no's, flag the closed (폐업자) and dormant (휴업자).
- **Form-fill validation** — confirm a user's typed b_no is structurally valid *before* hitting the API (saves quota and cost).
- **Tax-invoice (세금계산서) issuance gate** — Korean law requires verifying the counterparty's tax type (일반/간이/면세) before issuing; this returns it.
- **Public-procurement (나라장터) prep** — validate vendor records before bid submission.
## Do **not** use this skill for
- Address lookup → use `juso-address-cli`.
- Corporate-filings / disclosure data → use `opendart-cli`.
- Issuing the actual tax invoice → use `unified-invoice`.
- Looking up a business by *name* — NTS does not expose name-based search; you need the b_no first.
## Prerequisites
1. **Get a serviceKey** at <https://www.data.go.kr> (free, 1 business-day approval):
- Search for "**국세청 사업자등록정보 진위확인 및 상태조회 서비스**".
- Click **활용신청** (one form per API). Both `상태조회` and `진위확인` are commonly approved on the same day. Free-tier quota is **10,000 requests/day** for each.
2. Export the encoded key:
```bash
export NTS_API_KEY='Ad9...%2BAbc%3D' # paste the "일반 인증키 (Encoding)" value
```
Use the **Encoding** key (URL-encoded) — the wrapper passes it through `serviceKey=` directly.
3. Dependencies: `bash`, `curl`, `jq` (default on macOS/Linux).
## Commands
### 1) Status lookup (`scripts/status.sh`)
```bash
# Single
scripts/status.sh 124-81-00998
# Multiple (up to 100 per call)
scripts/status.sh 1248100998 220-81-62517 120-81-47521
```
Sample row:
```json
{"b_no":"1248100998","b_no_formatted":"124-81-00998","b_stt_cd":"01","b_stt":"계속사업자","tax_type_cd":"01","tax_type":"부가가치세 일반과세자","end_dt":"","utcc_yn":"N","tax_type_change_dt":"","invoice_apply_dt":"","rbf_tax_type_cd":"","rbf_tax_type":""}
```
Status codes:
- `b_stt_cd=01` → 계속사업자 (active)
- `b_stt_cd=02` → 휴업자 (dormant)
- `b_stt_cd=03` → 폐업자 (closed) — `end_dt` carries 폐업일.
- `b_stt_cd=""` → b_no not registered with NTS at all.
Tax-type codes:
- `01` 부가가치세 일반과세자, `02` 부가가치세 간이과세자, `03` 부가가치세 면세사업자, `04` 비영리법인, `05`/`06` 외국 / 임시.
### 2) Authenticity check (`scripts/validate.sh`)
Single record — flags:
```bash
scripts/validate.sh \
--b-no 124-81-00998 \
--start-dt 19690113 \
--p-nm "한종희"
```
Batch — JSON file:
```bash
cat > /tmp/payload.json <<'EOF'
{"businesses":[
{"b_no":"1248100998","start_dt":"19690113","p_nm":"한종희","p_nm2":"","b_nm":"","corp_no":"","b_sector":"","b_type":""},
{"b_no":"2208162517","start_dt":"19990602","p_nm":"최수연","p_nm2":"","b_nm":"","corp_no":"","b_sector":"","b_type":""}
]}
EOF
scripts/validate.sh --file /tmp/payload.json
```
Sample row:
```json
{"b_no":"1248100998","b_no_formatted":"124-81-00998","valid":true,"valid_code":"01","valid_msg":"확인","status":{...}}
```
`valid: true` ⇔ NTS confirms a match. `valid: false` (`valid_code:"02"`) means at least one of (b_no, 개업일, 대표자명) does not match — `valid_msg` carries the reason.
### 3) Local checksum (`scripts/format.sh`)
No network, no key. Cheap pre-filter before hitting the API.
```bash
scripts/format.sh 1248100998 abc-def-1234 220-81-62517
# {"input":"1248100998","normalized":"1248100998","formatted":"124-81-00998","valid_checksum":true}
# {"input":"abc-def-1234","normalized":"abcdef1234","formatted":"abcdef1234","valid_checksum":false}
# {"input":"220-81-62517","normalized":"2208162517","formatted":"220-81-62517","valid_checksum":true}
```
Algorithm (NTS official): weights `[1,3,7,1,3,7,1,3,5]` over the first 9 digits, plus `floor(d8*5/10)`, mod 10, complement to 10. Saves you a network round trip on typo'd inputs.
### 4) Bulk processor (`scripts/bulk.sh`)
```bash
# File input — one b_no per line, comments with #
cat > /tmp/suppliers.txt <<'EOF'
124-81-00998 # Samsung Electronics
220-81-62517 # NAVER
120-81-47521 # Kakao
123-45-67890 # bogus typo
EOF
scripts/bulk.sh /tmp/suppliers.txt > /tmp/audit.jsonl
# stdin
psql -At -c 'SELECT bno FROM suppliers' | scripts/bulk.sh - > /tmp/audit.jsonl
```
`bulk.sh` runs the local checksum first; bad entries get flagged with `{"error":"checksum_failed"}` and never burn API quota. Good entries are batched in groups of 100 (NTS hard limit).
## Common pipelines
```bash
# Find all suppliers that closed
scripts/bulk.sh suppliers.txt | jq -c 'select(.b_stt_cd=="03") | {b_no, end_dt}'
# Tax-type breakdown
scripts/bulk.sh suppliers.txt | jq -r '.tax_type' | sort | uniq -c | sort -rn
# Onboarding gate — only proceed if 계속사업자 + 일반과세
scripts/status.sh "$BNO" \
| jq -e 'select(.b_stt_cd=="01" and .tax_type_cd=="01")' >/dev/null \
&& echo "ok to issue 세금계산서" \
|| { echo "blocked"; exit 1; }
```
## Errors & quirks
- `code:-4 "등록되지 않은 인증키 입니다."` — your `NTS_API_KEY` is wrong or hasn't been approved yet for this specific endpoint. Check `data.go.kr → 마이페이지 → 활용신청 현황`.
- `code:-22 "사용한도..."` — you've exceeded your free-tier daily quota. Apply for 활용 한도 증가 in 마이페이지.
- The `validate` endpoint counts each item in `businesses[]` separately against quota; `status` counts only one request regardless of `b_no[]` length. Prefer `status` for cheap status sweeps.
- NTS returns historical `end_dt` even when `b_stt_cd != "03"` if the business was once closed and re-opened — read both fields.
- `start_dt` on `validate` must be **YYYYMMDD** (no dashes); pre-1900 / future dates are rejected.
## Project layout
```
nts-bizno-cli/
├── SKILL.md # this file
├── README.md # short user-facing intro (mirrors SKILL.md)
├── LICENSE # MIT
├── scripts/
│ ├── _common.sh # shared helpers (auth, POST, checksum, format)
│ ├── status.sh # 상태조회
│ ├── validate.sh # 진위확인
│ ├── format.sh # local checksum + formatter
│ └── bulk.sh # file-driven status sweep
└── examples/
└── supplier-audit.md
```
don't have the plugin yet? install it then click "run inline in claude" again.
verify korean business registration numbers against the official NTS (국세청) API. use when onboarding korean businesses, validating B2B partners, building KYB pipelines, deduplicating supplier lists, or pre-filling tax invoices (세금계산서), quotes (견적서), and contracts with NTS-confirmed data. pairs with unified-invoice (form fill), opendart-cli (corporate filings), and juso-address-cli (address resolution) to complete the korean B2B onboarding toolchain.
this skill verifies the authenticity and current operating status of korean business registration numbers (사업자등록번호) by querying the official NTS 사업자등록정보 진위확인 및 상태조회 서비스 via the data.go.kr / api.odcloud.kr public API. use it before signing contracts with new korean vendors, when bulk-auditing supplier lists for closures or dormancy, when validating user input before expensive API calls, or when you need to confirm a counterparty's tax classification (일반/간이/면세) before issuing a tax invoice. the skill also provides local checksum validation (no network, no quota) and batch processing for files of business numbers.
external connection: NTS data.go.kr API
NTS_API_KEY environment variable. example: export NTS_API_KEY='Ad9...%2BAbc%3D'validate batch call consumes one request; status consumes one request regardless of input count (up to 100 b_no's). no overage auto-charge; requests beyond quota are rejected.local dependencies
command-line inputs
XXX-XX-XXXXX or unformatted XXXXXXXXXX.validate endpoint. must be 1900-01-01 or later and not a future date.validate endpoint.#, optional tabs or spaces around values.export the NTS API key to your shell session or .env file. verify the key is set and curl is available.
input: NTS_API_KEY environment variable (URL-encoded string from data.go.kr).
output: bash session with NTS_API_KEY in scope; confirm with echo $NTS_API_KEY | head -c 20.
error handling: if NTS_API_KEY is empty or malformed, skip to step 2 (local checksum only).
run scripts/format.sh on one or more input b_no's to check the NTS-mandated checksum and normalize format. this filters out typos before hitting the API.
input: one or more b_no strings (formatted or unformatted). command:
scripts/format.sh 1248100998 220-81-62517 123-45-67890
output: JSONL, one line per input:
{"input":"1248100998","normalized":"1248100998","formatted":"124-81-00998","valid_checksum":true}
{"input":"220-81-62517","normalized":"2208162517","formatted":"220-81-62517","valid_checksum":true}
{"input":"123-45-67890","normalized":"1234567890","formatted":"123-45-67890","valid_checksum":false}
interpretation: valid_checksum:true means the NTS weighted-checksum algorithm passes (weights [1,3,7,1,3,7,1,3,5] over digits 0-8, plus floor(d8*5/10) mod 10). proceed to step 3 or 4. valid_checksum:false means the number is structurally invalid and will be rejected by NTS; flag and skip.
run scripts/status.sh on one or more b_no's to retrieve current status (active/dormant/closed), tax classification, and re-registration flags. one API call per command, regardless of input count (up to 100 b_no's per call).
input: one or more formatted or unformatted b_no's. command (single):
scripts/status.sh 124-81-00998
command (batch, up to 100):
scripts/status.sh 1248100998 220-81-62517 120-81-47521
output: JSONL, one line per b_no:
{"b_no":"1248100998","b_no_formatted":"124-81-00998","b_stt_cd":"01","b_stt":"계속사업자","tax_type_cd":"01","tax_type":"부가가치세 일반과세자","end_dt":"","utcc_yn":"N","tax_type_change_dt":"","invoice_apply_dt":"","rbf_tax_type_cd":"","rbf_tax_type":""}
interpretation:
b_stt_cd="01" (계속사업자): active, safe to contract.b_stt_cd="02" (휴업자): dormant, confirm intent before payment.b_stt_cd="03" (폐업자): closed. end_dt carries closure date (YYYYMMDD). do not proceed.b_stt_cd="": not registered with NTS.tax_type_cd="01": 부가가치세 일반과세자 (standard VAT). safe to issue tax invoices.tax_type_cd="02": 간이과세자 (simplified VAT).tax_type_cd="03": 면세사업자 (tax-exempt).tax_type_cd="04": 비영리법인 (non-profit).error handling: if API returns code:-4 ("등록되지 않은 인증키"), NTS_API_KEY is wrong, expired, or not yet approved for this endpoint. check data.go.kr 마이페이지. if code:-22 ("사용한도"), daily quota is exceeded. apply for 한도 증가.
use scripts/validate.sh when you need to confirm a user-supplied triple matches NTS records exactly. each item in the batch burns one API request. prefer step 3a for status sweeps.
input: either command-line flags (single) or JSON file (batch).
command (single, with flags):
scripts/validate.sh \
--b-no 124-81-00998 \
--start-dt 19690113 \
--p-nm "한종희"
command (batch, JSON file):
cat > /tmp/payload.json <<'EOF'
{"businesses":[
{"b_no":"1248100998","start_dt":"19690113","p_nm":"한종희","p_nm2":"","b_nm":"","corp_no":"","b_sector":"","b_type":""},
{"b_no":"2208162517","start_dt":"19990602","p_nm":"최수연","p_nm2":"","b_nm":"","corp_no":"","b_sector":"","b_type":""}
]}
EOF
scripts/validate.sh --file /tmp/payload.json
output: JSONL, one line per business:
{"b_no":"1248100998","b_no_formatted":"124-81-00998","valid":true,"valid_code":"01","valid_msg":"확인","status":{...}}
interpretation:
valid:true, valid_code:"01": NTS confirms the triple (b_no + opening date + representative name) matches records.valid:false, valid_code:"02": at least one field does not match. valid_msg carries the reason. do not proceed.error handling: start_dt must be YYYYMMDD (no dashes). dates before 1900-01-01 or after today are rejected. each item in the businesses[] array consumes one request against your daily quota.
use scripts/bulk.sh to read a file of b_no's, filter out checksum failures locally (no quota burn), batch the valid ones into groups of 100, and call the status endpoint.
input: file path or - (stdin). file format: plain text, one b_no per line, optional tabs/spaces, comments with #.
command (file input):
cat > /tmp/suppliers.txt <<'EOF'
124-81-00998 # Samsung Electronics
220-81-62517 # NAVER
120-81-47521 # Kakao
123-45-67890 # bogus, fails checksum
EOF
scripts/bulk.sh /tmp/suppliers.txt > /tmp/audit.jsonl
command (stdin, e.g., from database):
psql -At -c 'SELECT bno FROM suppliers LIMIT 5000' | scripts/bulk.sh - > /tmp/audit.jsonl
output: JSONL. valid entries (passed local checksum) output status records; invalid entries output error records:
{"b_no":"1248100998","b_no_formatted":"124-81-00998","b_stt_cd":"01","b_stt":"계속사업자",...}
{"b_no":"123-45-67890","error":"checksum_failed","reason":"NTS checksum algorithm rejected this b_no"}
interpretation: same as step 3a. checksum-failed entries never hit the API, saving quota.
error handling: if the file is empty, output nothing (exit 0). if file does not exist, exit with error message. if network times out mid-batch, the partial batch's output is written; re-run to resume (duplicates are safe).
use jq to filter, extract, or transform the JSONL output into reports or conditional logic.
examples:
find all closed businesses:
scripts/bulk.sh suppliers.txt | jq -c 'select(.b_stt_cd=="03") | {b_no, end_dt}'
tax-type breakdown:
scripts/bulk.sh suppliers.txt | jq -r '.tax_type' | sort | uniq -c | sort -rn
onboarding gate (only proceed if active + standard VAT):
scripts/status.sh "$BNO" \
| jq -e 'select(.b_stt_cd=="01" and .tax_type_cd=="01")' >/dev/null \
&& echo "ok to issue 세금계산서" \
|| { echo "blocked"; exit 1; }
if NTS_API_KEY is missing or unset: skip steps 3a, 3b, and 4 (network calls). you can still run step 2 (local checksum) to validate format. error message: "NTS_API_KEY not set. run: export NTS_API_KEY='your-encoded-key-from-data.go.kr'".
if a b_no fails local checksum (step 2): flag it with valid_checksum:false and do not submit to NTS. the checksum algorithm catches ~95% of typos and is free to compute. user should re-enter or investigate source data.
if b_stt_cd is empty (step 3a output): the b_no is not registered with NTS at all. block the transaction or require manual verification.
if b_stt_cd is "02" (dormant, step 3a output): business is temporarily inactive. proceed only if you have explicit approval from the user or counter-party. flag for manual review in KYB workflows.
if b_stt_cd is "03" (closed, step 3a output): block the transaction unless you are auditing historical records. check end_dt to determine when closure occurred.
if API returns code:-4 ("등록되지 않은 인증키"): your NTS_API_KEY is wrong, not approved for this endpoint, or approval has been revoked. check data.go.kr 마이페이지 → 활용신청 현황 to verify approval status. reapply if necessary.
if API returns code:-22 ("사용한도 초과"): you have exceeded your 10,000 free requests per endpoint per day. batch your requests more aggressively or apply for 한도 증가 in 마이페이지. requests beyond quota are rejected; no auto-charge.
if network call times out (step 3a, 3b, or 4): retry once after 2-5 seconds. if timeout persists, assume NTS API is temporarily unavailable. for bulk operations, you can safely re-run bulk.sh on the same file (checksum filtering + idempotent API calls mean duplicates are harmless).
if file is empty or contains only comments (step 4): exit with code 0 and produce no output.
all output is JSONL (JSON Lines: one complete JSON object per line, no array wrapper, newline-separated). each line is independently parseable by jq, csvkit, Python, or downstream skills.
step 2 output (format.sh):
{"input":"<original-input>","normalized":"<10-digit-string>","formatted":"<XXX-XX-XXXXX>","valid_checksum":true|false}
step 3a and step 4 output (status.sh, bulk.sh):
{"b_no":"<10-digit>","b_no_formatted":"<XXX-XX-XXXXX>","b_stt_cd":"01|02|03|<empty>","b_stt":"계속사업자|휴업자|폐업자","tax_type_cd":"01|02|03|04|05|06","tax_type":"<korean-description>","end_dt":"<YYYYMMDD>|<empty>","utcc_yn":"Y|N","tax_type_change_dt":"<YYYYMMDD>|<empty>","invoice_apply_dt":"<YYYYMMDD>|<empty>","rbf_tax_type_cd":"<code>|<empty>","rbf_tax_type":"<description>|<empty>"}
error record (step 4 only):
{"b_no":"<original>","error":"checksum_failed","reason":"NTS checksum algorithm rejected this b_no"}
step 3b output (validate.sh):
{"b_no":"<10-digit>","b_no_formatted":"<XXX-XX-XXXXX>","valid":true|false,"valid_code":"01|02","valid_msg":"<korean-message>","status":{...NTS-status-object...}}
file locations: output is written to stdout by default. redirect with > file.jsonl. no temporary files are created unless you specify --file in validate.sh, in which case the JSON payload file is user-supplied and not cleaned up.
character encoding: all output is UTF-8. korean characters (한글) are preserved in the JSON and not escaped.
you know the skill worked when:
step 2 (format.sh): you see valid_checksum:true for correctly-formatted b_no's and valid_checksum:false for typos or invalid inputs.
step 3a (status.sh): you receive JSONL output with b_stt_cd (status code) and tax_type_cd (tax classification). if b_stt_cd is empty, the b_no is not registered; if "01", the business is active.
step 3b (validate.sh): you receive valid:true if the triple (b_no + opening date + representative name) matches NTS records exactly. valid:false if any field is incorrect.
step 4 (bulk.sh): you receive a JSONL file with one line per input b_no (plus error lines for checksum failures). pipe into jq to count closed businesses (`jq -c 'select(.b_stt_