Working reference · October 2026
Everything here I did on my own data while trying to understand my atrial fibrillation. Every dead end listed is one I walked down personally. Apple changes things, so check the dates.
The raw waveform of every ECG you have ever recorded, including the ones the watch gave no verdict on. The interval between every individual heartbeat captured while you were still. And your complete multi-year history of heart rate, variability, irregular-rhythm notifications and low-heart-rate alerts.
Anything the watch did not record. No cleverness recovers the hours you were moving: in movement the watch stores only an averaged pulse every few seconds, not beat by beat. That hole is real and no method closes it.
Part 1
This is the part everybody underestimates, so it goes first. There are five routes. Two work.
The only route that gives you everything: ECG waveforms, beat-to-beat intervals, chest-strap recordings, the lot.
The word encrypted is load-bearing and skipping it is the single most common
mistake. An unencrypted iPhone backup does not contain the health database at all.
Tick “Encrypt local backup”, set a password, and
healthdb_secure.sqlite appears inside. Without it you will search for hours and
conclude the data does not exist.
What it costs, honestly, after a month of doing it: the backup is large — mine is 96 GB — incremental only after the first one, and the phone must be unlocked at the moment it starts. Over WiFi it works but drops; over cable it is faster.
Health app → your photo → Export All Health Data. Produces a zip you can email yourself. Slow and manual, but it needs no tools and it does include the ECG voltage data.
MBErrorDomain/208. The tooling reports it as
“Device locked” only if you capture the full output; otherwise it looks like a
mysterious failure and you will spend a day on disk space. It is not disk space
— I had 125 GB free throughout.
The process stays alive at 0 % CPU, writing nothing, indefinitely. And because most scripts politely skip when they see a backup already running, one hung process silently blocks every later attempt. Mine blocked for twelve hours while every trigger fired and did nothing. The defence is a deadline: if no file has been written in ten minutes, kill it rather than respect it.
This is the one that will actually hurt you. The backup completes, prints Backup Successful, and the health database inside is frozen days earlier because the phone served an old snapshot. There is no error of any kind. Mine did this for two days while a separate channel was showing 411 readings the backup swore did not exist.
Never trust the exit status. After every backup, open the database and ask how recent the newest heart-rate sample actually is. If it is more than a day old, the backup lied to you, whatever it printed. Anything that teaches you to trust “Successful” is teaching you to build a silent hole into your own record.
And be realistic about automation. I built a system with three independent triggers and it produced 5 successful backups in 21 days from 137 attempts. If you need this monthly, do it deliberately: phone unlocked, plugged in, five minutes of your attention. Treating it as automatic is how you discover a five-day hole the day before a cardiology appointment.
The Shortcuts app only reaches flat properties of a health sample — date, value, duration, source. Beat-to-beat intervals live in nested metadata and the waveform is not exposed at all. There is no workaround.
Not by a Shortcuts action, not by API, not by AppleScript. And there is no Health app on the Mac to automate instead.
Health Auto Export closed that feature request without implementing it. Welltory exposes them but only via manual download from its website. Terra API documents them properly and starts at $399 a month.
It does export full ECG waveforms with their sample rate, and heart-rate variability, which is most of what the backup gives you. But its own documentation states it cannot guarantee a schedule, because it depends on iOS granting background time and on the phone being unlocked — the identical wall. It removes the 96 GB and the passcode, not the dependency.
During exercise the watch only has an averaged pulse every few seconds. There is no higher-resolution data sitting there for an app to read. That gap is closed by a chest strap or not at all.
Part 2
healthdb_secure.sqlite. Everything below is from reading my own, so treat the
structure as true in late 2026 on iOS 27.
Timestamps are seconds since 1 January 2001, not the usual 1970. Get this wrong and your data lands 31 years early. Add your UTC offset separately.
Numeric samples live in quantity_samples joined to samples on
data_id. The data_type column selects what you are looking at.
| data_type | What it is | Note |
|---|---|---|
3 | Body mass | |
5 | Heart rate | multiply the value by 60 for beats per minute |
7 | Steps | |
139 | Heart rate variability (SDNN) | in milliseconds |
Events live in category_samples: type 156 is an irregular-rhythm
notification, type 147 a low-heart-rate event. These are the alerts the watch pushed
to you, not measurements.
ECGs live in ecg_samples, with voltage_payload (the waveform),
private_classification (the verdict as an integer), average_heart_rate
and symptoms_status.
The device that produced each sample is in
data_provenances.origin_product_type — mine shows Watch6,4 for the
watch I wore until 2022, Watch7,4 for a Series 9 and Watch8,1 for an
Ultra 4. Use this if you change watch mid-record; see Part 6. The build of watchOS that wrote each
row is in origin_build on the same table, and
that turns out to matter more than the model.
The payload is protobuf. The pattern that matters: a tag byte 0x1d followed by a
4-byte little-endian float, repeated. A standard recording is 15,360 samples at 512 Hz
over 30 seconds.
Apple does not document these. I derived them by matching rows against recordings whose on-screen result I had seen with my own eyes. Do this yourself before trusting them — take three ECGs you remember the result of and check.
| Code | Meaning | Heart rate range in my data |
|---|---|---|
1 | Could not be read | average_heart_rate is 0 — that is the giveaway |
2 | Inconclusive | 55–84 |
3 | Sinus rhythm | 50–82 |
6 | Low heart rate, not judged | 42–48 |
8 | Atrial fibrillation | 63–97 |
9 | High heart rate, not judged | 125 |
Two errors of mine, because they are the two you are most likely to repeat. I had 1 and 3 swapped and spent a week believing my reader agreed with Apple only 53 % of the time; the tell is that every code-1 row carries no average heart rate at all, because those are recordings the watch could not read. And I had 2 down as atrial fibrillation. It is a plain inconclusive, and calling it AF converts your own interpretation into “the device said so”, which is a different claim entirely and one a cardiologist will rightly throw out.
Part 3
Subtract a moving-average baseline, differentiate, square, integrate over a short window, then apply an adaptive threshold with a 0.25-second refractory period so no beat is counted twice. Decades old; every language has a library.
The irregularity: the proportion of intervals differing from the previous one by more than 5 %. And the spread: the standard deviation of the intervals divided by their mean.
Mine are irregularity above 52 % and spread above 13 %. Measured on the 57 ECGs my watch did classify: in the 22 fibrillations the irregularity runs 73–94 %; in the 35 normal traces, 0–49 %. A clean empty gap with the threshold inside it. The spread does not separate anything: 14–50 % against 1–27 %, heavily overlapped. Do not present two criteria as if both discriminate. In my data only one does, and the spread is a floor against flat traces, nothing more.
Count intervals falling outside half to 1.6 times the median. If more than 15 % do, the answer is unreadable, not fibrillation. Skip this and a bad contact on dry skin hands you a confident, completely false atrial fibrillation. In my data this gate separates 15 genuinely readable traces from 17 unusable ones among the recordings the watch rejected.
Report your output honestly. “My reading of the trace” is a measurement with a method. “The device detected” is a claim about a certified medical feature. They are not the same sentence and only one of them survives contact with a cardiologist.
Part 4
An ECG only exists when you deliberately hold the crown. But when you are still, and especially asleep, the watch records beat-to-beat intervals on its own — roughly every 2 minutes in my case. The same two numbers work on those sessions, and this is where most of your real coverage comes from.
This method sees things the watch’s own alert does not. Of 125 episodes I detected this way, 48 lasted under an hour and 57 consist of only one or two sessions. The watch’s irregular-rhythm notification reported none of them: that alert needs the rhythm to stay abnormal for a long stretch and can arrive up to four hours late.
My total measured burden is 524 hours over 329 days, 6.6 %.
Everything measured this way is a floor, never a figure. The watch samples intermittently; an episode that starts and ends between two readings never existed as far as your data is concerned. When you tell a doctor “I was in fibrillation X % of the time”, the honest sentence is “at least X %”.
Part 5
Do not copy mine. Here is how to derive yours.
Months or years before your symptoms started, or a stretch your cardiologist considers normal. Run the two numbers across every session in it. That distribution is your baseline.
Recordings where a certified device or a doctor said atrial fibrillation. Run the same two numbers on those.
Not both together. In my case the irregularity separates perfectly and the spread does not, and I assumed both worked for weeks before I plotted them. If an indicator overlaps, it is not a criterion and no amount of tuning makes it one — say so rather than picking a cut-off that flatters you.
Across 13,848 sessions, mine fires on 0.65 % of the 6,920 sessions in my clean period of 2021–2024 and on 26.8 % of the 4,052 sessions since onset on 31 October 2025. Always publish that second figure with its start date attached or it reads as a current rate when it is a period average. A criterion that fires on 20 % of a healthy period is detecting noise.
Wherever the watch did give a verdict, does your method agree? Mine agrees on 57 of 57. That number is what earns you the right to apply the same method where the watch stayed silent. Without it you are just asserting.
Part 6
On one night the watch logged five readings over 100 ms between 01:01 and 03:01 and my pipeline called it two hours of arrhythmia. A chest strap covered the same window with 17,927 consecutive beats and showed no fibrillation at all. It was optical-sensor noise. If you have a strap recording covering the window, that is the truth and the watch is discarded.
My confirmed ones hold high values continuously for hours — one ran from 155 to 326 without dropping below 100 for twenty-three hours. A pattern of high, normal, high, normal every fifteen minutes is not fibrillation; hearts do not enter and leave every sixteen minutes. Interleaved normal readings mean suspect the sensor.
After a night of beer my watch returned “poor recording” repeatedly, stopped measuring for three hours, and produced isolated high readings that were not arrhythmia.
My daughter took one with my watch. It is stored under my name, it is a perfectly good recording of a different heart, and nothing about it chirps. You cannot detect this automatically — keep an explicit exclusion list of date, time and whose it is, and apply it in every script that counts.
Check for a calibration step at handover: mine both read 48 bpm within eleven minutes of each other, so the historical series stayed comparable. The pulse cadence is the same on both, about every 3 minutes overnight. But the variability sampling is not — it went from 11 readings a day to 211, and most of that jump arrived by software update on the old watch, five days before I changed anything. The whole measurement is here. The boundary to check is the build, not the device.
I was certain late dinners triggered my fibrillation. Nights with a high 21:00–23:00 pulse showed arrhythmia 33 % of the time against 6 % for the calmest third — a five-fold effect with a clean mechanism. It was backwards. The fibrillation was already running and was itself raising my evening pulse. Filtering to episodes that start after bedtime and excluding nights already in arrhythmia at 21:00 gives 9 %, 6 % and 12 %. Nothing at all. In self-tracking everything is measured on the same body at the same time, so your candidate cause is very often downstream of the outcome. Ask this before you believe any association you find, including the exciting ones. Especially the exciting ones.
Run it rather than read it
Everything above is on this page to be read and judged. This block is the same method written as a job for a machine: paste it into whatever you use, say what device you have and what you have managed to export, and work through it together. It carries the source link and a line telling the model what it is looking at, so nothing is lost when it leaves this page.
SOURCE: https://afibcases.com/reading-your-own-heart-data
WHAT THIS IS: a working method one person - a patient with atrial
fibrillation, not a clinician or an engineer - verified on their own
Apple Watch data in October 2026 on iOS 27. Not medical advice, and
not a substitute for a certified device.
You are helping me extract and read my own heart data. Work through
the steps below with me, tell me where my setup differs from this one,
and say plainly when a step will not apply to me.
GETTING THE DATA OUT
- Only an ENCRYPTED local iPhone backup contains healthdb_secure.sqlite.
An unencrypted one does not include health data at all.
- The backup requires the phone UNLOCKED when it starts; locked gives
MBErrorDomain/208 "Device locked" (NOT a disk space problem).
- Dead ends, all personally verified: Shortcuts cannot read ECGs or
beat-to-beat intervals (only flat sample properties); the "Export All
Health Data" button cannot be triggered programmatically by any
means; no consumer app exports raw beat-to-beat intervals
automatically; Health Auto Export does export ECG waveforms but
cannot guarantee a schedule (same iOS dependency); a watch app gains
nothing, because the watch only stores averaged pulse during movement.
- Three failure modes, all of which you will meet:
(a) MBErrorDomain/208 = phone locked, NOT disk space
(b) the process hangs alive at 0% CPU writing nothing, and blocks
every later attempt because scripts skip when one is running
(c) WORST: it prints "Backup Successful" and the health database
inside is frozen days earlier, with NO error at all. Never trust
the exit status - always check how recent the newest sample
INSIDE the database is.
- Realistic reliability: 5 successful automated backups in 21 days
from 137 attempts. Treat it as a manual monthly job.
DATABASE
- Timestamps are seconds since 2001-01-01, not 1970.
- quantity_samples JOIN samples ON data_id. data_type: 3=body mass,
5=heart rate (x60 for bpm), 7=steps, 139=HRV SDNN.
- category_samples: 156=irregular rhythm notification, 147=low HR event.
- ecg_samples: voltage_payload, private_classification,
average_heart_rate. Waveform is protobuf, tag byte 0x1d + 4-byte
little-endian float, 15,360 samples at 512 Hz over 30 s.
- data_provenances.origin_product_type identifies the watch model and
origin_build the exact watchOS build. The BUILD matters more than
the model - see the sampling note below.
- Classification codes, undocumented and derived empirically:
1=unreadable (average_heart_rate is 0), 2=inconclusive, 3=sinus,
6=low HR not judged, 8=atrial fibrillation, 9=high HR not judged.
READING A TRACE
- Beat detection: moving-average baseline subtraction, derivative,
squaring, windowed integration, adaptive threshold, 0.25 s
refractory period.
- Two numbers on the intervals between beats: the proportion differing
>5% from the previous one ("irregularity"), and the coefficient of
variation ("spread"). His thresholds: 52% and 13%, both exceeded.
- CRITICAL: only ONE of those two discriminates. Measured on the 57
ECGs the watch classified - irregularity: normal 0-49%, fibrillation
73-94%, a clean empty gap with the 52% threshold inside it. Spread:
normal 1-27%, fibrillation 14-50%, heavily overlapped and useless as
a discriminator. The spread is only a floor against flat traces.
Check which indicator works in YOUR data before assuming both do.
- Quality gate FIRST: >15% of intervals outside 0.5-1.6x the median
means "unreadable", not "fibrillation".
- Derive your own thresholds from your own clean period and your own
confirmed events; check EACH indicator separately for overlap; then
validate against the device wherever it did give a verdict. His
agreement: 57 of 57.
- Volume check across 13,848 sessions: fires on 0.65% of the 6,920
sessions in a clean period (2021-2024) and 26.8% of the 4,052
sessions since onset on 31 Oct 2025.
- Beat-to-beat detection found 125 episodes, 48 under an hour, 57 of
only one or two sessions. Total burden 524 h over 329 days = 6.6%,
and that is a FLOOR.
TRAPS
- A chest strap overrides the watch wherever it covers the window.
- A real episode does not alternate high/normal every 15 minutes.
- Dehydration and dry skin create false high readings.
- Someone else's ECG in your account is undetectable automatically.
- Changing watch can shift your baseline - and so can a software
update. On this subject's data the plain pulse cadence was identical
across watches (~3 min overnight) but HRV sampling went from 11/day
to 211/day, and most of that jump came from a watchOS build on the
OLD hardware, five days before the device changed. Check the build,
not just the model.
- Check the direction of causality before believing any association.
- All burden measured this way is a FLOOR, never a figure.
MY SITUATION:
[what device you have, what you are trying to find out, what you have
already managed to export]
Help me do this on my own data. Tell me where my setup differs from
this one and which steps therefore will not apply.
It gets your own numbers out and makes them readable. It cannot tell you whether what you find is common, whether anyone else’s watch behaves the same way, or what happened to people who went on to have an ablation. One person’s method plus one person’s data is still one person.
That is what the rest of this site is for.