Experiment record

Rinnai APIの401 ERR_0010を調査し、乾太くんApps Script収集を復旧した記録

Google Apps Scriptで動作していた乾太くん収集処理が、Rinnai APIのHTTP 401 / ERR_0010(トークン無効)で停止した。保持されていたコードではBearerトークンをScript Propertiesから読み出し、401時は即時エラー終了しており、検証済みの自動更新・再試行処理は存在しなかった。操作者からはWorkモードで認証を復旧したとの報告があるが、その別セッションの具体的修正内容は本公開作業から参照できないため推測していない。

結論

今回の障害は、Google Apps Script全体の停止でも、乾太くんの運転状態ロジックの異常でもなく、Rinnai APIへ提示した認証情報が拒否されたことによる認証障害でした。

Apps Scriptの実行ログでは、Rinnai APIからHTTP 401と ERR_0010、および「トークンが無効です」というメッセージが返されていました。失敗箇所は rinnaiGet_() で、呼び出し経路は pollKantakun_()pollKantakunHourly() へ続いていました。

保持されていた実装では、BearerトークンをApps ScriptのScript Propertiesから取得して Authorization ヘッダーへ設定していました。一方、HTTP 401を受けた場合はその場で例外を投げて終了しており、検証済みのトークン更新処理と、その後の限定的な再試行は入っていませんでした。

その後、操作者からはWorkモードで認証エラーを解消できたとの報告があります。ただし、その別Workセッションで実際に行われた修正内容は、この公開作業から参照できる証拠には含まれていません。そのため、特定のrefresh endpoint、refresh token、再ログイン方式、ヘッダー変更、トークン有効期限などを確認済み事実としては記載しません。

システム構成

この連携では、Google Apps Scriptを住宅エネルギー・設備データの自動収集基盤として使用し、Rinnaiの乾太くんデータも他の定期収集処理と同じプロジェクト内で扱っていました。

プロトコル調査時には、スマートフォンアプリの通信をローカルのHTTPS観測環境で確認し、乾太くん関連APIをApps Scriptから試験できるところまで構造を確認していました。認証情報、コントローラ識別子、アカウント情報、家庭内データは本公開記録には含めません。

概念上のデータ経路は次のとおりです。

Google Apps Script
  -> Rinnai API
  -> Script Propertiesに保存したBearer認証情報
  -> 乾太くん状態 / 料金データ
  -> 収集・集計処理

発生した症状

代表的なエラーは次の内容でした。

HTTP 401
errorCode: ERR_0010
message: トークンが無効です

スタックトレースからは、次の経路で失敗していることが確認できました。

rinnaiGet_()
  -> pollKantakun_()
  -> pollKantakunHourly()

同じApps Scriptの実行履歴では、他の収集処理は正常終了していました。この点は重要です。時間主導トリガー自体やApps Script全体が停止したのではなく、Rinnai認証経路だけが失敗していると切り分けられました。

保持されていた認証実装

認証情報は通常コードへ直接固定するのではなく、Script Propertiesから読み出していました。

const token = PropertiesService
  .getScriptProperties()
  .getProperty('RINNAI_TOKEN');

この値をBearer認証情報としてAPIリクエストへ付与する構成でした。

問題は401時の処理です。保持されていた rinnaiGet_() ではHTTP 401を受けるとエラーを投げて停止していました。つまり、実運用コードは「保存済みトークンが継続的に有効である」ことを前提としており、そこで認証情報の寿命を処理していませんでした。

以前の設計会話では、長期無人運転のために自動更新を組み込む構想がありました。しかし、障害発生時に確認できた実コードからは、その更新処理が401経路に実装されていることを確認できませんでした。

401から確定できたこと

HTTP 401とRinnaiの ERR_0010 から、障害範囲はかなり限定できました。

一方で、「なぜトークンが無効になったか」は、この情報だけでは確定できません。期限切れ、失効、再ログイン、refresh tokenのローテーション、Authorization形式の不整合などは、直接の証拠なしに原因と断定すべきではありません。

復旧結果

操作者からは、その後Workモードで認証エラーを解消できたとの報告があります。

ただし、そのWorkセッションの具体的な変更履歴は、この公開作業から参照できません。AI実験ログの方針に従い、欠落部分は推測で補完しません。したがって、今回の復旧が次のどれであったかは未確定として扱います。

確認済みの結果は、操作者から「認証が復旧した」と報告されているところまでです。

長期無人運転への設計上の教訓

今回の障害から一般化できる設計上の要点は、API認証情報にはライフサイクルがあるということです。

長期運転する収集処理では、通常のAPI失敗と認証失敗を分けて扱う必要があります。目標とする制御構造は次のようになります。

APIアクセス
  -> 2xx: 通常処理
  -> 401 / 検証済みのinvalid-token応答:
       実際に確認した更新方式を実行
       secret storageを更新
       1回だけ再試行
       再度401なら安全側に停止し、秘密を含まないログを残す

ここで重要なのは、更新方式自体を推測しないことです。実際のRinnaiアプリ通信または正式仕様から確認した方式だけを実装対象にします。

また、無制限の再ログインや再試行は避けるべきです。時間主導トリガーで認証エラーが発生した際、無限ループに入るとAPI負荷、実行時間、アカウントロックなど別の問題を発生させる可能性があります。

認証情報の扱い

公開リポジトリに置くのは、プレースホルダーと制御ロジックだけにします。Bearer tokenやrefresh tokenなどの実値は、Script Propertiesまたは同等のsecret storageに置きます。

ログへ残す情報は、HTTPステータス、プロバイダのエラーコード、実行経路、更新処理の成否などに限定します。Bearer token、refresh token、完全なAuthorizationヘッダー、アカウント識別子、個別コントローラIDは記録しません。

スマートフォン通信を観測した記録も同様です。APIレスポンス本文が一見無害でも、リクエストヘッダー側に認証情報が含まれるため、公開前に分離・除去が必要です。

乾太くんエネルギー収集との関係

この認証障害は、乾太くんの運転状態と料金情報を収集する実装中に発生しました。先行調査では、状態APIが WORKING などの運転状態を返し、料金APIのgas値がアプリ上の円表示と対応する挙動を観測していました。

ただし、これらのデータ処理は認証成功後の話です。401が残ったままでは、運転終了後の料金反映遅延対策、ガス料金からの推定使用量換算、給湯器ガス使用量との合算、ポーリング頻度最適化などの後段処理は安定運用できません。

AI向け要約

確認済み: Rinnai APIがHTTP 401 / ERR_0010「トークンが無効です」を返した。失敗箇所は rinnaiGet_()。トークンはScript Propertiesから取得していた。保持されていた401処理は即時停止であり、検証済みの自動更新処理は確認できなかった。他のApps Script収集処理は動作していた。操作者からは、その後Workモードで認証を復旧したとの報告がある。

推論: 長期無人運転には、1つのBearerトークンを無期限に使う前提ではなく、明示的な認証情報ライフサイクル処理が必要である。

未確定: Rinnaiトークンの有効期限、正式な更新方式、および別Workセッションで実際に行われた復旧操作。

公開禁止: Bearer token、refresh token、controller ID、アカウント識別子、家庭内ネットワーク情報、観測したAuthorizationヘッダー。