Zaim APIでできることを提供終了前に実測した|ドキュメントとの差

「Zaim API」「Tested Before Shutdown」の文字と、ドキュメント(Docs)と実測(Actual)を左右で対比したイラスト データパイプライン

はじめに

Zaim API と Zaim Developers Center は 2026-12-07 に提供終了します。ドキュメントと実際の動きの差は終了後に確かめられなくなるため、消える前に読み取り系と書き込み系のエンドポイントを叩いて記録しました。結論として、手入力した家計簿データと一覧情報は取得できますが、ドキュメントどおりに動かない箇所が複数あります。

Zaim API は、家計簿アプリ Zaim のデータを外部のプログラムから読み書きするための API です。公式のお知らせによると、新規アプリの登録は 2026-11-09 に締め切られ、後継の API や代替手段は用意されません。今から新しく使い始める人に向けた手順ではなく、検証結果の記録として読んでください。

本記事のまとめ

  • 取得できるのは手入力の家計簿データと、カテゴリ・ジャンル・口座・通貨の一覧である。口座やカードの連携データは取得できない。
  • limit と page は無視され、1 回のリクエストで条件に合う全件が返る。
  • date=2026/11/01 を送ると、エラーなしで 2025-11-30 として保存された。
  • BigQuery へのロードは通るが、レコードの種類に関係しない ID の列は NULL ではなく 0 で入るので、絞り込みの条件に注意が要る。
Zaim 開発センターのトップ画面。上部に 2026年11月9日の追加受付停止と 2026年12月7日の提供終了の案内が表示されている
Zaim Developers Center のトップ。新規登録の停止と提供終了の日程が案内されている

Zaim API でできること・できないこと

Zaim API でできるのは、手入力した家計簿レコードの読み書きと、分類や口座の一覧の取得です。読み取り系の 9 本は、実際に叩いてすべて 200 が返りました。表の mode は、支出(payment)、収入(income)、振替(transfer)の区分です。

やりたいこと可否補足
手入力の家計簿レコードを取得するできるGET /v2/home/money
支出・収入・振替を登録、更新、削除するできる(登録は支出と収入、更新は支出、削除は収入で確認。振替を含むほかの組み合わせは未確認)mode を含むパスで通る
カテゴリ、ジャンル、口座、通貨の一覧を取得するできるドキュメント上、既定の一覧は認証なしで取得できる
口座やカードの連携データを取得するできないドキュメント上も手入力のデータだけが対象
残高を取得するできない該当するエンドポイントが無い
口座、カテゴリ、ジャンルを作成するできない該当するエンドポイントが無い

私の家計簿の件数の変化も、これと合っています。連携中心の運用に切り替えた時期を境に、API で取得できる件数が大きく減りました。

アプリの登録に審査はなく、フォームを保存するとその場で登録されました。ローカルのスクリプトから試すため、サービス種は「クライアントアプリ」を選んでいます。ドキュメントによると、アクセスレベルの「永続的に許可する」を付けないと権限は 24 時間で失効します。検証では 3 つとも付けました。認証の仕組みは OAuth 1.0a で、詳しい流れはOAuth 1.0a と 2.0 を実装で比べた記事で扱います。

未入力のアプリケーション登録フォーム。サービス種はブラウザアプリとクライアントアプリの二択で、アクセスレベルのチェックボックスが 3 つある
アプリの登録フォーム。サービス種とアクセスレベルをここで選ぶ

保存すると、次のようにアプリが一覧に追加されました。

「登録が完了しました。」の表示と、登録済みのアプリケーション一覧に zaim-api-verification が並んだ画面
保存するとその場で登録され、一覧に表示された。既存の別アプリの行は取り除いた

登録した内容は次のとおりです。

zaim-api-verification の設定画面。サービス種はクライアントアプリで、アクセスレベルは読み込み、書き込み、永続的な許可の 3 つすべてにチェックが入っている
登録したアプリの設定。クライアントアプリで、アクセスレベルを 3 つとも付けた

ドキュメントに書いてあるのに効かないパラメータ

最も影響が大きいのは、limit と page が効かず、ページングの仕組みが実際には無いことです。Zaim Developers Center の API ドキュメント(2018-07-04 が最終更新)では、GET /v2/home/money のパラメータとして次のように説明されています。

page: number of current page (default 1)
limit: number of items per page (default 20, max 100)

この記述なら、未指定で 20 件、limit=5 で 5 件が返り、page=2 で次のページへ進むはずです。実際には、どの指定でも同じ件数が返りました。ほかのパラメータも含め、記述と実際の動きが違ったものは次のとおりです。

パラメータドキュメント実測の結果
mapping(GET)必須付けなくても 200 で、結果も同じ
limit既定 20、最大 1005、100、101、未指定のどれでも同じ件数
page既定 1page=2 でも同じ件数
mapping(PUT)必須外しても 200
id(DELETE)必須外しても 200。パスに含まれている
date(income)過去 3 か月5 年 10 か月前も 3 年以上先も 200

一方で、絞り込みは正しく動きます。全件を手元で集計し、API の結果と突き合わせたところ、mode、category_id、genre_id の件数はすべて一致しました。start_date と end_date、order、group_by も効いています。

取得処理は、期間で絞って 1 回呼ぶだけで足ります。検証で使ったコードは次のとおりです。署名を作る authorization_header の中身は省略しています。

# limit / page は API に無視され、1 リクエストで全件返るのでページングしない。
# mapping も no-op だが、ドキュメントが必須としているので送る。
params = {"mapping": "1", "start_date": start_date, "end_date": end_date}
req = urllib.request.Request(f"{MONEY_URL}?{urllib.parse.urlencode(params)}")
req.add_header("Authorization", authorization_header(MONEY_URL, params, cred))
with urllib.request.urlopen(req, timeout=60) as res:
    return json.load(res)["money"]

ドキュメントに無いのに使えるもの

逆に、ドキュメントに記載が無くても動く項目が 4 種類ありました。

  • GET /v2/home/money の account_id は、口座で正しく絞り込める。手元の集計と件数が一致した。
  • POST の receipt_id は入力できる。999999 を送ると、そのまま保存された。
  • PUT の name と place は、パラメータの一覧に無いが更新に反映される。
  • レスポンスに未記載のキーがある。active と created(ユーザー情報)、place_uid(家計簿レコード)、original_money_ids(振替のレコード)の 4 つである。

未記載の項目は、提供元が動作を約束していないものです。検証の記録としては残しますが、仕様として頼るものではありません。なお、20 回の連続リクエストでレート制限は観測されませんでした。上限が無いという意味ではなく、この回数では出なかったという結果です。

エラーにならず別の日付で保存される

この検証で最も注意が必要なのは、日付の形式が違っても拒否されず、別の日付で保存されることです。1 円のテストレコードで試した結果は次のとおりです。

POST /v2/home/money/income に date=2026/11/01 を送信
-> 200
-> 保存された日付は 2025-11-30

原因は確認できていません。月と日が 0 として扱われると計算上はこの日付になりますが、API の内部は見えないため、推測にとどまります。

200 が返るので、呼び出し側は失敗に気づけません。登録する前に、自分のコードで YYYY-MM-DD の形式を確かめる必要があります。取得するときも範囲外の日付が混ざらないかを確かめるため、検証のスクリプトには次の処理を入れました。

# API は日付の範囲を検証しないので、範囲外が混ざっていないかをここで確かめる。
outside = [r for r in records if not args.start_date <= r["date"] <= args.end_date]
if outside:
    print(f"エラー: 範囲外の日付が {len(outside)} 件ある", file=sys.stderr)
    return 1

もう 1 つ、エラーの内容が原因と合わない例があります。ドキュメントの索引には更新と削除のパスが /v2/home/money/:id と載っていますが、ここを呼ぶと PUT は 404、DELETE は 401 を返しました。401 の本文は次のとおりで、権限の不足に見えます。

This consumer key does not have a permission for the action.

正しいパスは、ドキュメント本文にある /v2/home/money/payment/:id のように mode を含む形です。こちらは 200 で通りました。この 401 が出たら、権限を調べる前にパスを確かめてください。

BigQuery へロードして分かったこと

取得したレコードは BigQuery へそのままロードできますが、mode に関係しない ID の列は NULL ではなく 0 で入ります。分析できる形になるかを確かめるため、取得結果を JSON Lines(1 行に 1 レコードの JSON)で Cloud Storage へ置き、スキーマの自動検出でロードしました。

bq --project_id=PROJECT_ID --location=asia-northeast1 load \
  --replace --autodetect --source_format=NEWLINE_DELIMITED_JSON \
  DATASET.money gs://BUCKET_NAME/money/money.jsonl

ロード後の件数は API の返り件数と一致しました。スキーマの自動検出は 18 列を作りました。date は DATE、created は TIMESTAMP、振替にしか無い original_money_ids は INTEGER REPEATED と判定されています。

BigQuery のスキーマ画面。original_money_ids が INTEGER REPEATED、created が TIMESTAMP、date が DATE と表示された 18 列の一覧
スキーマの自動検出で作られた 18 列。プロジェクト名が写る上部は切り落とした

0 で埋まる列は mode によって次のように分かれました。ロードした期間だけの結果で、全期間では確認していません。

mode0 で埋まる列
paymentfrom_account_id、to_account_id
incomefrom_account_id、to_account_id、多くの genre_id
transfercategory_id、genre_id

これらの列を IS NULL で絞っても、該当なしのレコードは見つかりません。0 と比較してください。

検証後はアプリとデータを削除した

検証を終えたあと、登録したアプリを削除すると、access token は使えなくなりました。削除後に GET /v2/home/user/verify を呼ぶと、401 が返っています。

「削除しました。」の表示と、「まだ登録したアプリケーションはありません。」と書かれた登録済みアプリケーションの一覧
アプリを削除したあとの一覧。登録したアプリは 0 件になった

公式のお知らせでは、API で取得して保存したデータと認証情報の削除期限が 2027-01-07 とされています。アプリのほかに、検証で作ったものを次のように片付けました。

  • テストレコードは削除し、全件を取得し直して残っていないことを確かめた。
  • BigQuery のデータセットと Cloud Storage のバケットを削除した。どちらも削除後 7 日間は復元できる状態が残り、BigQuery にはさらに Google 側の保持期間が 7 日ある。
  • 手元に保存した認証情報と取得データを削除した。

まとめ

Zaim API で確実にできるのは、手入力の家計簿レコードと一覧の取得で、ページングは要りません。実測で分かった注意点は次のとおりです。

  • 連携データと残高は取得できない。
  • 日付は送る前に形式を確かめる。不正な形式でも 200 が返り、別の日付で保存される。
  • BigQuery では、mode に関係しない ID の列を 0 との比較で判定する。

API を使っている場合は、提供終了までにデータの扱いを確認してください。提供の終了は順次進むとされているので、日程と手順は最新のお知らせで確認してください。