health-sync
Analyze synced health data across Oura, Withings, Hevy, Strava, WHOOP, and Eight Sleep.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-health-sync
Repository
Skill path: skills/filipe-m-almeida/health-sync
Analyze synced health data across Oura, Withings, Hevy, Strava, WHOOP, and Eight Sleep.
Open repositoryBest for
Primary workflow: Analyze Data & AI.
Technical facets: Full Stack, Data / AI.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install health-sync into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding health-sync to shared team environments
- Use health-sync for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: health-sync
description: Analyze synced health data across Oura, Withings, Hevy, Strava, WHOOP, and Eight Sleep.
homepage: https://github.com/filipe-m-almeida/health-sync
metadata:
openclaw:
emoji: "🩺"
requires:
bins:
- node
- npm
- npx
config:
- workspace/health-sync/health-sync.toml
- workspace/health-sync/.health-sync.creds
- workspace/health-sync/health.sqlite
install:
- kind: node
package: health-sync
bins:
- health-sync
read_when:
- User asks for health-sync setup, auth, sync, provider status, or remote bootstrap onboarding
- User asks about sleep, recovery, training, activity, your health, or cross-provider trends
---
# Health Sync Analysis Skill
## Purpose
This skill is dedicated to analyzing the user's health data across available providers:
- Oura
- Withings
- Hevy
- Strava
- WHOOP
- Eight Sleep
The main goal is to help the user understand trends, compare signals across providers, and find useful insights from their synced data.
## Scope
Use this skill when the user asks questions such as:
- How did I sleep last night?
- How was my last workout?
- How did my resting heart rate change during the year?
- What trends are you seeing in my recovery, sleep, and training?
- What useful insights or next steps should I focus on?
## Setup Handling (Remote Bootstrap Only)
Setup is bot-led and remote-first. The only supported onboarding flow is:
1. Bot runs `npx health-sync init remote bootstrap`.
2. Bot sends user: `npx health-sync init --remote <bootstrap-token>`.
3. User sends back encrypted archive.
4. Bot runs `npx health-sync init remote finish <ref> <archive>`.
For full operational instructions, always consult:
- `references/setup.md`
Do not use or recommend legacy direct setup flows such as:
1. `health-sync init` as the primary user instruction
2. `health-sync auth <provider>` as a standalone onboarding path
Those commands may still exist for maintenance/debugging, but they are not the setup flow this skill should guide.
## Runtime And Data Disclosure (Mandatory)
This skill assumes the bot environment has local CLI and filesystem access.
1. Required binary:
- `node`
- `npm`
- `npx`
2. Expected local working paths:
- `workspace/health-sync/health-sync.toml`
- `workspace/health-sync/.health-sync.creds`
- `workspace/health-sync/health.sqlite`
3. Sensitive-data handling:
- Remote onboarding imports encrypted archives that contain provider credentials/tokens.
- Finish flow writes decrypted secrets to local files on the bot host.
- These files must be treated as sensitive at rest (access controls, backups, retention).
4. Chat-safety boundary:
- Never ask users to paste raw secrets in chat.
- Only collect encrypted archive files via remote bootstrap flow.
## Schema Handling
To understand data schemas and query correctly, read the provider reference files:
- `references/oura.md`
- `references/withings.md`
- `references/hevy.md`
- `references/strava.md`
- `references/whoop.md`
- `references/eightsleep.md`
## Freshness Rule (Mandatory)
Before any analysis, always run:
```bash
npx health-sync sync
```
If sync fails, report the failure clearly and continue analysis only if the user explicitly asks to proceed with potentially stale data.
## Analysis Workflow
1. Run `npx health-sync sync` first.
2. Identify the user question and which provider/resource(s) are relevant.
3. Read the provider schema reference before forming SQL.
4. Query `records`, `sync_state`, and `sync_runs` as needed.
5. Produce a clear, user-friendly answer with concrete numbers and dates.
6. Highlight meaningful patterns and offer practical guidance.
7. When data quality or coverage is limited, say so explicitly.
## Output Style
- Be concise, clear, and practical.
- Focus on useful interpretation, not just raw data dumps.
- Connect metrics to actionable insights (sleep, recovery, training, consistency, etc.).
- Ask follow-up questions only when necessary to improve analysis quality.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/setup.md
```markdown
# Health Sync Setup Reference (Bot View)
This is the authoritative setup flow for ClawHub bots.
Only one onboarding flow is supported:
1. Bot creates bootstrap token.
2. Bot tells user to run remote onboarding locally.
3. User sends encrypted archive.
4. Bot imports archive locally.
Legacy setup flows are out of scope for bot guidance.
The bot must actively guide the user step-by-step through this flow.
Do not just send one command and stop; confirm each phase before continuing.
## Command Summary
Bot-side commands:
1. `npx health-sync init remote bootstrap --expires-in 24h`
2. `npx health-sync init remote finish <bootstrap-ref> <archive-path>`
3. `npx health-sync providers --verbose`
4. `npx health-sync sync`
5. `npx health-sync status`
User-side command:
1. `npx health-sync init --remote <bootstrap-token>`
## User Prerequisite Guidance (`npx` and npm)
When the user is unsure what `npx` is, or reports `npx: command not found`, the bot must guide them through prerequisites before retrying setup.
Use this sequence:
1. Ask user to check existing tools:
```bash
node -v
npm -v
npx --version
```
2. If npm/npx is missing, tell user to install Node.js LTS (which includes npm and npx):
- macOS (Homebrew): `brew install node`
- Ubuntu/Debian: `sudo apt update && sudo apt install -y nodejs npm`
- Windows: install Node.js LTS from `https://nodejs.org/`
3. Ask user to close/reopen terminal and rerun:
```bash
node -v
npm -v
npx --version
```
4. Continue onboarding with:
```bash
npx health-sync init --remote <bootstrap-token>
```
## Bot Responsibilities
The bot must:
1. Run bootstrap on bot infrastructure.
2. Share only the single remote command with the user.
3. Never ask user for provider secrets in chat.
4. Receive user archive file.
5. Run finish locally and confirm import.
6. Run verification sync and status.
## Detailed Bot Runbook
### Phase 1: Bootstrap locally (bot machine)
Run:
```bash
npx health-sync init remote bootstrap --expires-in 24h
```
Capture from output:
1. bootstrap token (`hsr1...`)
2. session fingerprint
3. expiry timestamp
### Phase 2: Tell user exactly what to run
Send this instruction pattern:
```text
I created a secure one-time setup token for your Health Sync onboarding.
Please run this on your own machine:
npx health-sync init --remote <TOKEN>
This will walk you through provider setup and generate an encrypted archive.
Send that archive file back here when done.
```
Important guidance to include:
1. User should run command in a local terminal with browser access.
2. User should complete provider auth inside the interactive wizard.
3. User should upload the generated `.enc` archive file to the bot.
### Phase 3: Import archive locally (bot machine)
After receiving the archive:
```bash
npx health-sync init remote finish <TOKEN_OR_KEY_ID_OR_SESSION_ID> /path/to/archive.enc
```
Optional target paths:
```bash
npx health-sync init remote finish <REF> /path/to/archive.enc \
--target-config /path/to/health-sync.toml \
--target-creds /path/to/.health-sync.creds
```
Expected finish behavior:
1. decrypts archive
2. validates checksums
3. writes config + creds
4. creates backups if files already existed
5. marks bootstrap session as consumed
### Phase 4: Verify ingestion
Run:
```bash
npx health-sync providers --verbose
npx health-sync sync
npx health-sync status
```
Report:
1. providers discovered/enabled
2. sync success or provider-specific failures
3. current data freshness
## What The Bot Should Never Do
Do not ask users to:
1. paste `client_secret`, `api_key`, OAuth callback URLs, access tokens, or passwords into chat
2. run `health-sync auth <provider>` as onboarding
3. do manual same-machine setup (`health-sync init`) as the primary flow
4. globally install `health-sync` as a first step when `npx health-sync ...` is sufficient
Do not instruct mixed flows. Remote bootstrap is the only setup workflow for ClawHub bot guidance.
## User Experience Copy (Recommended)
### Bootstrap response
```text
Secure setup is ready.
Run this command on your own machine:
npx health-sync init --remote <TOKEN>
The wizard will guide you provider-by-provider and then output an encrypted archive.
Please upload that archive file here when complete.
```
### Archive received response
```text
Archive received. I am now importing your encrypted setup on my side.
```
### Import success response
```text
Setup import complete. I will now run a sync and verify your provider status.
```
## Failure Handling
1. Token expired:
- Generate a new bootstrap token.
2. Session already consumed:
- Start a fresh bootstrap and rerun user command.
3. Archive does not match token/session:
- Confirm user used the latest token.
4. User reports no archive generated:
- Ask them to rerun `npx health-sync init --remote <TOKEN>` and complete provider auth steps.
5. User reports `npx` not found:
- Guide npm installation using the prerequisite section above, then retry.
## Security Notes
1. Treat bootstrap tokens as sensitive and short-lived.
2. Keep bot bootstrap storage private.
3. Treat imported `health-sync.toml` and `.health-sync.creds` as secrets.
4. Do not commit secret files to version control.
## Architecture Notes
This setup reference is intentionally self-contained for skill runtime.
If a runtime platform cannot resolve repository-level docs paths, continue using this file as the authoritative bot runbook for remote bootstrap onboarding.
```
### references/oura.md
```markdown
# Oura Schema (provider = 'oura')
This file describes how Oura data is stored in the `health-sync` SQLite cache (`health.sqlite`).
At a glance:
- Table: `records`
- Provider: `provider = 'oura'`
- Resources: `personal_info`, `daily_activity`, `daily_sleep`, `daily_readiness`, `sleep`, `workout`, `heartrate`
- Storage model: one row per Oura document, raw JSON in `payload_json`
Important note about time columns:
- For many Oura resources, `records.start_time` is a **date string** (`YYYY-MM-DD`) even when the payload contains full datetimes.
- For precise timestamps, prefer `json_extract(payload_json, '$.start_datetime')`, `$.end_datetime`, `$.bedtime_start`, etc.
## Resource Map
### `personal_info`
- Upstream endpoint: `GET /v2/usercollection/personal_info`
- `record_id`: always `"me"` (this sync stores one row)
- `start_time` / `end_time`: NULL
- `source_updated_at`: NULL
`payload_json` keys:
- `id` (string)
- `age` (number)
- `weight` (number)
- `height` (number)
- `biological_sex` (string)
- `email` (string)
### `daily_activity`
- Upstream endpoint: `GET /v2/usercollection/daily_activity`
- `record_id`: `$.id` (fallback: `$.day`)
- `start_time`: `$.day` (YYYY-MM-DD)
- `source_updated_at`: `$.updated_at` or `$.modified_at` or `$.timestamp` (implementation-dependent; often `timestamp`)
`payload_json` keys (top-level):
- `id`
- `day`
- `timestamp`
- `score`
- `contributors` (object)
- `steps`
- `class_5_min`
- `active_calories`
- `total_calories`
- `equivalent_walking_distance`
- `meters_to_target`
- `target_meters`
- `target_calories`
- `average_met_minutes`
- `high_activity_met_minutes`, `high_activity_time`
- `medium_activity_met_minutes`, `medium_activity_time`
- `low_activity_met_minutes`, `low_activity_time`
- `sedentary_met_minutes`, `sedentary_time`
- `resting_time`
- `non_wear_time`
- `inactivity_alerts`
- `met`
`contributors` keys:
- `meet_daily_targets`
- `move_every_hour`
- `recovery_time`
- `stay_active`
- `training_frequency`
- `training_volume`
### `daily_sleep`
- Upstream endpoint: `GET /v2/usercollection/daily_sleep`
- `record_id`: `$.id` (fallback: `$.day`)
- `start_time`: `$.day` (YYYY-MM-DD)
- `source_updated_at`: `$.updated_at` or `$.modified_at` or `$.timestamp`
`payload_json` keys:
- `id`
- `day`
- `timestamp`
- `score`
- `contributors` (object)
`contributors` keys:
- `deep_sleep`
- `efficiency`
- `latency`
- `rem_sleep`
- `restfulness`
- `timing`
- `total_sleep`
### `daily_readiness`
- Upstream endpoint: `GET /v2/usercollection/daily_readiness`
- `record_id`: `$.id` (fallback: `$.day`)
- `start_time`: `$.day` (YYYY-MM-DD)
- `source_updated_at`: `$.updated_at` or `$.modified_at` or `$.timestamp`
`payload_json` keys:
- `id`
- `day`
- `timestamp`
- `score`
- `contributors` (object)
- `temperature_deviation`
- `temperature_trend_deviation`
`contributors` keys:
- `activity_balance`
- `body_temperature`
- `hrv_balance`
- `previous_day_activity`
- `previous_night`
- `recovery_index`
- `resting_heart_rate`
- `sleep_balance`
- `sleep_regularity`
### `sleep`
- Upstream endpoint: `GET /v2/usercollection/sleep`
- `record_id`: `$.id` (fallback: `$.day`)
- `start_time`: `$.day` (YYYY-MM-DD) (note: payload includes precise bedtime datetimes)
- `end_time`: NULL (note: payload includes bedtime datetimes)
- `source_updated_at`: `$.updated_at` or `$.modified_at` or `$.timestamp` (when present)
`payload_json` keys (top-level):
- `id`
- `day`
- `type`
- `period`
- `bedtime_start` (ISO datetime with timezone offset)
- `bedtime_end` (ISO datetime with timezone offset)
- `average_heart_rate` (number)
- `lowest_heart_rate` (number)
- `average_hrv` (integer)
- `average_breath` (number)
- `efficiency` (integer)
- `latency` (integer)
- `awake_time` (integer)
- `restless_periods` (integer)
- `time_in_bed` (integer)
- `total_sleep_duration` (integer)
- `deep_sleep_duration` (integer)
- `light_sleep_duration` (integer)
- `rem_sleep_duration` (integer)
- `movement_30_sec` (string)
- `sleep_phase_5_min` (string)
- `sleep_score_delta` (integer)
- `readiness_score_delta` (integer)
- `sleep_algorithm_version` (string)
- `sleep_analysis_reason` (string)
- `low_battery_alert` (boolean)
- `heart_rate` (object, time-series)
- `hrv` (object, time-series)
- `readiness` (object)
`heart_rate` and `hrv` (time-series) shape:
- `interval` (number): sample interval
- `timestamp` (datetime): series start timestamp
- `items` (array): samples
### `workout`
- Upstream endpoint: `GET /v2/usercollection/workout`
- `record_id`: `$.id` (fallback: `$.day` or hash)
- `start_time`: `$.day` (YYYY-MM-DD) (note: payload includes `start_datetime`)
- `end_time`: `$.end_datetime` (ISO datetime; populated when present in payload)
- `source_updated_at`: `$.updated_at` or `$.modified_at` or `$.timestamp` (often NULL for workout rows)
`payload_json` keys:
- `id`
- `day`
- `activity`
- `start_datetime` (ISO datetime)
- `end_datetime` (ISO datetime)
- `calories`
- `distance`
- `intensity`
- `label`
- `source`
### `heartrate`
- Upstream endpoint: `GET /v2/usercollection/heartrate`
- `record_id`: `$.timestamp` (fallback: hash)
- `start_time`: `$.timestamp` (ISO datetime with `Z`)
- `end_time`: NULL
- `source_updated_at`: same as `start_time` (timestamp)
`payload_json` keys:
- `timestamp` (ISO datetime)
- `bpm` (integer)
- `source` (string, e.g. `awake`, `sleep`, `rest`)
## Common Analysis Queries
Daily readiness score (last 30 days):
```sql
select
start_time as day,
json_extract(payload_json, '$.score') as readiness_score
from records
where provider = 'oura' and resource = 'daily_readiness'
order by day desc
limit 30;
```
Sleep duration and efficiency (use payload fields; `records.start_time` is day-only):
```sql
select
start_time as day,
json_extract(payload_json, '$.total_sleep_duration') as total_sleep_s,
json_extract(payload_json, '$.efficiency') as efficiency
from records
where provider = 'oura' and resource = 'sleep'
order by day desc
limit 30;
```
Workouts (use `start_datetime`/`end_datetime` from payload):
```sql
select
json_extract(payload_json, '$.start_datetime') as start_dt,
json_extract(payload_json, '$.end_datetime') as end_dt,
json_extract(payload_json, '$.activity') as activity,
json_extract(payload_json, '$.calories') as calories
from records
where provider = 'oura' and resource = 'workout'
order by start_dt desc
limit 50;
```
Heart rate daily average (UTC date derived from timestamp):
```sql
select
substr(start_time, 1, 10) as day_utc,
round(avg(json_extract(payload_json, '$.bpm')), 1) as avg_bpm,
count(*) as samples
from records
where provider = 'oura' and resource = 'heartrate'
group by day_utc
order by day_utc desc
limit 30;
```
```
### references/withings.md
```markdown
# Withings Schema (provider = 'withings')
This file describes how Withings data is stored in the `health-sync` SQLite cache (`health.sqlite`).
At a glance:
- Table: `records`
- Provider: `provider = 'withings'`
- Resources: `measures`, `activity`, `workouts`, `sleep_summary`
- Storage model: one row per Withings document, raw JSON in `payload_json`
Important note about Withings measure values:
- In `measures`, each element in `$.measures[]` uses a `(value, unit)` pair.
- The real-world value is typically `value * 10^unit` (e.g., grams -> kg scaling).
## Resource Map
### `measures` (Body / Vitals)
- Upstream endpoint: `POST https://wbsapi.withings.net/measure` with `action=getmeas`
- `record_id`: `$.grpid` (measure group id)
- `start_time`: derived from `$.date` (epoch seconds) into ISO timestamp (UTC)
- `end_time`: NULL
- `source_updated_at`: `$.modified` (epoch seconds, stored as string)
Top-level `payload_json` keys commonly present:
- `grpid` (integer)
- `date` (epoch seconds)
- `modified` (epoch seconds)
- `category` (integer)
- `attrib` (integer)
- `timezone` (string)
- `deviceid` / `hash_deviceid` (integers/strings depending on response)
- `model` / `modelid` (device model identifiers)
- `measures` (array)
`payload_json.measures[]` elements:
- `type` (integer): measurement type code (e.g., `1` weight)
- `unit` (integer): base-10 exponent to scale `value`
- `value` (integer): scaled integer value
- `algo` (integer, optional)
- `fm` (integer, optional)
Common `type` codes synced by default (see `health_sync/providers/withings.py`):
- `1` Weight
- `4` Height
- `5` FatFreeMass
- `6` FatRatio
- `8` FatMassWeight
- `9` DiastolicBP
- `10` SystolicBP
- `11` HeartPulse
- `12` Temp
- `54` SPO2
- `71` BodyTemp
- `73` SkinTemp
- `76` MuscleMass
- `77` Hydration
- `88` BoneMass
- `91` Pulse Wave Velocity
- `123` VO2 max
### `activity` (Daily Activity)
- Upstream endpoint: `POST https://wbsapi.withings.net/v2/measure` with `action=getactivity`
- `record_id`: usually `$.date` (YYYY-MM-DD)
- `start_time`: `$.date` (YYYY-MM-DD)
- `end_time`: NULL
- `source_updated_at`: NULL (Withings activity responses typically do not provide a per-row modified timestamp)
Top-level `payload_json` keys commonly present:
- `date` (YYYY-MM-DD)
- `steps`
- `distance`
- `elevation`
- `calories`
- `totalcalories`
- `active`, `soft`, `moderate`, `intense`
- Optional HR zone fields may appear depending on device/data availability:
- `hr_average`, `hr_min`, `hr_max`, `hr_zone_0..3`
- Plus device metadata fields like `brand`, `deviceid`, `model`, `modelid`, `timezone`, `modified`
### `workouts` (Workout Sessions)
- Upstream endpoint: `POST https://wbsapi.withings.net/v2/measure` with `action=getworkouts`
- `record_id`: `$.id` (workout id)
- `start_time`: derived from `$.startdate` (epoch seconds) into ISO timestamp (UTC)
- `end_time`: derived from `$.enddate` (epoch seconds) into ISO timestamp (UTC)
- `source_updated_at`: `$.modified` (epoch seconds, stored as string)
Top-level `payload_json` keys commonly present:
- `id` (integer)
- `startdate` / `enddate` (epoch seconds)
- `modified` (epoch seconds)
- `date` (YYYY-MM-DD, may be present)
- `timezone` (string)
- `deviceid`, `model`
- `attrib`, `category`
- `data` (object): workout metrics
`payload_json.data` keys commonly requested by this project:
- `calories`
- `effduration`
- `intensity`
- `manual_distance`
- `manual_calories`
- `pause_duration`
- `algo_pause_duration`
- `steps`
- `distance`
- `elevation`
- Heart rate stats:
- `hr_average`, `hr_min`, `hr_max`, `hr_zone_0..3`
- `spo2_average`
### `sleep_summary` (Aggregated Sleep)
- Upstream endpoint: `POST https://wbsapi.withings.net/v2/sleep` with `action=getsummary`
- `record_id`: `$.id` (sleep summary id)
- `start_time`: derived from `$.startdate` (epoch seconds) into ISO timestamp (UTC)
- `end_time`: derived from `$.enddate` (epoch seconds) into ISO timestamp (UTC)
- `source_updated_at`: `$.modified` (epoch seconds, stored as string)
Top-level `payload_json` keys commonly present:
- `id`
- `startdate` / `enddate` (epoch seconds)
- `modified` (epoch seconds)
- `date` (YYYY-MM-DD, may be present)
- `timezone`
- `created`, `completed`
- `data` (object): summary metrics
`payload_json.data` keys vary by device generation; commonly:
- `sleep_score`
- `lightsleepduration`, `deepsleepduration`, `remsleepduration`
- `wakeupcount`, `wakeupduration`
- `durationtosleep`, `durationtowakeup`
- `hr_average`, `hr_min`, `hr_max`
- `rr_average`, `rr_min`, `rr_max`
- Additional fields may appear, e.g. `snoring`, `chest_movement_rate_*`, etc.
## `sync_state` Notes (Withings)
Withings incremental sync uses epoch seconds.
- `sync_state.provider = 'withings'`
- Provider code computes/uses epoch values for `lastupdate`, but persisted `sync_state.watermark` is normalized to UTC ISO format in SQLite.
- On read, provider code converts the stored watermark back to epoch seconds before calling the API.
## Common Analysis Queries
Daily steps (last 30 days):
```sql
select
start_time as day,
json_extract(payload_json, '$.steps') as steps,
json_extract(payload_json, '$.distance') as distance_m
from records
where provider = 'withings' and resource = 'activity'
order by day desc
limit 30;
```
Weight time series (type = 1) with scaling:
```sql
with grp as (
select start_time as ts, payload_json
from records
where provider = 'withings' and resource = 'measures'
),
meas as (
select
grp.ts,
json_extract(m.value, '$.type') as type,
json_extract(m.value, '$.value') as value_i,
json_extract(m.value, '$.unit') as unit_exp
from grp, json_each(grp.payload_json, '$.measures') m
)
select
ts,
(value_i * pow(10, unit_exp)) as value
from meas
where type = 1
order by ts desc
limit 50;
```
Workouts calories and duration:
```sql
select
start_time as start_dt,
end_time as end_dt,
json_extract(payload_json, '$.data.calories') as calories,
json_extract(payload_json, '$.data.effduration') as effduration_s
from records
where provider = 'withings' and resource = 'workouts'
order by start_dt desc
limit 50;
```
```
### references/hevy.md
```markdown
# Hevy Schema (provider = 'hevy')
This file describes how Hevy data is stored in the `health-sync` SQLite cache (`health.sqlite`).
At a glance:
- Table: `records`
- Provider: `provider = 'hevy'`
- Resources: `workouts`, `workout_events`
- Storage model: one row per workout (plus optional audit/event rows), raw JSON in `payload_json`
Data-quality note:
- If the user identifies a known cleanup/correction date, apply a cutoff when doing trend/report analysis.
- Session baseline from setup errata: `2026-02-12` onward is trusted.
## Resource Map
### `workouts`
- Upstream endpoints:
- First sync: `GET /v1/workouts` (paged)
- Delta sync: `GET /v1/workouts/events?since=...` (stores updated workouts)
- `record_id`: `$.id` (UUID string)
- `start_time`: `$.start_time` (ISO datetime)
- `end_time`: `$.end_time` (ISO datetime)
- `source_updated_at`: `$.updated_at` (fallback: `$.created_at`)
Top-level `payload_json` keys commonly present:
- `id` (UUID)
- `title` (string)
- `description` (string or null)
- `routine_id` (UUID or null)
- `start_time` (ISO datetime)
- `end_time` (ISO datetime)
- `created_at` (ISO datetime)
- `updated_at` (ISO datetime)
- `exercises` (array)
`payload_json.exercises[]` element keys:
- `index` (integer)
- `title` (string)
- `notes` (string or null)
- `exercise_template_id` (UUID)
- `superset_id` (UUID or null)
- `sets` (array)
`payload_json.exercises[].sets[]` element keys:
- `index` (integer)
- `type` (string): indicates the set modality
- `weight_kg` (number or null)
- `reps` (integer or null)
- `distance_meters` (number or null)
- `duration_seconds` (integer or null)
- `rpe` (number or null)
- `custom_metric` (varies; may be null)
Notes:
- Hevy uses mixed modalities; do not assume every set has `weight_kg` and `reps`.
- Workout `start_time`/`end_time` strings may be in `...Z` or `...+00:00` format depending on the client.
### `workout_events` (Audit Trail)
This project stores an optional event log when syncing via `/v1/workouts/events`.
- `record_id`:
- Updated: `updated:{workout_id}:{updated_at}`
- Deleted: `deleted:{workout_id}:{deleted_at}`
- `start_time`:
- Updated: `$.workout.updated_at` (or `$.workout.created_at`)
- Deleted: `$.deleted_at` (if present)
- `source_updated_at`: mirrors `start_time`
Top-level `payload_json` keys commonly present:
- `type` (string): `updated` or `deleted`
- `workout` (object): the full workout (for `updated`)
`payload_json.workout` has the same shape as a normal `workouts` payload.
## Common Analysis Queries
Workout list (most recent first):
```sql
select
start_time,
end_time,
json_extract(payload_json, '$.title') as title,
json_extract(payload_json, '$.updated_at') as updated_at
from records
where provider = 'hevy'
and resource = 'workouts'
and start_time >= '2026-02-12'
order by start_time desc
limit 50;
```
Workouts per month:
```sql
select substr(start_time, 1, 7) as month, count(*) as workouts
from records
where provider = 'hevy'
and resource = 'workouts'
and start_time >= '2026-02-12'
group by month
order by month;
```
Top exercises (by appearance in workouts):
```sql
with workouts as (
select record_id, payload_json
from records
where provider = 'hevy'
and resource = 'workouts'
and start_time >= '2026-02-12'
),
exercises as (
select
workouts.record_id as workout_id,
json_extract(e.value, '$.title') as exercise_title
from workouts, json_each(workouts.payload_json, '$.exercises') e
)
select exercise_title, count(*) as n
from exercises
where exercise_title is not null
group by exercise_title
order by n desc
limit 30;
```
Total lifted volume approximation (sum of weight_kg * reps for strength sets):
```sql
with workouts as (
select record_id, start_time, payload_json
from records
where provider = 'hevy'
and resource = 'workouts'
and start_time >= '2026-02-12'
),
sets as (
select
workouts.record_id as workout_id,
workouts.start_time as start_time,
json_extract(s.value, '$.weight_kg') as weight_kg,
json_extract(s.value, '$.reps') as reps
from workouts,
json_each(workouts.payload_json, '$.exercises') e,
json_each(e.value, '$.sets') s
)
select
substr(start_time, 1, 10) as day,
round(sum(weight_kg * reps), 1) as volume_kg_reps
from sets
where weight_kg is not null and reps is not null
group by day
order by day desc
limit 30;
```
```
### references/strava.md
```markdown
# Strava Schema (provider = 'strava')
This file describes how Strava data is stored in the `health-sync` SQLite cache (`health.sqlite`).
At a glance:
- Table: `records`
- Provider: `provider = 'strava'`
- Resources: `athlete`, `activities`
- Storage model: one row per Strava document, raw JSON in `payload_json`
Important notes about timestamps and watermarks:
- For `activities`, `records.start_time` is `$.start_date` (ISO datetime in UTC).
- For `activities`, `records.source_updated_at` is `$.updated_at` when present, otherwise `$.start_date`.
- For `athlete`, `records.source_updated_at` is set to sync time (`utc_now`) in the provider code, not to Strava's profile `updated_at`.
- `sync_state` for `strava/activities` is stored as an ISO timestamp in SQLite, but is computed from epoch seconds in the sync loop.
## Resource Map
### `athlete` (Profile Snapshot)
- Upstream endpoint: `GET /api/v3/athlete`
- `record_id`: `$.id` (fallback: `"me"`)
- `start_time`: NULL
- `end_time`: NULL
- `source_updated_at`: current sync timestamp (`utc_now_iso()`)
Top-level `payload_json` keys commonly present:
- `id`
- `username`
- `firstname`, `lastname`
- `bio`
- `city`, `state`, `country`
- `sex`
- `weight`
- `profile`, `profile_medium`
- `created_at`, `updated_at`
- `premium`, `summit`, `follower`, `friend`
### `activities`
- Upstream endpoint: `GET /api/v3/athlete/activities`
- Request params used by this project:
- `after` (epoch seconds)
- `page`
- `per_page` (1-200)
- `record_id`: `$.id` (fallback: SHA-256 hash of canonicalized JSON payload)
- `start_time`: `$.start_date` (ISO datetime, usually `...Z`)
- `end_time`: NULL
- `source_updated_at`: `$.updated_at` (if present), otherwise `$.start_date`
Top-level `payload_json` keys commonly present:
- Identity/labeling:
- `id`
- `name`
- `type`
- `sport_type`
- `workout_type` (optional)
- Time/location:
- `start_date`
- `start_date_local`
- `timezone`
- `utc_offset`
- `location_city`, `location_state`, `location_country`
- `start_latlng`, `end_latlng`
- Effort metrics:
- `distance` (meters)
- `moving_time` (seconds)
- `elapsed_time` (seconds)
- `total_elevation_gain` (meters)
- `average_speed`, `max_speed` (meters/second)
- `average_heartrate`, `max_heartrate` (optional)
- `suffer_score` (optional)
- `kilojoules` (optional)
- Visibility/flags:
- `private`
- `visibility`
- `manual`
- `trainer`
- `commute`
- `flagged`
- `has_heartrate`
- Social/metadata:
- `achievement_count`
- `kudos_count`
- `comment_count`
- `athlete_count`
- `photo_count`
- `total_photo_count`
- `pr_count`
- `has_kudoed`
- `external_id`
- `upload_id`
- `gear_id`
- `device_name`
Nested objects commonly present:
- `athlete`
- `id`
- `resource_state`
- `map`
- `id`
- `summary_polyline`
- `resource_state`
Notes:
- In observed data, `record_id` for `activities` matches `$.id` for all rows.
- `activities` may include many sport types (for example `Run`, `Walk`, `WeightTraining`, `StairStepper`, `Hike`, `Swim`).
## `sync_state` Notes (Strava)
Strava activity sync uses watermark overlap and pagination:
- `sync_state.provider = 'strava'`
- `sync_state.resource = 'activities'`
- Stored watermark tracks the max seen activity start time and is reused as the next `after` baseline minus configured overlap seconds.
The `athlete` resource also writes sync state with a current timestamp watermark, mainly as a freshness marker.
## Common Analysis Queries
Recent Strava activities:
```sql
select
start_time as start_dt,
json_extract(payload_json, '$.name') as name,
json_extract(payload_json, '$.sport_type') as sport_type,
round(json_extract(payload_json, '$.distance') / 1000.0, 2) as distance_km,
json_extract(payload_json, '$.moving_time') as moving_time_s,
json_extract(payload_json, '$.elapsed_time') as elapsed_time_s
from records
where provider = 'strava' and resource = 'activities'
order by start_dt desc
limit 50;
```
Monthly activity counts by sport type:
```sql
select
substr(start_time, 1, 7) as month,
json_extract(payload_json, '$.sport_type') as sport_type,
count(*) as activities
from records
where provider = 'strava' and resource = 'activities'
group by month, sport_type
order by month desc, activities desc;
```
Monthly distance and moving time totals:
```sql
select
substr(start_time, 1, 7) as month,
round(sum(json_extract(payload_json, '$.distance')) / 1000.0, 1) as distance_km,
round(sum(json_extract(payload_json, '$.moving_time')) / 3600.0, 1) as moving_hours,
count(*) as activities
from records
where provider = 'strava' and resource = 'activities'
group by month
order by month desc;
```
Heart rate coverage by sport type:
```sql
select
json_extract(payload_json, '$.sport_type') as sport_type,
sum(case when json_type(payload_json, '$.average_heartrate') is not null then 1 else 0 end) as with_hr,
count(*) as total,
round(100.0 * sum(case when json_type(payload_json, '$.average_heartrate') is not null then 1 else 0 end) / count(*), 1) as pct_with_hr
from records
where provider = 'strava' and resource = 'activities'
group by sport_type
order by total desc;
```
Coverage window and sync watermark sanity check:
```sql
select
(select min(start_time) from records where provider = 'strava' and resource = 'activities') as min_activity_start,
(select max(start_time) from records where provider = 'strava' and resource = 'activities') as max_activity_start,
(select watermark from sync_state where provider = 'strava' and resource = 'activities') as sync_watermark;
```
```
### references/whoop.md
```markdown
# WHOOP Schema (provider = 'whoop')
This file describes how WHOOP data is stored in the `health-sync` SQLite cache (`health.sqlite`).
At a glance:
- Table: `records`
- Provider: `provider = 'whoop'`
- Resources: `profile_basic`, `body_measurement`, `cycles`, `recoveries`, `sleep`, `workouts`
- Storage model: one row per WHOOP document, raw JSON in `payload_json`
Important notes about timestamps and watermarks:
- `profile_basic` and `body_measurement` are snapshot-style resources and use sync-time watermarks.
- Collection resources (`cycles`, `recoveries`, `sleep`, `workouts`) use incremental windows:
- query params include `start`, `end`, `limit`, and pagination via `nextToken`
- `sync_state.watermark` tracks max observed event time and is stored as UTC ISO
- next run subtracts configured overlap days from previous watermark to avoid edge misses
## Resource Map
### `profile_basic` (User Profile Snapshot)
- Upstream endpoint: `GET /v2/user/profile/basic`
- `record_id`: `$.user_id` (fallback: `"me"`)
- `start_time`: `$.created_at` when present, else NULL
- `end_time`: NULL
- `source_updated_at`: `$.updated_at` when present, else sync time
Common `payload_json` keys:
- `user_id`
- `email`
- `first_name`
- `last_name`
### `body_measurement` (User Body Snapshot)
- Upstream endpoint: `GET /v2/user/measurement/body`
- `record_id`: `$.user_id` when present, fallback `"me"`
- `start_time`: `$.created_at` when present, else NULL
- `end_time`: NULL
- `source_updated_at`: `$.updated_at` when present, else sync time
Common `payload_json` keys:
- `height_meter`
- `weight_kilogram`
- `max_heart_rate`
### `cycles`
- Upstream endpoint: `GET /v2/cycle`
- Request params used by this project:
- `limit` (default 25, max 25)
- `start` (UTC ISO)
- `end` (UTC ISO)
- `nextToken` (for pagination)
- `record_id`: `$.id` (fallback: SHA-256 hash of payload)
- `start_time`: `$.start` (fallback: `$.created_at` / `$.updated_at`)
- `end_time`: `$.end`
- `source_updated_at`: `$.updated_at` (fallback: `$.created_at` / `$.start`)
Common `payload_json` keys:
- `id`
- `user_id`
- `created_at`
- `updated_at`
- `start`
- `end`
- `timezone_offset`
- `score_state`
- `score.*` (strain and cycle-level metrics)
### `recoveries`
- Upstream endpoint: `GET /v2/recovery`
- Request params used by this project:
- `limit` (default 25, max 25)
- `start` (UTC ISO)
- `end` (UTC ISO)
- `nextToken` (for pagination)
- `record_id`: `$.cycle_id` (fallback: SHA-256 hash of payload)
- `start_time`: `$.start` when present, otherwise `$.created_at` or `$.updated_at`
- `end_time`: `$.end` when present, else NULL
- `source_updated_at`: `$.updated_at` (fallback: `$.created_at` / start time)
Common `payload_json` keys:
- `cycle_id`
- `sleep_id`
- `user_id`
- `created_at`
- `updated_at`
- `score_state`
- `score.*` (recovery score, HRV, resting heart rate, etc.)
### `sleep`
- Upstream endpoint: `GET /v2/activity/sleep`
- Request params used by this project:
- `limit` (default 25, max 25)
- `start` (UTC ISO)
- `end` (UTC ISO)
- `nextToken` (for pagination)
- `record_id`: `$.id` (fallback: SHA-256 hash of payload)
- `start_time`: `$.start`
- `end_time`: `$.end`
- `source_updated_at`: `$.updated_at` (fallback: `$.created_at` / `$.start`)
Common `payload_json` keys:
- `id`
- `cycle_id`
- `user_id`
- `created_at`
- `updated_at`
- `start`
- `end`
- `nap`
- `score_state`
- `score.*` (sleep performance, stage durations, etc.)
### `workouts`
- Upstream endpoint: `GET /v2/activity/workout`
- Request params used by this project:
- `limit` (default 25, max 25)
- `start` (UTC ISO)
- `end` (UTC ISO)
- `nextToken` (for pagination)
- `record_id`: `$.id` (fallback: SHA-256 hash of payload)
- `start_time`: `$.start`
- `end_time`: `$.end`
- `source_updated_at`: `$.updated_at` (fallback: `$.created_at` / `$.start`)
Common `payload_json` keys:
- `id`
- `user_id`
- `created_at`
- `updated_at`
- `start`
- `end`
- `sport_name`
- `sport_id` (legacy compatibility field)
- `score_state`
- `score.*` (strain, average HR, max HR, zone durations)
## `sync_state` Notes (WHOOP)
WHOOP sync state entries are resource-specific:
- `sync_state.provider = 'whoop'`
- `sync_state.resource` in:
- `profile_basic`
- `body_measurement`
- `cycles`
- `recoveries`
- `sleep`
- `workouts`
Watermark behavior:
- Snapshot resources (`profile_basic`, `body_measurement`) store sync-time watermark.
- Collection resources store max observed event timestamp in the run.
- Collection query start date is computed as:
- previous watermark minus `[whoop].overlap_days`, or
- `[whoop].start_date` on first sync.
## Common Analysis Queries
Recent WHOOP sleep sessions:
```sql
select
start_time as sleep_start,
end_time as sleep_end,
json_extract(payload_json, '$.score_state') as score_state,
json_extract(payload_json, '$.score.sleep_performance_percentage') as sleep_perf_pct,
json_extract(payload_json, '$.score.stage_summary.total_in_bed_time_milli') / 60000.0 as in_bed_minutes
from records
where provider = 'whoop' and resource = 'sleep'
order by sleep_start desc
limit 30;
```
Recent WHOOP recovery scores:
```sql
select
source_updated_at as observed_at,
json_extract(payload_json, '$.cycle_id') as cycle_id,
json_extract(payload_json, '$.score.recovery_score') as recovery_score,
json_extract(payload_json, '$.score.hrv_rmssd_milli') as hrv_rmssd,
json_extract(payload_json, '$.score.resting_heart_rate') as resting_hr
from records
where provider = 'whoop' and resource = 'recoveries'
order by observed_at desc
limit 30;
```
Monthly workout strain totals by sport:
```sql
select
substr(start_time, 1, 7) as month,
json_extract(payload_json, '$.sport_name') as sport_name,
round(sum(coalesce(json_extract(payload_json, '$.score.strain'), 0)), 2) as total_strain,
count(*) as workouts
from records
where provider = 'whoop' and resource = 'workouts'
group by month, sport_name
order by month desc, workouts desc;
```
Coverage and watermark sanity check:
```sql
select
(select min(start_time) from records where provider = 'whoop' and resource = 'sleep') as min_sleep_start,
(select max(start_time) from records where provider = 'whoop' and resource = 'sleep') as max_sleep_start,
(select watermark from sync_state where provider = 'whoop' and resource = 'sleep') as sleep_sync_watermark,
(select watermark from sync_state where provider = 'whoop' and resource = 'workouts') as workout_sync_watermark;
```
```
### references/eightsleep.md
```markdown
# Eight Sleep Schema (provider = 'eightsleep')
This file describes how Eight Sleep data is stored in the `health-sync` SQLite cache (`health.sqlite`).
At a glance:
- Table: `records`
- Provider: `provider = 'eightsleep'`
- Resources: `users_me`, `devices`, `users`, `trends`
- Storage model: one row per API document/day record, raw JSON in `payload_json`
Important notes about timestamps and watermarks:
- `users_me`, `devices`, and `users` are snapshot-like resources:
- `records.start_time` and `records.end_time` are NULL.
- `records.source_updated_at` is sync time (`utc_now_iso()`), not provider-side `updatedAt`.
- For `trends`:
- `records.start_time` is `$.day` (fallback: `$.presenceStart`).
- `records.end_time` is `$.presenceEnd`.
- `records.source_updated_at` is `$.updatedAt` when present, otherwise `$.presenceStart` or `start_time`.
- `sync_state` watermarks for Eight Sleep resources are written as current sync time.
- For incremental `trends` sync, this project reuses that watermark date and subtracts configured overlap days.
## Resource Map
### `users_me` (Current User Profile)
- Upstream endpoint: `GET /v1/users/me`
- `record_id`: `$.user.id` (fallback: `"me"`)
- `start_time`: NULL
- `end_time`: NULL
- `source_updated_at`: current sync timestamp (`utc_now_iso()`)
Top-level `payload_json` keys commonly present:
- `user` (object)
`payload_json.user` keys commonly present:
- `userId`
- `firstName`, `lastName`
- `email`, `emailVerified`
- `dob`, `gender`, `zip`
- `devices` (array)
- `currentDevice`
- `sleepTracking`
- `autopilotEnabled`
- `tempPreference`
- `createdAt`
- Additional account/app settings fields may appear.
Note:
- In observed data, `payload_json.user` includes `userId` (not `id`), so this code may store `record_id = "me"` unless the API also provides `user.id`.
### `devices`
- Upstream endpoint: `GET /v1/devices/{device_id}`
- `record_id`: first device id from `users_me.user.devices[]`
- `start_time`: NULL
- `end_time`: NULL
- `source_updated_at`: current sync timestamp (`utc_now_iso()`)
Top-level `payload_json` keys commonly present:
- `result` (object)
`payload_json.result` keys commonly present:
- Device/account linkage:
- `deviceId`
- `ownerId`
- `leftUserId`, `rightUserId`
- `awaySides` (object)
- Device state:
- `online`
- `timezone`
- `firmwareVersion`
- `firmwareUpdating`, `firmwareUpdated`
- `lastHeard`
- Pod/side metrics:
- `leftHeatingLevel`, `rightHeatingLevel`
- `leftKelvin`, `rightKelvin`
- `leftNowHeating`, `rightNowHeating`
- `leftSchedule`, `rightSchedule`
- Additional hardware/network/sensor fields may appear.
### `users` (Bed Occupants / Related Users)
- Upstream endpoint: `GET /v1/users/{user_id}`
- `record_id`: `{user_id}` gathered from:
- current user id
- `devices.result.leftUserId`
- `devices.result.rightUserId`
- `devices.result.awaySides.*`
- `start_time`: NULL
- `end_time`: NULL
- `source_updated_at`: current sync timestamp (`utc_now_iso()`)
Top-level `payload_json` keys commonly present:
- `user` (object, same general shape as `users_me.user`)
### `trends` (Per-Day Sleep/Presence Metrics)
- Upstream endpoint: `GET /v1/users/{user_id}/trends`
- Request params used by this project:
- `tz`
- `from`
- `to`
- `include-main=false`
- `include-all-sessions=true`
- `model-version=v2`
- Response parsing:
- loops over `trend_resp.days[]`
- stores one `records` row per day item
- `record_id`: `{user_id}:{day}` (fallback: `{user_id}:{sha256(day_json)}`)
- `start_time`: `$.day` (fallback: `$.presenceStart`)
- `end_time`: `$.presenceEnd`
- `source_updated_at`: `$.updatedAt` or `$.presenceStart` or `start_time`
Top-level `payload_json` keys commonly present:
- Date/session identity:
- `day`
- `mainSessionId`
- `sessionIds` (array)
- `sessions` (array)
- Timing:
- `presenceStart`, `presenceEnd`
- `sleepStart`, `sleepEnd`
- `presenceDuration`, `sleepDuration`
- Sleep staging/snoring:
- `lightDuration`, `deepDuration`, `remDuration`
- `deepPercent`, `remPercent`
- `snoreDuration`, `snorePercent`
- `heavySnoreDuration`, `heavySnorePercent`
- Scoring:
- `score`
- `sleepQualityScore`
- `sleepRoutineScore`
- Other fields often present:
- `tnt`
- `performanceWindows` (array)
- `tags` (array)
- `hotFlash`
- `incomplete`
- Additional mitigation/elevation fields may appear in some rows:
- `mitigationEvents`
- `stoppedSnoringEvents`
- `reducedSnoringEvents`
- `elevationDuration`
- `snoringReductionPercent`
Nested `sessions[]` objects commonly include:
- `id`
- `duration`
- `sleepStart`, `sleepEnd`
- `presenceEnd`
- `score`
- `timezone`
- `stages`, `stageSummary`
- `timeseries`
- `snoring`
## `sync_state` Notes (Eight Sleep)
Eight Sleep writes sync markers for each resource:
- `sync_state.provider = 'eightsleep'`
- resources: `users_me`, `devices`, `users`, `trends`
- watermark values are sync timestamps (`utc_now_iso()`), not max event times.
For next `trends` run, `_trend_start_date()`:
- parses `sync_state('eightsleep','trends').watermark` as date/datetime
- subtracts `[eightsleep].overlap_days`
- falls back to `[eightsleep].start_date` on first run
## Common Analysis Queries
Latest nightly scores:
```sql
select
record_id,
start_time as day,
json_extract(payload_json, '$.score') as score,
json_extract(payload_json, '$.sleepQualityScore') as sleep_quality_score,
json_extract(payload_json, '$.sleepRoutineScore') as sleep_routine_score,
json_extract(payload_json, '$.sleepDuration') as sleep_duration_s
from records
where provider = 'eightsleep' and resource = 'trends'
order by day desc
limit 50;
```
Daily sleep stage durations:
```sql
select
start_time as day,
json_extract(payload_json, '$.lightDuration') as light_s,
json_extract(payload_json, '$.deepDuration') as deep_s,
json_extract(payload_json, '$.remDuration') as rem_s
from records
where provider = 'eightsleep' and resource = 'trends'
order by day desc
limit 60;
```
Per-user trend coverage:
```sql
select
substr(record_id, 1, instr(record_id, ':') - 1) as user_id,
min(start_time) as first_day,
max(start_time) as last_day,
count(*) as days
from records
where provider = 'eightsleep'
and resource = 'trends'
and instr(record_id, ':') > 0
group by user_id
order by days desc;
```
Flatten trend sessions for session-level analysis:
```sql
with trend_days as (
select
record_id as trend_record_id,
start_time as day,
payload_json
from records
where provider = 'eightsleep' and resource = 'trends'
)
select
trend_record_id,
day,
json_extract(s.value, '$.id') as session_id,
json_extract(s.value, '$.duration') as session_duration_s,
json_extract(s.value, '$.score') as session_score,
json_extract(s.value, '$.sleepStart') as session_sleep_start,
json_extract(s.value, '$.sleepEnd') as session_sleep_end
from trend_days, json_each(trend_days.payload_json, '$.sessions') s
order by day desc
limit 200;
```
Device status snapshot:
```sql
select
record_id as device_record_id,
json_extract(payload_json, '$.result.deviceId') as device_id,
json_extract(payload_json, '$.result.online') as online,
json_extract(payload_json, '$.result.firmwareVersion') as firmware_version,
json_extract(payload_json, '$.result.timezone') as timezone,
json_extract(payload_json, '$.result.leftUserId') as left_user_id,
json_extract(payload_json, '$.result.rightUserId') as right_user_id
from records
where provider = 'eightsleep' and resource = 'devices'
order by fetched_at desc
limit 10;
```
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### _meta.json
```json
{
"owner": "filipe-m-almeida",
"slug": "health-sync",
"displayName": "health-sync",
"latest": {
"version": "0.3.5",
"publishedAt": 1771561726812,
"commit": "https://github.com/openclaw/skills/commit/f2774002aed8cec889dd2a629141cdfae9ba74d7"
},
"history": [
{
"version": "0.2.5",
"publishedAt": 1771481152975,
"commit": "https://github.com/openclaw/skills/commit/6a3489d43de8bcd0e4df300d11843747d2059f5d"
}
]
}
```