I'm building a system where all my daily records live in Notion, so I can point an AI at it and get feedback. Goals, tasks, daily logs, finances — those are all manual entry, and that's fine. But one day it hit me that weight would be nice to sync automatically.
The requirements were simple:
That's it. My scale is a Withings Body Smart.
This one:
Scale → vendor app → Apple Health → iOS Shortcut → Notion API
I chose Apple Health as the hub for these reasons:
Generic, zero cost, extensible. The design looked sound to me.
Here's what the Shortcut looks like:
1. Find Health Samples [Weight] latest, limit 1
2. Get Details of Health Sample [Value] → variable Kg
3. Get Details of Health Sample [Start Date] → variable SampleDate
4. Format Date yyyy-MM-dd → variable Ymd
5. If Ymd == today
6. Text ← build the JSON
7. Get Contents of URL ← POST to the Notion API
Step 5 matters. Without it, on a day you don't step on the scale, yesterday's weight gets appended under today's date.
Here's the JSON built in step 6:
{
"parent": { "database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" },
"properties": {
"Date": { "title": [ { "text": { "content": "@@YMD@@" } } ] },
"Measured": { "date": { "start": "@@YMD@@" } },
"Weight kg": { "number": @@KG@@ },
"Body fat %": { "number": @@FAT@@ }
}
}
(My real database uses Japanese property names. What matters is that they match your database exactly.)
I write this as a plain string in a Text action and hand it to the POST action.
Notion offers two auth methods for a connection: an internal access token, and OAuth. OAuth is meant for apps that get installed into other people's workspaces, or listed on the Marketplace — so for this I went with the access token.
Notion permissions are inherited by child pages. If you connect an integration at a parent page, everything underneath it becomes reachable.
In my case I may want to auto-sync things other than weight later, so I granted access at the parent page that holds my personal data.
parent
{
"status": 400,
"code": "validation_error",
"message": "Provide a `parent.page_id` or `parent.database_id` parameter to create a page,
or use a public integration with `insert_content` capability.
Internal integrations aren't owned by a single user, so creating
workspace-level private pages is not supported."
}
My JSON has a parent
. And I still got this.
The cause was that I never wired the JSON into the request body. When I set up the POST action in Shortcuts, I hadn't configured it to actually take the JSON — so I was POSTing an empty body. Yeah.
invalid_json
{ "status": 400, "code": "invalid_json", "message": "Error parsing JSON body." }
Weight was appending fine, so I went to add body fat percentage. When I added the property, I forgot the trailing comma on the previous line.
"Weight kg": { "number": 67.878 } ← no comma
"Body fat %": { "number": 16.058 }
Shortcuts has no syntax highlighting and no linter, so I walked right past it.
Drop a Quick Look action immediately after the Text action. It shows you the expanded JSON exactly as it will be sent, so you can confirm the variables actually resolved to values.
In Shortcuts development this is effectively your only debugging tool. When you're stuck, look at the payload.
The weight coming out of Health was something like 67.87800598144531
.
I first tried to round it inside Shortcuts, but somehow I could not get it to work, so I rounded on the Notion side instead.
Add one formula column to the weight database:
round(prop("Weight kg") * 10) / 10
Then hide the raw column in the view and show only the rounded one. Much easier to read.
As a side effect this turned out to be the better design anyway: the raw value stays in the database, and rounding is a display concern.
It ran. Rows appeared in Notion.
But there was one problem left.
Apple Health doesn't get the data unless I open the Withings app.
Stepping on the scale every morning isn't enough. If I want a row every day, I have to open the Withings app every day. Background App Refresh is on, and even then there's no guarantee it syncs daily.
So the actual workflow is:
The appending is automated now, but a daily manual step survived. What I actually wanted was for stepping on the scale to be the whole interaction.
Apple Health is a bucket that gets written to when a connected app launches — it isn't necessarily something that goes and fetches data on its own. I thought I was picking a hub. What I actually put in the middle of the path was a bucket waiting for the Withings app to open.
Meanwhile, Withings has a public API. The scale is on Wi-Fi and presumably pushes measurements straight to the cloud, so a path like this might work:
Scale --Wi-Fi--> Withings cloud --webhook--> my own endpoint --> Notion
A row appears in Notion the moment I step on the scale. There's no app to open.
I picked Health because I wanted the design to be generic — but the scale I actually own is one of the models that can skip that generic layer and be called directly. By choosing "a design that works with any scale," I missed the better path that only my scale had.
When you design an automation path, check up front whether each hop can push data forward on its own. If even one hop is pull-based — it only moves data when something comes asking — that's where a human eventually ends up doing the work.
Originally published in Japanese on Zenn: 体重を自動でNotionに記録したかったが、毎朝アプリを開く仕事が残った