GASで請求書発行を自動化している。ボタン一つで請求書PDF作成→メール送信→チャット報告→スプレッドシート更新まで全部走る仕組みだ。
最初のプロジェクトは順調だった。取引先のMisoca IDもわかっていたし、APIドキュメント通りに叩けば動いた。問題は2つ目。新しい取引先のIDが必要になった。
「APIで取引先一覧を取ればいいだろ」
甘かった。ここからMisoca API v3の罠に3回ハマることになる。
そもそも何を自動化しているのか
毎月、取引先から明細メールが届く。中身は売上報告とPDF明細書。これを見て請求書を作り、返信メールに添付し、社内チャットで報告し、経理用スプレッドシートに記帳する。
手作業だと毎月15分。ミスると請求漏れ。地味だけど放置すると痛い。
GASで組んだフローはこう:
① Gmailから明細メールを自動検索
② 金額をパース → プレビュー画面で確認
③ Misoca APIで請求書作成 → PDF取得
④ スプレッドシートに記帳
⑤ Gmailで請求書PDF添付の返信
⑥ Chatworkで社内報告
⑦ Google Driveに請求書・明細書を保存
ボタン1回。全部終わる。完成形のプレビュー画面はこんな感じだ。
この仕組みを別の取引先にも展開しようとして、取引先IDの壁にぶつかった。
罠① リストAPIの返却フィールドが直感と違う
まず取引先の一覧を取得する。ここは素直にいける。
function listMisocaContacts() {
var service = getMisocaService();
if (!service.hasAccess()) {
Logger.log('Misoca未認証です。先にOAuth認証を実行してください。');
return;
}
var url = 'https://app.misoca.jp/api/v3/contacts?per_page=50&page=1';
var res = UrlFetchApp.fetch(url, {
headers: { 'Authorization': 'Bearer ' + service.getAccessToken() },
muteHttpExceptions: true
});
var contacts = JSON.parse(res.getContentText());
Logger.log('=== Misoca取引先一覧 ===');
contacts.forEach(function(c) {
Logger.log(c.id + ' : ' + c.recipient_name +
(c.recipient_name1 && c.recipient_name1 !== c.recipient_name
? ' (name1: ' + c.recipient_name1 + ')' : ''));
});
Logger.log('合計: ' + contacts.length + '件');
if (contacts.length === 50) {
Logger.log('※ 50件以上あります。page=2以降も確認してください。');
}
}
コードは動く。問題は返ってくるJSON。
取引先の名前を取りたいとき、普通は name とか company を探すだろう。ない。 そんなフィールドは存在しない。
正解は recipient_name。
「recipient(受取人)」——つまり請求書の宛名フィールドそのまま。考えてみれば当たり前なんだけど、最初に name で検索して undefined が返ってきたときは「おい嘘だろ」と声が出た。
ドキュメントにフィールド一覧がちゃんと載っていればこんなことにはならない。が、Misoca APIのドキュメントは必要最低限しか書いてない。結局、レスポンスを丸ごとダンプして眺めるのが一番早かった。
罠② 同名の取引先が複数ある
一覧を取得してログを眺めていたら、同じ名前の取引先が2件。
ID: 8165600 : 株式会社A商事
ID: 8406825 : 株式会社A商事
同じ会社と複数の取引がある場合、用途ごとに取引先を分けて登録していることがある。経費精算用とレベニューシェア用、のように。
で、どっちがどっちだ?
ここで recipient_name1 が効いてくる。Misocaの取引先には recipient_name の他に recipient_name1 というフィールドがある。請求書の宛名2行目に相当するもので、部署名や担当者名を入れるのが一般的。うちの場合は「支払元の別名」を入れて区別していた。
ID: 8165600 : 株式会社A商事 (name1: 株式会社A商事) ← 経費精算用
ID: 8406825 : 株式会社A商事 (name1: B株式会社) ← レベニューシェア用
これで区別がつく。でもログだけでは「どっちがどの取引に紐づくか」がわからない。
確実なのは、過去の請求書を引っ張ること。ただし使うのは contact_id ではなく contact_group_id。ここも罠。
function identifyContactByInvoices(contactGroupId) {
var service = getMisocaService();
var url = 'https://app.misoca.jp/api/v3/invoices?per_page=5&page=1'
+ '&contact_group_id=' + contactGroupId;
var res = UrlFetchApp.fetch(url, {
headers: { 'Authorization': 'Bearer ' + service.getAccessToken() },
muteHttpExceptions: true
});
var invoices = JSON.parse(res.getContentText());
invoices.forEach(function(inv) {
Logger.log(inv.issue_date + ' | ' + inv.subject + ' | '
+ (inv.body.total_amount_including_tax || ''));
});
}
過去の請求書の件名と金額を見れば、どの取引先がどの用途かすぐわかる。contact_group_id は取引先一覧のレスポンスに含まれているので、一覧取得→請求書検索の2ステップで特定できる。
なぜ contact_id ではなく contact_group_id なのか? 正直よくわからない。Misocaの内部では取引先を「グループ」で束ねているらしいが、ドキュメントに説明はない。試して動いたからそう使っている。
罠③ 個別取得エンドポイントがHTMLを返す
取引先の詳細を見たくて /contacts/{id} を叩いた。
var res = UrlFetchApp.fetch(
'https://app.misoca.jp/api/v3/contacts/' + contactId,
{ headers: { 'Authorization': 'Bearer ' + token } }
);
Logger.log(res.getContentText());
返ってきたのは <!DOCTYPE html>。
は?
HTMLだ。Misocaの取引先編集画面のHTMLが丸ごと返ってくる。JSONじゃない。APIエンドポイントに投げて、ブラウザ用のHTMLが返ってくる体験。なかなか新鮮だった。
「Content-Typeの指定が足りないのか?」と思って Accept: application/json をつけてみた。
変わらない。HTMLが返る。
請求書の個別取得 /invoices/{id} も試した。同じ。HTML。
一瞬、自分のコードがおかしいのかと疑った。URLを3回見直した。間違ってない。エンドポイントも合ってる。認証も通ってる。なのにHTMLが返る。
結論: v3のAPIでまともにJSONが返るのはリストエンドポイントだけ。個別取得は全部ブラウザ用のHTMLにリダイレクトされる。仕様なのかバグなのか判断がつかないが、2026年3月時点でこの挙動だった。
つまりMisoca APIでやりたいことがあるなら、リストAPIで per_page を大きめに取って一括取得し、クライアント側でフィルタする。これしかない。
まとめ:Misoca API v3で取引先を扱うときの鉄則
| やりたいこと | 方法 |
|---|---|
| 取引先の名前を取得 |
recipient_name を見る(name は存在しない) |
| 同名取引先の区別 |
recipient_name1 で確認 + contact_group_id 経由で過去の請求書を検索 |
| 取引先の詳細情報 | 個別APIは使えない。リストAPIで全件取得してフィルタ |
| 請求書の検索 |
contact_group_id でフィルタ(contact_id ではない) |
このハマりを経て自動化したもの
最終的に、GASのスプレッドシートメニューからボタン1つで以下が全自動で動くようになった:
- Gmailから取引先の明細メールを自動検索・金額パース
- プレビュー画面で確認(金額の自動計算・チャットメッセージのプレビュー付き)
- Misoca APIで請求書作成 → PDF取得
- スプレッドシートに記帳
- Gmailで請求書PDF添付の返信を自動送信
- Chatworkで関係者に報告
- Google Driveに請求書・明細書PDFを自動保存
月次の精算が15分 → 30秒に。人的ミスはゼロになった。請求書の金額間違い、メール送り忘れ、スプレッドシートの記入漏れ——全部消えた。
Misoca APIのドキュメントは正直つらい。ネット上にも情報がほとんどない。でもリストAPIさえ理解すれば、GASとの相性は抜群にいい。OAuth認証を一度通せば、あとは UrlFetchApp.fetch で何でもできる。
取引先IDの特定に半日使った。そのおかげで毎月15分の作業が消えた。投資回収は2ヶ月。悪くない。
使用技術: Google Apps Script / Misoca API v3 / OAuth2 for Apps Script
シリーズ:
- 第1弾: Claude CodeにMCPを10個繋いだら、ブラウザを開かなくなった
- 第2弾: CLAUDE.mdが634行になって関係が壊れた話
- 第3弾: 本記事