GASで経費明細から
AIで勘定科目を自動仕分けする方法
スプレッドシートに並んだ経費明細を、AI(Claude API)で消耗品費旅費交通費などの勘定科目に自動で振り分ける方法を、動くコードで解説します。科目候補を固定し、確信度が低い明細だけ人が確認する現実的な運用まで紹介します。
Table of Contents
経費の勘定科目仕分けは手作業だと大変
経費精算では、1件ごとに「これは消耗品費か、それとも事務用品費か」と勘定科目(会計上の分類ラベル)を決める必要があります。 件数が多い月末や、担当者によって判断がぶれる点が、経理の地味な負担になりがちです。
摘要(何を買ったか)と支払先から科目を推測する作業は、AIが得意とするところです。 スプレッドシートの明細をClaude APIに渡し、決まった科目候補の中から選ばせれば、下書きの仕分けを自動化できます。 ただし科目の最終判断は経理ルールや税務に関わるため、AIの結果は「たたき台」として扱うのが前提です。
月末のまとめ処理
溜まった経費明細を一括で仮仕分けし、確認作業だけに集中できる
判断のぶれを減らす
同じ候補から選ばせることで、担当者ごとの科目ゆれを抑えられる
会計ソフト取り込み前の下ごしらえ
科目列を埋めてからCSVで会計ソフトに取り込める
確認すべき明細の可視化
確信度の低い明細だけを浮かび上がらせ、チェックを効率化できる
APIキーを安全に保管する
まずClaude APIのキーを用意します。コードに直接書くと共有時に漏れる恐れがあるため、PropertiesService(スクリプトごとの設定値を安全に保管する仕組み)のスクリプトプロパティに保存します。
// スクリプトプロパティにAPIキーを保存しておく(初回1回だけ実行)
function saveApiKey() {
PropertiesService.getScriptProperties()
.setProperty("CLAUDE_API_KEY", "sk-ant-xxxxxxxx");
}
function getApiKey() {
const key = PropertiesService.getScriptProperties()
.getProperty("CLAUDE_API_KEY");
if (!key) throw new Error("APIキーが未設定です。saveApiKey を実行してください。");
return key;
}勘定科目の候補を固定する
この自動化で最も大事なのが、選ばせる勘定科目を先に固定することです。 候補を渡さないと、AIは「消耗品費」「事務用品費」のように似た科目を毎回ゆらいで返してしまいます。 自社で実際に使っている科目一覧を配列にして、その中から選ばせます。
// 自社で使っている勘定科目に差し替える(ここを固定するのが最大のポイント)
const ACCOUNTS = [
"旅費交通費",
"消耗品費",
"会議費",
"接待交際費",
"通信費",
"新聞図書費",
"水道光熱費",
"支払手数料",
"広告宣伝費",
"外注費",
];会計ソフトの科目コードがある場合は、科目名とコードをセットで渡し、コードも一緒に返してもらうと転記が楽になります。
経費明細を渡して科目と確信度を受け取る
摘要・金額・支払先をまとめてClaude APIに渡し、勘定科目とconfidence(その判断の自信度。0〜1の数値)をJSONで受け取ります。 確信度を一緒にもらうことで、あとで「自信のない明細だけ人が確認する」運用ができます。 AIの返答には前後に説明文が付くことがあるため、[から]までを切り出してからJSON.parseする点がポイントです。
// 経費明細の配列を渡し、勘定科目と確信度を受け取る
function classifyExpenses(items) {
const prompt =
"あなたは日本の中小企業の経理担当です。\n" +
"次の経費明細を、下の勘定科目の候補から1つだけ選んで仕分けしてください。\n" +
"候補にどうしても当てはまらない場合だけ \"要確認\" としてください。\n" +
"各明細に、選んだ理由の自信度を confidence として 0〜1 で付けてください。\n" +
"余計な説明は出力せず、JSON配列だけを返してください。\n" +
"形式: [{\"index\":0,\"account\":\"消耗品費\",\"confidence\":0.9}]\n\n" +
"勘定科目の候補:\n" + ACCOUNTS.join(" / ") + "\n\n" +
"経費明細(番号: 摘要 / 金額 / 支払先):\n" +
items.map((it, i) =>
i + ": " + it.memo + " / " + it.amount + "円 / " + it.payee
).join("\n");
const payload = {
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
};
const res = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", {
method: "post",
contentType: "application/json",
headers: {
"x-api-key": getApiKey(),
"anthropic-version": "2023-06-01",
},
payload: JSON.stringify(payload),
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 200) {
throw new Error("API error: " + res.getContentText());
}
const text = JSON.parse(res.getContentText()).content[0].text;
return JSON.parse(extractJson(text));
}
// レスポンスからJSON配列部分だけを取り出す(前後の余計な文字対策)
function extractJson(text) {
const start = text.indexOf("[");
const end = text.lastIndexOf("]");
return text.slice(start, end + 1);
}15件ずつのバッチ処理で一括仕分け
1件ずつAPIを呼ぶと通信回数が増え、実行時間もコストもかさみます。15件をまとめて1回のリクエストで送るバッチ処理にします。 勘定科目列(E列)がすでに埋まっている行はスキップするので、途中で止まっても再実行すれば続きから処理できます。
// A列=日付 B列=摘要 C列=金額 D列=支払先 E列=勘定科目 F列=確信度 を想定
function classifyExpensesAll() {
const sheet = SpreadsheetApp.getActive().getSheetByName("経費");
const last = sheet.getLastRow();
if (last < 2) return;
const values = sheet.getRange(2, 1, last - 1, 6).getValues();
const BATCH = 15;
// 勘定科目(E列)が未入力の行だけを対象にする
const targets = [];
values.forEach((row, i) => {
const memo = String(row[1]).trim();
const done = String(row[4]).trim() !== "";
if (memo && !done) {
targets.push({
rowIndex: i,
memo: memo,
amount: row[2],
payee: String(row[3]).trim(),
});
}
});
for (let s = 0; s < targets.length; s += BATCH) {
const chunk = targets.slice(s, s + BATCH);
const result = classifyWithRetry(chunk);
result.forEach((r) => {
const target = chunk[r.index];
if (!target) return;
values[target.rowIndex][4] = r.account || "要確認";
values[target.rowIndex][5] = r.confidence;
});
Utilities.sleep(500); // 連続呼び出しの間隔をあける
}
sheet.getRange(2, 1, values.length, 6).setValues(values);
}一時的なAPIエラーに備えて、失敗したら少し待って再試行するリトライも挟みます。待ち時間を試行のたびに伸ばす(指数バックオフ)と、混雑時に成功しやすくなります。
// 一時的なエラー時に最大3回まで再試行する
function classifyWithRetry(items, maxRetry) {
const limit = maxRetry || 3;
for (let attempt = 1; attempt <= limit; attempt++) {
try {
return classifyExpenses(items);
} catch (e) {
Logger.log("試行" + attempt + "回目で失敗: " + e.message);
if (attempt === limit) throw e;
Utilities.sleep(1000 * attempt); // 待ち時間を伸ばして再試行
}
}
}確信度が低い明細だけ色を付けて確認する
AIの仕分けをそのまま信じるのではなく、確信度が低い明細と「要確認」になった明細にだけ背景色を付け、人がチェックすべき行を目立たせます。 全件を見直すのではなく、怪しい行だけに絞れるので確認が一気に楽になります。
// 確信度が低い行・要確認の行に色を付けて担当者に知らせる
function highlightLowConfidence() {
const sheet = SpreadsheetApp.getActive().getSheetByName("経費");
const last = sheet.getLastRow();
if (last < 2) return;
const range = sheet.getRange(2, 5, last - 1, 2); // E列(科目)とF列(確信度)
const values = range.getValues();
const THRESHOLD = 0.8; // この値未満は人の確認対象にする
const colors = values.map((row) => {
const account = String(row[0]).trim();
const confidence = Number(row[1]);
const needCheck = account === "要確認" || confidence < THRESHOLD;
return [needCheck ? "#FCE8E6" : null, needCheck ? "#FCE8E6" : null];
});
range.setBackgrounds(colors);
}しきい値(この例では0.8)は運用しながら調整してください。厳しくすると確認対象が増え、緩めると見落としが増えます。
毎月のトリガーで自動実行する
毎月決まったタイミングで仕分けと確認マークをまとめて動かすには、時間主導トリガー(時刻や日付で自動実行する仕組み)を使います。onMonthDayで毎月1日を指定できます。
// 毎月1日の朝に自動で仕分けと確認マークを実行する
function createMonthlyTrigger() {
ScriptApp.newTrigger("runMonthlyClassify")
.timeBased()
.onMonthDay(1)
.atHour(7)
.create();
}
function runMonthlyClassify() {
classifyExpensesAll();
highlightLowConfidence();
}これで、前月分の経費が仮仕分けされ、確認すべき行に色が付いた状態で担当者の朝を迎えられます。
実務での注意点
1. 最終判断は人と税理士に委ねる
勘定科目は税務や自社の経理方針に依存します。AIの仕分けはあくまで下書きで、正解を保証しません。 確定は経理担当や税理士のチェックを前提にし、AIの結果を鵜呑みにしないでください。
2. 科目候補を自社仕様に保つ
候補にない科目が必要になったら、コードのACCOUNTSを更新します。過去の仕分け実績(摘要と確定科目の対応)を数件プロンプトに例示すると、判断がさらに安定します。
3. 情報の外部送信ルールを確認する
経費データには取引先名や金額が含まれます。外部APIに送る前に、社内規程や取引先との契約で外部送信が許されるかを確認してください。
4. 実行時間の上限に注意する
GASには1回あたり6分の実行時間制限があります。件数が多い場合は、仕分け済み行をスキップする仕組みと分割実行を組み合わせると安定します。
まとめ
経費明細の勘定科目仕分けは、候補科目を固定してClaude APIに渡せば下書きを自動化できます。 確信度を一緒に受け取り、自信のない明細だけ色を付けて人が確認する運用にすれば、全件チェックより格段に楽になります。 15件ずつのバッチ処理、仕分け済み行のスキップ、リトライ、毎月のトリガーを組み合わせれば、月末の経費処理を実務レベルで支えられます。 科目の最終判断は人と税理士に委ねる、という前提だけ守って活用してください。
よくある質問
そのままの利用はおすすめしません。勘定科目の判断は税務や自社の経理ルールに依存し、AIは統計的にそれらしい科目を返すだけで正解を保証しません。この記事のように確信度(自信度)を一緒に受け取り、確信度が低い明細は人が確認する運用にしてください。最終的な仕訳の確定は経理担当や税理士のチェックを前提にするのが安全です。
自社で実際に使っている科目一覧をそのままAIに渡すのが基本です。候補を固定しておかないと、AIが「事務用品費」「消耗品費」のように似た科目を毎回ゆらいで返してしまいます。コードの科目リストを自社の勘定科目に差し替えて使ってください。会計ソフトの科目コードがあれば、科目名と一緒にコードも返してもらうと転記が楽になります。
候補科目をコードで固定したうえで、プロンプトに「必ず候補の中から選ぶ」「候補にない場合は要確認とする」と明記してください。それでもゆれる場合は、過去の仕分け実績(摘要と確定科目の対応表)を数件プロンプトに例として添えると、判断の基準が安定します。
できますが、1件ずつAPIを呼ぶと通信回数が増えて時間がかかります。この記事のように15件ずつまとめて1回のリクエストで送るバッチ処理にすると、実行時間とAPIコストの両方を抑えられます。仕分け済み行をスキップする仕組みと併用すれば、途中で止まっても再実行で続きから処理できます。
直接書くのは避けてください。スクリプトを共有した際にキーが漏れる恐れがあります。PropertiesService のスクリプトプロパティに保存し、コードからは PropertiesService.getScriptProperties().getProperty() で読み込む方法が安全です。
関連するサービス・記事
経理・バックオフィスの
自動化を相談する。
経費の勘定科目仕分け以外にも、請求書の読み取り・経費集計・会計データ整備など、バックオフィスを自動化するGAS×AI開発をご相談いただけます。