【Google Chat API】「スペースの過去ログを取りたいだけなのに」403エラーと戦い続けた泥沼の記録と、最終的な正解
はじめに
社内のGoogle Chat(スペース)に蓄積された会話データを取得し、AIで解析したいというニーズがあり、API連携の実装を試みました。
「APIを叩くだけなら簡単だろう」と高を括っていましたが、認証周りの仕様、特に「Botとしての権限」と「ユーザーなりすまし」の違いで泥沼にはまりました。
PHP(レンタルサーバー)での実装から始まり、最終的にPython(VPS)+ドメイン全体の委任で解決に至った経緯と、動くコードを共有します。
結論:何が正解だったか
先に結論を書くと、組織内のチャットログを自由に取得するには、以下の構成が必須でした。
- 単純なBot参加ではダメ: Service Accountを作るだけでは、Botが招待されていない過去ログなどは取得できない(403エラーになる)。
- 「ドメイン全体の委任」が必要: Google Workspace管理コンソールで、そのService Accountに「ドメイン内のユーザーになりすます権限」を与える必要がある。
- Pythonライブラリが強い: PHPでCURLを頑張るより、公式ライブラリが充実しているPython(google-auth, google-api-python-client)を使う方が圧倒的に楽。
苦戦の経緯(ハマりポイント)
1. PHP × Service Account の限界
最初はレンタルサーバー(PHP)で実装を試みました。GCPでService Accountを作成し、JSONキーを発行して認証させましたが、以下のエラーが頻発しました。
{
"error": {
"code": 403,
"message": "Request had insufficient authentication scopes.",
"status": "PERMISSION\_DENIED",
"details": \[
{
"reason": "ACCESS\_TOKEN\_SCOPE\_INSUFFICIENT"
}
\]
}
}
Bot用のスコープ https://www.googleapis.com/auth/chat.bot だけを指定しても、人間用の読み取りスコープ chat.messages.readonly を混ぜてもNG。
Google Chat APIの仕様として、**「Botとして振る舞うなら、人間用の読み取りAPIは使わせない(Botが参加している部屋しか見せない)」**という強い制約があるようでした。
2. ライブラリのバージョン問題
PHPの google/apiclient は更新頻度は高いものの、Chat API特有のクラス(Google\Service\Chat)が環境によっては見つからなかったり、古い HangoutsChat クラスが混在していたりと、環境構築だけで時間を浪費しました。
解決策:Python × ドメイン全体の委任
手順1:GCP側での準備
- Google Cloud Consoleでプロジェクトを作成し、「Google Chat API」を有効化。
- IAMと管理 > サービスアカウントを作成。
- キー(JSON)を作成してダウンロード。
- 【重要】 JSONファイル内の client_id (数字の羅列)をコピーしておく。
手順2:Google Workspace 管理コンソールでの設定
ここが最大の山場です。API経由でログを吸い出すには、特権管理者による**「ドメイン全体の委任(Domain-Wide Delegation)」**設定が必須でした。
- Google Workspace 管理コンソールへアクセス。
- セキュリティ > アクセスとデータ管理 > APIの制御 > ドメイン全体の委任を管理。
- 「新しく追加」をクリック。
- クライアントID: 手順1でコピーしたJSON内の数字
- OAuthスコープ: https://www.googleapis.com/auth/chat.messages.readonly
- 承認する。
手順3:Pythonコードの実装
Flaskを使って、指定したスペースのログを取得するAPIサーバーを構築しました。
ポイントは with_subject() メソッドを使って、管理者やメンバーのメールアドレスになりすますことです。
flask
google-auth
google-api-python-client
import os
from datetime import datetime, timedelta, timezone
from flask import Flask, jsonify, request
from google.oauth2 import service\_account
from googleapiclient.discovery import build
app \= Flask(\_\_name\_\_)
\# 設定項目
KEY\_FILE\_PATH \= '/path/to/service-account-key.json' \# ダウンロードしたJSON
IMPERSONATE\_USER \= 'admin@example.co.jp' \# 【重要】なりすますユーザーのメアド
SCOPES \= \['\[https://www.googleapis.com/auth/chat.messages.readonly\](https://www.googleapis.com/auth/chat.messages.readonly)'\]
@app.route('/api/chat\_log', methods=\['GET'\])
def get\_chat\_log():
target\_space \= request.args.get('space') \# 例: spaces/AAAAxxxx
days\_param \= request.args.get('days') \# 例: 3 (過去3日分)
if not target\_space:
return jsonify({"error": "Missing space parameter"}), 400
try:
\# 1\. 認証情報の作成
creds \= service\_account.Credentials.from\_service\_account\_file(
KEY\_FILE\_PATH, scopes=SCOPES
)
\# 2\. 【ここが魔法の1行】ユーザーになりすます
delegated\_creds \= creds.with\_subject(IMPERSONATE\_USER)
\# 3\. クライアント構築
service \= build('chat', 'v1', credentials=delegated\_creds)
\# 4\. 取得ロジック(ページネーション対応)
all\_messages \= \[\]
page\_token \= None
cutoff\_time \= None
if days\_param:
cutoff\_time \= datetime.now(timezone.utc) \- timedelta(days=int(days\_param))
while True:
result \= service.spaces().messages().list(
parent=target\_space,
orderBy='createTime desc',
pageSize=100,
pageToken=page\_token
).execute()
messages \= result.get('messages', \[\])
if not messages: break
for msg in messages:
\# 日付フィルタなど必要な処理を記述
\# ...
all\_messages.append(msg)
page\_token \= result.get('nextPageToken')
if not page\_token: break
return jsonify({"count": len(all\_messages), "messages": all\_messages})
except Exception as e:
return jsonify({"error": str(e)}), 500
if \_\_name\_\_ \== '\_\_main\_\_':
app.run(port=5000)
実行結果
このAPIに対して、以下のようにリクエストを送ります。
GET /api/chat_log?space=spaces/AAAAxxxxx&days=3
すると、Botを招待していないスペースであっても、指定したユーザー(IMPERSONATE_USER)が参加しているスペースであれば、無事にJSONでログが返ってきました!
{
"count": 18,
"messages": \[
{
"sender": "Unknown",
"text": "【日報未提出 通知】...",
"time": "2025-12-11T00:00:09.789871Z"
},
...
\]
}
※BotやWebhookからのメッセージの場合、sender情報の取得には制限があるようですが、本文(text)は問題なく取得できています。
まとめ
- Google Chat APIでログ収集などの「管理・分析」をしたいなら、Bot権限で頑張るより「ドメイン全体の委任」を使うのが正解。
- PHPよりもPythonの方が、Google系APIのライブラリサポートが手厚く、AI連携(Gemini/OpenAI)への接続もスムーズ。
- 403エラーが出たら、コードを疑う前に「管理コンソール」と「IAM」の設定を見直すべき。特にWorkspaceの管理コンソール側は盲点
同じような悩みを持つ方の参考になれば幸いです。