iPhone のヘルスケアの体重と体脂肪率を、ショートカットで毎朝 Cloud Run の受け口に送り、BigQuery に入れています(全体の構成はGarmin・Plaud・iPhone のライフログを BigQuery に自動で集めた:Cloud Run と手元の Mac を使い分けた設計)。この記事では、ショートカットの設定と、つまずいたところを紹介しようかと思います。
- 送るのは「最新の1件」だけです。 受け口が同じキーで BigQuery に MERGE するので、送り直しても重複しません
- つまずいたのは、ほとんどがショートカット側でした。 ショートカットを直すより、受け口の側で受け取れる形を広げたほうが楽でした
- Cloud Run では、
/healthzが 404 になる、作る順番で失敗する、の2つでつまずきました
ショートカットの設定
トークンは長いので、Mac でコピーしてユニバーサルクリップボードで iPhone に貼りました。
アクションを上から3つ並べます(名前は「lifelog ヘルスケア送信」)。
- ヘルスケアサンプルを検索
- 種類:体重(Weight)、単位:kg
- 開始日:過去 3650 日以内(実質、全期間)
- 並び順:開始日、新しい順。制限:1項目
- ヘルスケアサンプルを検索(2つ目)
- 種類:体脂肪率(Body Fat Percentage)、単位:%。ほかは 1 と同じ
- URL の内容を取得
- URL:
<受け口の URL>/v1/health(変数ではなく文字で入力) - 方法:POST
- ヘッダ:
AuthorizationにBearer <トークン> - 本文を要求:JSON で、次の3つのフィールドを足す

| キー | 値 |
|---|---|
weight_body_mass |
1 の「ヘルスケアサンプル」 |
body_fat_percentage |
2 の「ヘルスケアサンプル」 |
date |
「ヘルスケアサンプル」の「開始日」 |

フォームにキーと変数を並べるだけなので、JSON を手で書く必要はありません。1つ目と2つ目の「ヘルスケアサンプル」は名前が同じなので、そのアクションの直下に出ているものを選びます。
▶ で実行して {"rows":2} と出れば成功です。初回は、ヘルスケアの読み取りの許可を聞かれます。
毎朝動かすには、オートメーション →「+」→「時刻」(私は毎日 9:00)→「lifelog ヘルスケア送信」を選び、「すぐに実行」にします。確認ありのままだと、毎朝通知をタップしないと送られません。

「今日の分」ではなく「最新の1件」を送る

「今日の分」を探すと、測らなかった日に何も見つからず、ショートカットに「なければ送らない」の分岐が要ります。「最新の1件」なら、毎日同じ動きで済みます。
受け口は BigQuery に MERGE(同じキーの行は上書き、なければ追加)で書き込んでいて、キーは(指標名, 計測時刻)です。測らなかった日は前回と同じサンプルが届きますが、同じ行が上書きされるだけです。
受け取った JSON は、BigQuery に書く前に GCS に置いています。書き込みに失敗しても、元の JSON から入れ直せます。
この方法では、測定の履歴をすべて集めることはできません。1日に何回か測っても、送られるのは最後の1件だけです。毎朝の最新の値を見る用途に割り切っています。
つまずいたところ
| 起きたこと | 原因 | どう直したか |
|---|---|---|
| 何度も 400 が返る | ショートカットの中で JSON の文字列を手で組み立てていた | 平らな形を受け付け、フォームだけで組めるようにした |
| 日付が読めず 400 | 「日付をフォーマット」の出力が、端末の設定で変わる | GMT+9・タイムゾーンなし・スラッシュ区切りも読む |
値が 65.0 kg のように来る |
サンプルを、そのまま文字として入れていた | 先頭の数字を読む |
| URL に変換できないというエラー | URL 欄を変数のチップにしていた | 文字として直接入力する |
| 体重と体脂肪率が入れ替わる | 同じ名前の「ヘルスケアサンプル」が2つある | 各アクションの直下に出るものを選ぶ |
| 動作確認のパスが 404 | /healthz が Cloud Run の予約されたパスに当たったと思われる |
/status に変える |
| Cloud Run の作成が失敗 | Secret にトークンを入れる前に Service を作った | Secret の箱 → トークン → Service の順にする |
ショートカットを直すのをやめて、受け取る側を広げた

最初は、ショートカットの中で JSON の文字列を組み立てていました。iPhone のキーボードで { や " を打つのはかなり辛く、" を消しすぎたり、値に単位が付いたまま入ったりして、何度も 400 になりました。
そこで、受け口の側で受け取れる形を広げました。
{"date": ..., "weight_body_mass": 65.0}のような平らな形を受け付ける(フォームだけで作れる)"65.0 kg"のような単位付きの文字列でも、先頭の数字を読む- 日付は
+0900・GMT+9・タイムゾーンなし(日本時間とみなす)・スラッシュ区切りのどれでも読む
iPhone の設定は一度作ったら触りたくないので、こちらのほうが楽でした。今のショートカットは「日付をフォーマット」も使わず、開始日をそのまま渡しています。
ただし、この受け取り方には前提があります。
- 単位の文字は見ていません。
65.0 lbが来ても kg として入ります。ショートカット側で単位を kg・% に固定しているのはそのためです - タイムゾーンのない日付は日本時間とみなします。 日本で使う前提で、海外にいる日は時刻がずれます
/healthz はアプリに届かない
run.app のアドレスで GET /healthz を叩くと 404 になり、/status に変えると返ってくるようになりました。Cloud Run のドキュメントの既知の問題には、「末尾が z のパスには予約されているものがあるので、末尾が z のパスは避けることをすすめる」という注意書きがあります。原因は確かめきれていませんが、これに関係していると思います。
Secret の中身より先に Service を作ると失敗する
Cloud Run には、トークンを Secret Manager の「最新の版」として渡しています。Terraform で Secret と Service を一度に作ると、Secret に版がまだないので Service の作成が失敗しました。Cloud Run は、デプロイのときに Secret の版を確かめにいくためです。
先に Secret の箱だけを作り、トークンを入れてから Service を作っています。
terraform apply -target=google_secret_manager_secret.ingest_token
openssl rand -hex 32 | tr -d '\n' | gcloud secrets versions add ingest-token --data-file=-
terraform apply
別のやり方
ほかのアプリを経由しない:以前は、体重計 → ヘルスケア → MyFitnessPal → Garmin の経路で体重を送っていました。体脂肪率は入らず、MyFitnessPal を消した5月で止まりました。経由するアプリが多いほど止まりやすいので、ヘルスケアから直接送る形にしました。
指標を増やすなら Health Auto Export:ショートカットは、指標を1つ足すたびにアクションが増え、送れるのも「最新の1件」だけです。歩数や HRV のように1日に何件もある指標なら、Health Auto Export のほうが向いていると思います。受け口はこのアプリの JSON の形も受け取れるので、乗り換えてもサーバー側は変えなくて済みます。
使っている体重計
Eufy の体重計です。ヘルスケアに書き込める体重計なら、メーカーを問わず同じショートカットで送れます。
参考
あわせて読みたい
