Personal blog powered by a passion for technology.

Automating Apple Watch Sleep Tracking with Shortcuts and OpenClaw

I wanted a small piece of personal automation.

My Apple Watch already tracks sleep every night. I wanted the numbers to land in my own system every morning, so OpenClaw could keep history, spot bad streaks, and ask me the missing human part: how do you feel?

I thought this would take an hour.

Of course it did not.

The final setup is boring:

  • Apple Watch records sleep into Apple Health
  • iPhone Shortcuts runs every morning at 07:00
  • Shortcut extracts sleep stages and a few other fields
  • Shortcut posts JSON to a private OpenClaw webhook over Tailscale
  • OpenClaw imports the record, computes a sleep score, and keeps history

The webhook was easy. The annoying part was convincing Shortcuts to return the correct sleep samples.

The architecture

I did not try to make OpenClaw “read” the Apple Watch.

Apple Health lives on the iPhone, and Apple quite reasonably keeps it there. So the direction is push, not pull. The phone extracts the data. OpenClaw stores and analyzes it.

The local side is a tiny receiver:

POST https://my-host.tailnet.ts.net/sleep
Authorization: Bearer <token>
Content-Type: application/json

The service is not public. It binds to 127.0.0.1 and is exposed to my tailnet via Tailscale Serve. The token sits in a local .env file with 600 permissions.

The Shortcut posts a payload like this:

iPhone Shortcuts dictionary for sending Apple Watch sleep data to OpenClaw

{
  "date": "2026-07-11",
  "asleep_minutes": 444,
  "in_bed_minutes": 458,
  "awake_minutes": 14,
  "deep_minutes": 40,
  "rem_minutes": 70,
  "core_minutes": 334,
  "wakeups": 7,
  "bedtime_delta_minutes": -26,
  "energy": 4,
  "notes": "felt fine"
}

If Apple exposes a score, I can store it. If not, I calculate a rough one:

  • duration: up to 50 points
  • bedtime consistency: up to 30 points
  • interruptions: up to 20 points

That roughly mirrors how Apple presents sleep quality without pretending to be Apple.

The Shortcut gotchas

The first version reached the webhook. Good.

The numbers were nonsense.

At one point the backend received:

{
  "asleep_minutes": 638,
  "awake_minutes": 16,
  "in_bed_minutes": 654
}

That is 10h38m asleep. Nice fantasy. Wrong planet.

Apple Watch showed 7h24m.

Apple Health sleep score screen showing 91/100 and the component scores

The problem was sample selection in Shortcuts.

Gotcha 1: sleep crosses midnight

Do not query “today”.

A normal night starts yesterday and ends today. If you filter sleep samples by date in the obvious way, you either miss part of the night or include some other sleep period.

The fix is to query broadly, then manually filter inside the loop.

For example:

sleep_window_start = submitted date - 1 day, 18:00
sleep_window_end   = submitted date, 12:00

Then process samples that overlap that window.

Gotcha 2: Find Health Samples filters by date, not really by time

This was the main trap.

The Shortcuts action looks like it accepts precise date-time filters. In practice, sleep samples behaved more like date buckets. Samples from another sleep period appeared even when the time window should have excluded them.

So I stopped trusting the action filter.

The Shortcut now asks for a broad set of samples and does the real filtering in the repeat loop:

If sleep_window_start is before sample_end
  If sleep_window_end is after sample_start
    process sample

That is simple overlap logic. If a sample does not touch the sleep window, ignore it.

Gotcha 3: comparison order matters in Shortcuts

This one is stupid, but real.

This condition was unreliable:

If Start Date of Repeat Item is after window_start

The reversed comparison worked:

If window_start is before Start Date of Repeat Item

Same logic. Different result.

My guess: the first operand controls type coercion. If the first operand is a Health Sample detail token, Shortcuts sometimes treats it strangely. If the first operand is your own Date variable, comparison behaves like a normal date comparison.

So the rule became:

Put your own Date variable first.
Compare against Health sample dates second.

Ugly, but it works.

Gotcha 4: Duration values may not be minutes

Do not blindly add the Duration field.

Shortcuts may give you something that serializes weirdly, or a value that looks like seconds. The safer path is:

Get sample_start
Get sample_end
Get Time Between Dates
Unit: Minutes
Round Number
Set sample_minutes

Use that number. Do not add the raw Duration field.

Backend defenses

The backend should not blindly trust the phone. Mine certainly should not have trusted the first few payloads.

I added a few defensive behaviors after seeing bad payloads:

  • accept Shortcut-friendly nested dictionaries
  • accept labels like core sleep minutes and wakeups_amount
  • tolerate typos from visual-programming experiments
  • compute in_bed_minutes from asleep + awake when missing
  • merge partial updates by date
  • avoid overwriting known good score components with fallback values
  • keep the last received JSON payload locally for debugging

The last one saved time immediately. Instead of guessing what Shortcuts sent, I could inspect the exact body.

Why this is worth doing

Apple Health already has nice charts. I am not trying to replace them.

I want the data close to my agent because the useful questions are not only “how did I sleep last night?”

The interesting questions are:

  • Did I sleep badly three nights in a row?
  • Is resting heart rate elevated compared with my baseline?
  • Does late dinner correlate with worse sleep?
  • Do hard workouts affect deep sleep?
  • Should I take it easy today?

Numbers are still incomplete. I also want one tiny subjective prompt:

Energy 1-5?
Anything unusual?

That is the bit Apple cannot infer. Sleep stages explain part of the story. Feeling tired, stressed, sick, or fine explains another part.

The final lesson

The AI part was not the hard bit.

The webhook was not the hard bit either.

The hard bit was getting a consumer automation tool to return the right HealthKit samples.

That is a pattern I keep seeing with personal agents. Intelligence is rarely the bottleneck. The boring plumbing is: permissions, local data formats, date boundaries, and tools that almost expose what you need.

Still, once the pipe exists, it becomes a new capability.

Tomorrow at 07:00, my phone will send sleep data to my own system. OpenClaw can compare it with baseline, notice trends, and ask the one question Apple cannot answer:

How do you actually feel?

That is the kind of automation I like: small, personal, boring, useful.