法人番号Web-APIで取引先名簿を名寄せする — Claude Codeで商号・所在地の変更を追う
国税庁の法人番号Web-APIで取引先マスタと突合し、商号変更・所在地変更・閉鎖をClaude Codeで洗い出す手順。変更履歴と差分取得の使い分けつき。
取引先マスタの商号が、登記上の名称とずれていませんか。社名変更や本店移転は、取引先から連絡が来ないまま進むことが少なくありません。請求書や契約書の宛名を間違える原因になります。
国税庁の法人番号システムには、法人番号をキーに最新の商号と所在地を取れるWeb-APIがあります。マスタの法人番号で引けば、名称と住所のずれが一覧で出ます。名寄せ(表記の違う同一法人をひとつにまとめる作業)の土台に使えます。突合スクリプトはClaude Codeに書かせ、判定の分岐をテストで固めるのが早道です。ここでは、使える機能の整理から、スクリプト、差分取得による変更の追跡までを順に扱います。適格請求書の登録番号を照合したい場合は、適格請求書発行事業者のWeb-APIをClaude Codeで呼ぶ手順が別の仕組みを扱っています。
名寄せに使う3機能と、変更履歴の取り方
法人番号システムのWeb-APIは、REST方式で次の3機能を提供します。
| 機能 | エンドポイント | 向く場面 |
|---|---|---|
| 法人番号を指定 | エンドポイント/4/num | 向く場面マスタの法人番号から最新の商号・所在地を引く |
| 法人名を指定 | エンドポイント/4/name | 向く場面法人番号が空の取引先を名称から探す |
| 取得期間を指定 | エンドポイント/4/diff | 向く場面期間内に変更があった法人だけを拾う |
先頭の 4 はAPIのバージョンです。Ver.2.0で英語表記、Ver.3.0でフリガナ、Ver.4.0で「検索対象除外」とされた法人の情報が加わりました。法人名を指定する機能はVer.2.0以降で使えます。新規に作るなら、すべてを含むVer.4.0で足ります。
名寄せの軸にするのは法人番号(13桁)です。番号は商号や所在地が変わっても変わりません。名称で突合する方式は表記ゆれに弱いので、番号があるものは番号で引き、無いものだけ名称検索に回します。
法人番号を指定する機能は、history=1 を付けると変更履歴も返します。履歴は古い順に並び、最新の1件だけ latest が 1 になります。公式の例では、商号が変わった法人に対し「変更前」「変更後」の2件が返ります。
curl -s "https://api.houjin-bangou.nta.go.jp/4/num\
?id=$HOUJIN_APP_ID&number=8040001999013&type=12&history=1"type は応答形式で、01 がCSV(Shift-JIS)、02 がCSV(Unicode)、12 がXML(Unicode)です。文字コードの扱いを避けたいので、以降はXMLを使います。法人番号は、カンマ区切りで最大10件まで1回に渡せます。
Web-APIでは全件データを取得できません。全件が要る場合は、同じサイトの基本3情報ダウンロードを使う設計になります。
使い始める前に済ませる準備
アプリケーションIDを申請する
利用にはアプリケーションID(13桁)が必要です。発行に費用はかかりません。ただし発行まで2週間から1か月ほどかかるので、実装より先に申請します。申請や変更、利用停止の手続きは、国税庁適格請求書発行事業者公表サイトから行います。インボイス側のIDとの関係は、登録番号照合の記事のIDの節に手順があります。
規約で押さえる点
利用規約には、スクリプトの設計に直結する条項があります。
- IDを第三者に譲渡・貸与・開示してはならない
- 短時間の大量アクセスは禁止。集中時には利用を制限されることがある
- 3年以上アクセス実績のないIDは、利用停止の対象になりうる
- Web-APIで得た情報を使ったサービスを提供する場合は、取得元を明示する
明示する文言は「このサービスは、国税庁法人番号システムのWeb-API機能を利用して取得した情報をもとに作成しているが、サービスの内容は国税庁によって保証されたものではない」です。社内の名簿チェックだけなら公開サービスには当たりません。突合結果を画面や資料として社外に出すなら、この一文を入れておきます。
検証環境で先に試す
国税庁は、架空の法人名を使った検証環境を用意しています。使うには利用希望の連絡が必要で、概ね1週間以内に案内の資料がメールで届きます。IDは本番と検証のどちらでも使えます。
法人番号がない取引先は名称検索で埋める
マスタに法人番号が入っていない行は、法人名を指定する機能で探します。条件は次のとおりです。
| 条件 | 項目ID | 値 |
|---|---|---|
| 法人名(必須) | 項目IDname | 値UTF-8でURLエンコードした値。1回に1つだけ |
| 検索方式 | 項目IDmode | 値1 前方一致(既定)、2 部分一致 |
| 検索対象 | 項目IDtarget | 値1 JIS第一・第二水準(あいまい検索)、2 第一〜第四水準(完全一致)、3 英語表記 |
| 所在地 | 項目IDaddress | 値都道府県コード2桁、または市区町村コードまで5桁 |
| 法人種別 | 項目IDkind | 値01 国の機関、02 地方公共団体、03 設立登記法人、04 外国会社等・その他 |
| 変更履歴 | 項目IDchange | 値1 で旧名称も検索対象にする |
| 閉鎖等 | 項目IDclose | 値0 で閉鎖済みを除く(既定は含める) |
前方一致では、「株式会社」などの法人格を除いた名称の先頭から検索します。名簿の「株式会社○○」から法人格を外して name に渡す、という扱いです。部分一致なら法人格を含めた検索もできます。
名称検索には注意が2つあります。
- 候補が複数返る: 同名の法人は珍しくありません。
addressで都道府県や市区町村を絞り、候補が1件に定まったときだけ自動採用します。複数なら人の確認に回します。 change=1では最新情報が返らない: 旧名称でヒットした場合、返るのは変更前の法人情報で、その法人の最新情報は応答に含まれません。応答の法人番号で、法人番号を指定する機能を引き直します。
結果が多すぎる条件はエラー(エラーコード180)になります。条件を足して絞り直します。名称検索で番号が確定したら、マスタに法人番号を書き戻します。次回以降は番号で引けます。
制約をClaude Codeに渡す
仕様の要点は、毎セッション読み込まれるCLAUDE.mdに書いておきます。ここにあるのは、公式仕様から引いた制約です。
## 法人番号 Web-API
- ホストは api.houjin-bangou.nta.go.jp、バージョンは /4/
- 法人番号は1リクエスト最大10件、カンマ区切り。履歴が要るときは history=1
- 応答はXML(type=12)。latest=1 の行が最新、履歴は古い順
- アプリケーションIDは .env の HOUJIN_APP_ID から読む。コード・ログ・会話に出さない
- リクエストの間は1秒以上空ける(規約: 短時間の大量アクセス禁止)
- 元のCSVは上書きしない。結果は別ファイルへ
- 確認の順序はスタブのテスト、検証環境、本番CLAUDE.mdの構成はCLAUDE.mdの書き方パターンが参考になります。
IDの漏えいは、権限ルールで読み取りを拒否して防ぎます。公式は Read(./.env) のようにパスを指定する形を例に挙げています。
{
"permissions": {
"deny": ["Read(./.env)"]
}
}この拒否が効くのは、組み込みのファイルツールと、cat などBashの一部のファイルコマンドです。Pythonスクリプトが自分で .env を読む動作は止められません。厳密に防ぐなら、サンドボックスを併用します。権限ルールの管理は/permissionsコマンドの記事にまとまっています。
依頼は、成果物の形で伝えます。
customers.csv(取引先名,法人番号,所在地)を法人番号Web-API(/4/num、XML、history=1)で
突合する match_houjin.py を書いて。CLAUDE.md の制約を守ること。
出力は別CSV。一致・要更新・閉鎖合併・該当なしを区別し、
要更新は商号と所在地のどちらがずれたかを差分欄に書いて。
先に公式サンプルのXMLでテストを書き、通ってから本番を叩く。「先にテストを書く」と指示に入れておくと、Claudeはテストを実行して失敗を読みながら直せます。判定ロジックは、通信を伴わずに検証できるからです。
突合スクリプトの中身
書き上がるコードの主要部は、次のような形になります。公式仕様の項目名に沿って組んだ例で、Claudeの出力そのものではありません。
import csv, os, re, sys, time, unicodedata
import urllib.parse, urllib.request
import xml.etree.ElementTree as ET
BASE = "https://api.houjin-bangou.nta.go.jp/4/num"
CHUNK = 10 # 法人番号は1リクエストあたり最大10件
def norm(name):
s = unicodedata.normalize("NFKC", name)
s = re.sub(r"[\s ]", "", s)
return re.sub(r"株式会社|\(株\)|有限会社|\(有\)|合同会社|\(同\)", "", s)
def parse(xml_text):
root = ET.fromstring(xml_text)
recs = []
for c in root.findall("corporation"):
recs.append({e.tag: (e.text or "") for e in c})
return recs
def fetch(app_id, numbers):
q = urllib.parse.urlencode(
{"id": app_id, "number": ",".join(numbers), "type": "12", "history": "1"},
safe=",")
with urllib.request.urlopen(f"{BASE}?{q}", timeout=30) as res:
return parse(res.read().decode("utf-8"))
def address(r):
return r["prefectureName"] + r["cityName"] + r["streetNumber"]
def judge(row, recs):
if not recs:
return "該当なし", ""
latest = [r for r in recs if r["latest"] == "1"][-1]
if latest["process"] in ("21", "71"):
return "閉鎖・合併", latest.get("successorCorporateNumber", "")
notes = []
if norm(latest["name"]) != norm(row["取引先名"]):
notes.append("商号: " + latest["name"])
if norm(address(latest)) != norm(row["所在地"]):
notes.append("所在地: " + address(latest))
return ("要更新" if notes else "一致"), " / ".join(notes)
def main(src, dst, app_id):
with open(src, encoding="utf-8-sig", newline="") as f:
rows = list(csv.DictReader(f))
by_num = {}
valid = [r for r in rows if re.fullmatch(r"\d{13}", r["法人番号"])]
for i in range(0, len(valid), CHUNK):
part = [r["法人番号"] for r in valid[i:i + CHUNK]]
for rec in fetch(app_id, part):
by_num.setdefault(rec["corporateNumber"], []).append(rec)
time.sleep(1) # 短時間の大量アクセスは規約で禁止
with open(dst, "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f)
w.writerow(["取引先名", "法人番号", "判定", "差分"])
for r in rows:
kind, diff = judge(r, by_num.get(r["法人番号"], []))
w.writerow([r["取引先名"], r["法人番号"], kind, diff])
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2], os.environ["HOUJIN_APP_ID"])要点は3つです。
normで全角半角を揃え、空白と法人格(株式会社・(株)など)を外してから比べます。「(株)」と「株式会社」の違いを差分にしないためです- 比較は
latestが1の行で行います。履歴の古い行と比べると、変更前の名称に一致して「一致」になりかねません - 閉鎖は
processが21(登記記録の閉鎖等)か71(吸収合併)の行で判定します。successorCorporateNumberに承継先の法人番号が入る場合があるため、差分欄に書き出します
process(処理区分)の主な値は、01 新規、11 商号又は名称の変更、12 国内所在地の変更、13 国外所在地の変更、21 登記記録の閉鎖等、22 閉鎖後の復活等、71 吸収合併、72 吸収合併無効、81 商号の登記の抹消、99 削除です。99 の行は、一連番号・法人番号・更新年月日以外が空になります。
判定結果をテストで固める
判定の分岐は、公式サンプルのXMLからテストを作れます。概要編にある商号変更の例(法人番号8040001999013)を、固定のXMLとして使った例です。
import match_houjin as m
X = """...(公式サンプルのXML。2件の<corporation>、latestが0と1)..."""
def test_rename():
recs = m.parse(X)
assert len(recs) == 2
row = {"取引先名": "株式会社商号変更前", "所在地": "千葉県千葉市中央区蘇我5丁目9番1号"}
kind, diff = m.judge(row, recs)
assert kind == "要更新" and "商号変更後" in diff
def test_abbreviation_is_not_a_diff():
row = {"取引先名": "(株)商号変更後", "所在地": "千葉県千葉市中央区蘇我5丁目9番1号"}
assert m.judge(row, m.parse(X))[0] == "一致"この2つのテストは、サンプルをもとにしたXMLで通ることを確かめました。ただしXMLは公式サンプルをもとにした固定データで、国税庁側の挙動を証明するものではありません。確かめられるのは、スクリプトが仕様の項目名どおりに読み、分岐を取りこぼさないかどうかです。実データの動作は、検証環境、本番の順に10件程度から確かめます。
差分取得で商号変更と所在地変更を追う
突合を1回きりで終わらせず、変更が出たら気づける形にするのが、取得期間を指定する機能です。指定した期間に、法人番号の指定、商号・所在地の変更、登記記録の閉鎖等があった法人の情報を返します。
curl -s "https://api.houjin-bangou.nta.go.jp/4/diff\
?id=$HOUJIN_APP_ID&from=2026-10-01&to=2026-10-12&type=12&address=13"仕様の制約は次のとおりです。
fromは2015-12-01以降。これより前だとエラー(エラーコード013)になります- 期間の最大は50日。
toはfromから50日以内に置きます - 結果が2,000件を超えると分割され、ヘッダーの「分割数」が1以外になります。
divideを1から分割数まで増やして取得します addressで都道府県(2桁)や市区町村(5桁)、kindで法人種別(最大4種類)を絞れます- 結果は更新年月日の昇順、次に法人番号の昇順で並びます
ここで気づく点があります。差分には全国の変更が入るため、そのままでは件数が多くなります。使い方としては、返ってきた法人番号をマスタの法人番号集合と突き合わせ、重なったものだけを「要確認」に回す形が向いています。
運用の型は2つ考えられます。
- 月次で全件を引き直す: マスタの法人番号をすべて
/4/numで引き、差分欄を出す。取引先が数百社なら、1秒間隔でも1分前後で終わります - 日次・週次で差分を見る: 前回の
toの翌日から今日までを/4/diffで取り、マスタと重なる番号だけ通知する。取得漏れを避けるため、前回の終了日を保存しておきます
定期実行の設定をClaude Codeに任せる前に、1回目は手動で走らせます。出力CSVを目で読んでから、スケジューラーに載せます。
判定結果の読み方
出力CSVの判定は、取るべき行動が分かれるように区分します。
| 判定 | 意味 | 次の行動 |
|---|---|---|
| 一致 | 意味商号・所在地とも正規化後に一致 | 次の行動何もしない |
| 要更新(商号) | 意味process が 11 の履歴があり、名称がずれている | 次の行動契約書・請求書の宛名を確認し、マスタを更新 |
| 要更新(所在地) | 意味国内所在地の変更(12)で住所がずれている | 次の行動送付先と契約書の記載を確認 |
| 閉鎖・合併 | 意味21 や 71 | 次の行動取引継続の有無を担当者に確認。承継先法人番号を控える |
| 該当なし | 意味応答に行がない | 次の行動番号の入力ミスの可能性。名称検索で再確認 |
商号の変更と所在地の変更は、changeDate(変更年月日)で発生時期が分かります。「いつからずれていたか」を見たいときに使います。
判定を狂わせやすい点
- 住所の表記差: 公式サンプルでは所在地が「蘇我5丁目9番1号」と返ります。マスタが「蘇我5-9-1」なら、正規化しても一致しません。比較前に丁目・番・号をハイフンへ寄せる処理を足すか、都道府県と市区町村だけで比べて、丁目以降は人が見る運用にします。この種の誤検出を最初に洗い出すには、実データ10行で出力を読ませるのが確実です
- 名称のイメージID: 商号がJIS第一・第二水準以外の文字を含む場合や、150文字を超える場合は、
nameImageIdに値が入ります。文字を取りこぼしている可能性があるため、このときは「要確認」に回します - 検索対象除外の法人: Ver.4.0では、検索対象除外の項目が
1の法人もAPIで取得できます。公表サイトの検索画面では、既定の条件で表示されません。サイトで目視確認したとき、APIでは出る法人が見つからないことがあります - アクセス制限: 同じIDで短時間に多数アクセスすると、HTTP 403が返ります。IDが未登録や無効なときは404、公表機能の障害時は500です。403を見たら、リトライを重ねず間隔を空けます
- エラーの返り方: 誤りのあるリクエストでは、HTTPステータスコードに加え、エラーコードとメッセージがCSV形式で応答すると仕様にあります。XMLのパーサーに渡す前に、先頭が
<かどうかで分岐させます
変更の通知先や運用フローは、取引先管理の担当者と決めます。スクリプトが出すのは「ずれの候補」までで、宛名や契約の変更は人が判断します。