[{"data":1,"prerenderedAt":453},["ShallowReactive",2],{"content-/mf-journal-export-api":3,"all-pages-for-dir":451,"og-image-/mf-journal-export-api":452},{"id":4,"title":5,"body":6,"category":431,"description":432,"extension":433,"meta":434,"navigation":134,"ogImage":435,"path":436,"project_name":437,"published":438,"publishedAt":439,"seo":440,"stem":441,"tags":442,"todo":449,"unpublished":438,"updatedAt":435,"__hash__":450},"pages/2026-03/2026-03-23/mf-journal-export-api.md","会計サービス仕訳帳エクスポートのAPI調査と実装 - REST API・サービス形式CSV・ハイブリッド方式の試行錯誤",{"type":7,"value":8,"toc":417},"minimark",[9,14,18,21,26,34,39,42,70,85,159,162,166,177,179,183,186,194,197,200,203,206,299,303,311,321,323,327,330,335,347,350,352,355,358,361,363,366,376,379,399,402,404,407,410,413],[10,11,13],"h1",{"id":12},"会計サービス仕訳帳エクスポートのapi調査と実装","会計サービス仕訳帳エクスポートのAPI調査と実装",[15,16,17],"p",{},"Chrome拡張の会計サービス連携に仕訳帳エクスポートを追加した。REST APIでJSON→CSV変換する方式で着手し、途中でサービス形式CSVエクスポートAPIを見つけ、最終的に両者を組み合わせるハイブリッド方式に着地した。方針が二転三転した一日の記録。",[19,20],"hr",{},[22,23,25],"h2",{"id":24},"第1フェーズ-rest-apiでjson取得csv変換","第1フェーズ: REST APIでJSON取得→CSV変換",[15,27,28,29,33],{},"最初に目をつけたのは ",[30,31,32],"code",{},"/api/v1/journals"," エンドポイント。仕訳データがJSONで返ってくるので、クライアントサイドでサービス形式のCSV列構成に組み替える方針で実装を始めた。",[35,36,38],"h3",{"id":37},"マスタapiの構造調査","マスタAPIの構造調査",[15,40,41],{},"仕訳の勘定科目名や補助科目名を解決するために、マスタAPIを叩く必要がある。調査したエンドポイントは4つ。",[43,44,45,52,58,64],"ul",{},[46,47,48,51],"li",{},[30,49,50],{},"categorized_items"," — 勘定科目",[46,53,54,57],{},[30,55,56],{},"sub_categories"," — 補助科目",[46,59,60,63],{},[30,61,62],{},"walletables"," — 口座",[46,65,66,69],{},[30,67,68],{},"departments"," — 部門",[15,71,72,73,76,77,80,81,84],{},"ここで最初につまずいた。マスタのアイテムが持つプロパティ名が ",[30,74,75],{},"name"," ではなく ",[30,78,79],{},"label"," だった。DevToolsのNetworkタブでレスポンスを確認するまで気づかず、科目名が全て ",[30,82,83],{},"undefined"," で出力されていた。",[86,87,92],"pre",{"className":88,"code":89,"language":90,"meta":91,"style":91},"language-javascript shiki shiki-themes vitesse-light vitesse-light","// NG: nameプロパティは存在しない\nconst name = item.name;\n\n// OK: labelが会計サービスのマスタAPIでの正しいプロパティ名\nconst name = item.label;\n","javascript","",[30,93,94,103,129,136,142],{"__ignoreMap":91},[95,96,99],"span",{"class":97,"line":98},"line",1,[95,100,102],{"class":101},"sxvE3","// NG: nameプロパティは存在しない\n",[95,104,106,110,114,118,121,124,126],{"class":97,"line":105},2,[95,107,109],{"class":108},"stQ0i","const",[95,111,113],{"class":112},"s4oTP"," name",[95,115,117],{"class":116},"shFtX"," =",[95,119,120],{"class":112}," item",[95,122,123],{"class":116},".",[95,125,75],{"class":112},[95,127,128],{"class":116},";\n",[95,130,132],{"class":97,"line":131},3,[95,133,135],{"emptyLinePlaceholder":134},true,"\n",[95,137,139],{"class":97,"line":138},4,[95,140,141],{"class":101},"// OK: labelが会計サービスのマスタAPIでの正しいプロパティ名\n",[95,143,145,147,149,151,153,155,157],{"class":97,"line":144},5,[95,146,109],{"class":108},[95,148,113],{"class":112},[95,150,117],{"class":116},[95,152,120],{"class":112},[95,154,123],{"class":116},[95,156,79],{"class":112},[95,158,128],{"class":116},[15,160,161],{},"IDからマスタ名を引くルックアップMapを作り、仕訳JSONの各行を走査してCSV行に変換するところまで動いた。",[35,163,165],{"id":164},"壁-作成者最終更新者が取れない","壁: 作成者・最終更新者が取れない",[15,167,168,169,172,173,176],{},"REST APIのレスポンスをよく見ると、",[30,170,171],{},"created_by"," や ",[30,174,175],{},"updated_by"," に相当するフィールドが存在しない。会計サービスの仕訳帳CSV（管理画面からダウンロードできるもの）には「作成者」「最終更新者」列があるが、REST APIはこれを返さない。税額の計算ロジックもクライアント側で再現する必要があり、再実装コストが膨らみ始めた。",[19,178],{},[22,180,182],{"id":181},"第2フェーズ-サービス形式csvエクスポートapiの発見","第2フェーズ: サービス形式CSVエクスポートAPIの発見",[15,184,185],{},"REST APIに限界を感じてDevToolsのNetworkタブを眺めていたら、管理画面の「エクスポート」ボタンが叩いているAPIを捕捉した。",[86,187,192],{"className":188,"code":190,"language":191},[189],"language-text","POST /exports/mf_journals_csv_file\n  → 302 リダイレクト\n  → GET /storage/files/{fid}\n  → CSV本体がダウンロードされる\n","text",[30,193,190],{"__ignoreMap":91},[15,195,196],{},"会計サービスのサーバーサイドでCSVを生成するAPIが存在していた。これを使えば、作成者・最終更新者・税額など、REST APIでは取得できなかったフィールドが全て含まれる。列構成も会計サービスの管理画面と完全に一致する。",[15,198,199],{},"REST API方式をいったん脇に置き、このCSVエクスポートAPIに乗り換えた。",[35,201,202],{"id":202},"エンコーディングの罠",[15,204,205],{},"CSVをダウンロードして中身を確認したら文字化けしていた。原因はエンコーディングの誤判定。会計サービスのCSVはUTF-8で出力されるが、デコード処理がShift-JISを前提にしていた。",[86,207,209],{"className":88,"code":208,"language":90,"meta":91,"style":91},"// NG: Shift-JISでデコードすると文字化け\nconst text = new TextDecoder('shift-jis').decode(buffer);\n\n// OK: 会計サービスのCSVはUTF-8\nconst text = new TextDecoder('utf-8').decode(buffer);\n",[30,210,211,216,259,263,268],{"__ignoreMap":91},[95,212,213],{"class":97,"line":98},[95,214,215],{"class":101},"// NG: Shift-JISでデコードすると文字化け\n",[95,217,218,220,223,225,228,232,235,239,243,245,248,251,253,256],{"class":97,"line":105},[95,219,109],{"class":108},[95,221,222],{"class":112}," text",[95,224,117],{"class":116},[95,226,227],{"class":108}," new",[95,229,231],{"class":230},"senZ8"," TextDecoder",[95,233,234],{"class":116},"(",[95,236,238],{"class":237},"sMJiu","'",[95,240,242],{"class":241},"sdGka","shift-jis",[95,244,238],{"class":237},[95,246,247],{"class":116},").",[95,249,250],{"class":230},"decode",[95,252,234],{"class":116},[95,254,255],{"class":112},"buffer",[95,257,258],{"class":116},");\n",[95,260,261],{"class":97,"line":131},[95,262,135],{"emptyLinePlaceholder":134},[95,264,265],{"class":97,"line":138},[95,266,267],{"class":101},"// OK: 会計サービスのCSVはUTF-8\n",[95,269,270,272,274,276,278,280,282,284,287,289,291,293,295,297],{"class":97,"line":144},[95,271,109],{"class":108},[95,273,222],{"class":112},[95,275,117],{"class":116},[95,277,227],{"class":108},[95,279,231],{"class":230},[95,281,234],{"class":116},[95,283,238],{"class":237},[95,285,286],{"class":241},"utf-8",[95,288,238],{"class":237},[95,290,247],{"class":116},[95,292,250],{"class":230},[95,294,234],{"class":116},[95,296,255],{"class":112},[95,298,258],{"class":116},[35,300,302],{"id":301},"もう一つの壁-開始仕訳が含まれない","もう一つの壁: 開始仕訳が含まれない",[15,304,305,306,310],{},"サービス形式CSVを年度分ダウンロードして行数を確認したら、REST APIで取得した仕訳数より少ない。調べてみると、会計サービスのサーバーサイドCSV生成は",[307,308,309],"strong",{},"開始仕訳を除外する仕様","だった。開始仕訳（期首の残高設定）は通常の仕訳と異なる扱いらしく、エクスポート対象から外される。",[15,312,313,314,316,317,320],{},"管理画面の仕訳帳で開始仕訳を表示させても、エクスポートボタンの対象にはならない。REST APIの ",[30,315,32],{}," では ",[30,318,319],{},"journal_type"," パラメータで開始仕訳を指定すれば取得できる。",[19,322],{},[22,324,326],{"id":325},"第3フェーズ-ハイブリッド方式の採用","第3フェーズ: ハイブリッド方式の採用",[15,328,329],{},"サービス形式CSVだけでは開始仕訳が欠ける。REST APIだけでは作成者・税額が欠ける。どちらか一方では完全なデータが揃わない。そこで両者を組み合わせるハイブリッド方式を採用した。",[15,331,332],{},[307,333,334],{},"方針:",[336,337,338,341,344],"ol",{},[46,339,340],{},"サービス形式CSVをダウンロード（作成者・税額・本来の列構成を確保）",[46,342,343],{},"REST APIから開始仕訳のみ取得してCSV行に変換",[46,345,346],{},"両者をマージして出力",[15,348,349],{},"開始仕訳は年度に数件しかないため、REST APIの呼び出し回数は最小限で済む。サービス形式CSVが持つ情報量とREST APIの網羅性を両立できた。",[19,351],{},[22,353,354],{"id":354},"年度別一括エクスポート対応",[15,356,357],{},"仕訳帳は年度単位で管理される。複数年度をまとめてエクスポートできるよう、仕訳帳専用の年度選択UIを追加した。",[15,359,360],{},"既存の明細取得で使っていた年度選択チップのコンポーネントを流用し、仕訳帳エクスポート用にインスタンスを分離した。選択された年度ごとにサービス形式CSV取得→開始仕訳補完→マージの処理が走る。",[19,362],{},[22,364,365],{"id":365},"純粋関数への分離とテスト",[15,367,368,369,372,373,375],{},"CSV列の組み立て、マスタIDの解決、開始仕訳のCSV行変換といったロジックを ",[30,370,371],{},"lib.js"," に切り出した。副作用（API呼び出し、DOM操作）はハンドラ層に残し、",[30,374,371],{}," の関数は全て入力→出力が確定する純粋関数にした。",[15,377,378],{},"テストは39件追加。主なカバー範囲は以下の通り。",[43,380,381,387,390,393,396],{},[46,382,383,384,386],{},"マスタMapの構築（",[30,385,50],{}," → IDから科目名を引く）",[46,388,389],{},"仕訳JSONからCSV行への変換（借方・貸方の展開）",[46,391,392],{},"開始仕訳のCSV行変換",[46,394,395],{},"サービス形式CSVと開始仕訳CSVのマージ",[46,397,398],{},"エッジケース（補助科目なし、部門なし、税区分なし）",[15,400,401],{},"テストが手元で通る状態を維持しながらリファクタリングを進められたので、ハイブリッド方式への切り替え時も既存ロジックを壊さずに済んだ。",[19,403],{},[22,405,406],{"id":406},"振り返り",[15,408,409],{},"方針が3回変わった。REST API→サービス形式CSV→ハイブリッドと、調査が進むたびに「これだけでは足りない」が見つかった。最初から会計サービスの内部APIを全て把握していれば一直線に進めたが、DevToolsでリクエストを一つずつ追いかけながら全体像を掴んでいく過程自体が、APIの仕様を理解する最短ルートだったとも思う。",[15,411,412],{},"ロジックを純粋関数に閉じ込めてテストを書いておいたことで、方針転換のたびにゼロから書き直す事態は避けられた。39件のテストが、リファクタリング中に何度も壊れかけた箇所を即座に教えてくれた。",[414,415,416],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}",{"title":91,"searchDepth":105,"depth":105,"links":418},[419,423,427,428,429,430],{"id":24,"depth":105,"text":25,"children":420},[421,422],{"id":37,"depth":131,"text":38},{"id":164,"depth":131,"text":165},{"id":181,"depth":105,"text":182,"children":424},[425,426],{"id":202,"depth":131,"text":202},{"id":301,"depth":131,"text":302},{"id":325,"depth":105,"text":326},{"id":354,"depth":105,"text":354},{"id":365,"depth":105,"text":365},{"id":406,"depth":105,"text":406},"dev","Chrome拡張からクラウド会計サービスの仕訳帳をエクスポートする機能を実装。REST APIで始めてサービス形式CSVを発見し、最終的にハイブリッド方式に落ち着くまでの過程を記録","md",{},null,"/mf-journal-export-api","misc-dev",false,"2026-03-23T00:00:00.000Z",{"title":5,"description":432},"2026-03/2026-03-23/mf-journal-export-api",[443,444,445,446,447,448],"クラウド会計","Chrome拡張機能","REST API","CSV","仕訳帳","会計","memo","Hda4msKERkbH2NzkF4GqLkr2UfNgVfcqc3fWcFnB20g",[],"https://log.eurekapu.com/og/blog/mf-journal-export-api.png?v=2026-03-23T00%3A00%3A00.000Z&title=%E4%BC%9A%E8%A8%88%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E4%BB%95%E8%A8%B3%E5%B8%B3%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AEAPI%E8%AA%BF%E6%9F%BB%E3%81%A8%E5%AE%9F%E8%A3%85%20-%20REST%20API%E3%83%BB%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E5%BD%A2%E5%BC%8FCSV%E3%83%BB%E3%83%8F%E3%82%A4%E3%83%96%E3%83%AA%E3%83%83%E3%83%89%E6%96%B9%E5%BC%8F%E3%81%AE%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4&author=Kei%20Komatsu&sig=c354bcabc51a1f68",1782528820256]