Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.
Before you start
An iPhone, and the Health Auto Export app from the App Store. Its REST API
automations may need the app’s paid tier. That is the app’s paywall, not Postern’s.
Remote access, set up first. Postern only answers requests from the machine it
runs on. Your phone is a different machine, so
open two ports to your own devices first.
Tailscale’s private tier is enough. Nothing has to be public.
Postern is up, with the Console open at http://localhost:8787. Otherwise
install Postern first. Allow about 10 minutes, plus your first
export.
Your password manager open. The Console shows the device secret once.
1
Create the ingest endpoint in the Console
Open the Console at http://localhost:8787. Go to Sources → Add a source →
Apple Health.
The Apple Health card, before you press the button.
Postern cannot show you this secret again. It keeps a scrambled copy it can check
against, never the secret itself. If you lose it, press Rotate secret on the
connection. That makes a new secret and stops every device that still sends the old one.
Open your password manager before you press the button.
Press Create your ingest endpoint. The card returns two values, then reads
waiting for the first push:
Ingest path — /api/ingest/<connection-id>
Device secret — shown once
Copy the secret into your password manager now.The ingest path is not a web address. Postern is never told your remote-access address,
so you build the address yourself:
On the machine Postern runs on, run tailscale status. Read its Tailscale name. It
looks like gateway.example-tailnet.ts.net.
Put https://, that name and :8443 in front of the path, exactly as the Console
printed it. Port 8443 is the port you opened in
Open two ports to your own devices.
Copy that address. It goes into your phone in step 3.Any address you put in front of the path works. The secret is what protects it.
The ingest route is the one route Postern serves without a host check.
The Console prints the same example with its own placeholder host,
your-mac.tailnet.ts.net:8443. Both host names are fake. Use yours.
Leave this screen and the address stays. The connection page shows it again under
Delivery → Ingest endpoint, with a copy control. Only the secret is one-time.Come back here once a connection exists and the card reads A device is already pushing
Health data here. Create another endpoint only if you’re adding a second device. Tick
Yes — create a second ingest endpoint. and the button reads Create another
endpoint. Take it literally: that is a second connection for a second device, not a
repair. To fix the phone you already have, use Rotate secret instead.
2
Install Health Auto Export on the iPhone
On the iPhone, open the App Store and install Health Auto Export. Open the app.On first launch it asks for access to Apple Health. Grant every category you want
exported. Without that access every export is empty, and every step after this one
still looks like it worked.The app carries an Automations tab. The next three steps happen there.
Any program that can POST Postern’s JSON body with the secret in a header works. The
Console’s Apple Health card carries that body under The JSON body Postern expects,
with a Copy the template button. The same card describes an iPhone Shortcut built
from a Get Contents of URL step. Four rules decide whether a payload lands:
Each metrics[].name becomes a metric type.
The metric named sleep_analysis becomes a sleep session.
Every entry under workouts[] needs its own id.
Dates are yyyy-MM-dd HH:mm:ss ±hhmm, not ISO. Postern skips a point it cannot
date. It never stores one wrong.
The rest of this page uses the app. That is the path that runs in production.
3
Build the metrics automation
Open the Automations tab and press New Automation. Set Automation Type to
REST API. The automation screen opens.Put the address you copied in step 1 into URL.Under Add Headers, add one header. Its name is Authorization. Its value is
Bearer <your secret> — the word Bearer, one space, then the secret from step 1. A header
named X-PCI-Webhook-Secret with the secret alone as its value works too, and it is the
one the Console names.Under Data Type Settings, open Data Type and choose health metrics. An exported
automation records that choice as "exportDataType": "healthMetrics".
Set the export version to v2 before you save. Postern reads the shape v2 sends: one
data object that holds metrics and workouts. Postern refuses a body that carries no
data object at all. Nobody has ever tested a push from an older export version, so
leave it on v2.
Set the rest to match the phone that runs in production. The names below are the ones
Health Auto Export writes into an exported automation, with the values that phone exports:
Automations, New Automation, Automation Type, REST API, URL, Add
Headers, Data Type Settings, Data Type and Timeout Interval are the app’s
own words, read off a capture of the phone in production on 1 August 2026.Nobody has recorded what the app calls the settings in the block above. Those are the key
names an exported automation uses. Match them by what they do, and trust your screen over
this page. requestTimeout is the app’s Timeout Interval field.
Select every metric. A day of metrics stays small. The app writes each name into the
automation one by one, and the phone in production lists 113 of them. It stores a list, not
a wildcard, so an app update that adds a new metric does not pick it up on its own — come
back and tick it.Leave exportFileLength on day and batchRequests off. Postern refuses any
single push over 25 MB with {"error":"payload too large"}, and nothing in a refused push
lands. One day at a time has never come close on the phone in production.
Both aggregation settings change what an agent gets. Turn them on anyway — this is the
pair the phone in production runs, and it has been healthy for weeks — but know the price.aggregateData: true changes the names. A point that carries a plain qty becomes one
row under <name>. A point that carries Min, Avg or Max instead becomes one row per
statistic present: <name>_min, <name>_avg, <name>_max. In production this happens to
exactly one metric, heart rate: that store holds heart_rate_min, heart_rate_avg and
heart_rate_max, and no heart_rate at all. An agent that asks for heart_rate gets
nothing back. Postern tags every fanned row aggregation: daily_total, even when the
value covers one minute. Never sum those rows across a day.aggregateSleep: true costs you the sleep numbers. Postern reads a night’s length from
two fields that the aggregated shape leaves at 0, so a sleep session stores
time_asleep_seconds0, time_in_bed_seconds0 and an empty efficiency_pct. The
start and the end are right, and the real total survives under raw as totalSleep, in
hours. An agent works the night’s length out from the start and the end.
The automation exists once it appears in the automations list. Nothing reaches Postern
until it runs.
An exported automation carries your live address and secret in its JSON, in plain text,
on disk wherever you export it. Share one and you hand someone a live key to your health
data. You cannot unshare a secret. Remove the URL and the headers before a config leaves
your phone, or rotate the secret afterwards.
4
Build the workouts automation
In the same Automations tab, press New Automation and set Automation Type to
REST API again. Copy every setting from step 3 — the same address, the same header, and
the whole block. Then change the four that differ:
On screen that is three moves: open Data Type and choose workouts, turn health metrics
off in the include list, and turn workouts on. Then set Timeout Interval to 60.includeWorkoutMetadata and includeRoutes stay true in both automations. They do
nothing in the metrics one, which carries no workouts.You now have two automations, both on Automation TypeREST API and both on the same
address: one exports health metrics, one exports workouts. Health Auto Export exports one
data type per automation. With one automation, workouts never arrive.Sleep rides the metrics automation, not this one. Health Auto Export exports sleep as
the metric sleep_analysis, and Postern turns that metric into sleep sessions.Postern stores workout routes and workout metadata in raw only. They are not columns
an agent can query, and they make each push larger. Leave them on anyway: the phone in
production has run includeRoutes and includeWorkoutMetadata on for weeks, one day per
push, and no push has been refused on size.
5
Set the schedule, then run one automation by hand
Set each automation’s schedule. Hourly is a good default; the phone in production pushes
every hour. Then run one automation by hand in the Automations tab, rather than wait
for the schedule.In the Console, open Sources → Apple Health. Until the first push lands the
connection carries the badge Awaiting first push. When it lands, that badge
disappears, the line reads Last synced with a time, and the caption beneath reads
Device controlled. A healthy connection carries no badge, and no next time is
predicted.Postern stamps Last synced on every push that arrives, even a push that stored no
records.You can run the automation again safely. Health Auto Export’s export windows overlap on
purpose. Postern gives every sample, sleep session and workout a fixed id. The same
data sent twice updates those rows. It never duplicates them.Keep the gap between pushes under 48 hours. After 48 hours without a push the connection
reads Stale. Stale is advice, not a fault. Your phone sends data to Postern when it
decides to. Postern cannot ask for it, so there is no Sync now button, and Postern
never calls a device Unreachable.
Nothing arrives, and nothing on the machine Postern runs on explains why. Every
rejection at the ingest endpoint answers with the same 401 and the same body,
{"error":"unauthorized"}. The answer never says which cause it was, so check all
four:
The address carries the connection id the Console printed.
The header holds the secret exactly.
The connection is not paused. Pause receiving stops new pushes. Data already
stored still answers.
Console → Sources: one Apple Health connection, not two.
The connection page: Samples cached reads above zero.
The connection’s Delivery panel: Shared secret reads •••••••••••• set and a relative
time — •••••••••••• set 3 days ago, or just now on a secret you made this minute. It is not a
calendar date. It must not read no secret minted yet, and the button beside it must read
Rotate secret, not Mint secret.
Health Auto Export → Automations: two automations, both on Automation TypeREST API and both on the same address. One exports health metrics, one exports
workouts.
{"error":"unauthorized"}, or nothing arrives at all
Check all four causes; they answer identically. The address carries the connection id the Console printed; the header holds the secret exactly; the connection is not paused (Resume receiving on its State row); the phone reaches port 8443.
The connection reads Last synced, but Samples cached stays at 0
The push arrived and stored nothing. Check the export version reads v2 in both automations, then run one by hand.
{"error":"payload too large"}
The push passed 25 MB, and none of it landed. Set the export file length back to one day (exportFileLength: "day") and turn batch requests off (batchRequests: false) in both automations. Postern records nothing for a push it refuses on size — not even in the record. The app’s own answer on the phone is the only place you will see this.
You did not copy the secret, or you shared an exported automation that held it
Open the connection → Delivery → Rotate secret. Every device on the old secret stops. Paste the new secret into both automations.
Your phone sends health data straight to Postern, with no cloud in between. Postern
refuses a push that does not carry the correct secret.An agent can read what your phone has sent since you set this up: sleep, workouts and
every metric you selected. Two of those arrive renamed or thinned by the settings this
page prescribes — what aggregateData and aggregateSleep
cost. There is no history to backfill. Health is read-only here —
a key granted health reads this data, and there is nothing to act on.Your phone decides when data moves. If the data looks old, run the automation on your
phone. The secret lives in those two automations until you rotate it. Rotate it and
every device on the old secret stops. Pause receiving stops new pushes; data already
stored still answers. A paused connection refuses a push exactly as a wrong secret does.
your own Google Cloud app + one app password · about 20 minutes · weekly re-consent until you
publish to production
Connect an agent
a key you created, granted health · a few minutes, plus a restart of the agent · one config
block to paste
{"docsConfig":{"theme":"sequoia","$schema":"https://mintlify.com/docs.json","name":"Postern","colors":{"primary":"#E7A42A","light":"#E7A42A","dark":"#B83E15"},"favicon":"https://mintcdn.com/postern/L5OSSMQX3JjXD-Ob/favicon.svg?fit=max\u0026auto=format\u0026n=L5OSSMQX3JjXD-Ob\u0026q=85\u0026s=acbbe5732970fc6bf76b003f012843f1","appearance":{"default":"light","strict":false},"background":{"color":{"light":"#F5EDDA","dark":"#151F19"}},"navbar":{"links":[{"href":"https://getpostern.com","label":"getpostern.com"}],"primary":{"type":"github","href":"https://github.com/getpostern/postern"}},"footer":{"socials":{"website":"https://getpostern.com","github":"https://github.com/getpostern/postern"}},"fonts":{"heading":{"family":"Cormorant Garamond","source":"fonts/cormorant-garamond-var.woff2","format":"woff2"},"body":{"family":"Source Serif 4","source":"fonts/source-serif-4-var.woff2","format":"woff2"}},"styling":{"codeblocks":"system","latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"postern","hasAudioNarration":false,"actualSubdomain":"postern","customDomains":["docs.getpostern.com"],"gitSource":{"type":"github","owner":"getpostern","repo":"docs","deployBranch":"main","contentDirectory":"","isPrivate":false,"mountPath":"","label":""},"chroma":{"latestBuiltDeploymentHistoryId":"6a77bc437948e45f24bff423","collectionId":"465e71f9-edfd-4e92-a7b2-36446667a983","liveDeploymentHistoryId":"6a77bc437948e45f24bff423","schemaVersion":"v3"},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6aea219dcf56d9cceaaf5561:success","clientVersion":"0.0.4076","searchFilterCounts":[{"language":"default","tag":"Reference","count":15},{"language":"default","tag":"Connect a source","count":9},{"language":"default","tag":"Start here","count":6},{"language":"default","tag":"MCP tools","count":5},{"language":"default","tag":"Security","count":4},{"language":"default","tag":"Mail, calendar \u0026 contacts","count":3},{"language":"default","tag":"Agents, keys and grants","count":3},{"language":"default","tag":"The record","count":3},{"language":"default","tag":"Health","count":2},{"language":"default","tag":"Money","count":2}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"comment-mode","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"@font-face{font-family:\"Courier Prime\";src:url(\"/fonts/courier-prime-400.woff2\") format(\"woff2\");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:\"Courier Prime\";src:url(\"/fonts/courier-prime-700.woff2\") format(\"woff2\");font-weight:700;font-style:normal;font-display:swap}@font-face{font-family:\"Cormorant Garamond\";src:url(\"/fonts/cormorant-garamond-marks.woff2\") format(\"woff2\");font-weight:300 700;font-style:normal;font-display:swap;unicode-range:U+2116, U+2190-2192}@font-face{font-family:\"Cormorant Garamond\";src:url(\"/fonts/cormorant-garamond-italic-marks.woff2\") format(\"woff2\");font-weight:300 700;font-style:italic;font-display:swap;unicode-range:U+2116, U+2190-2192}@font-face{font-family:\"EB Garamond\";src:url(\"/fonts/eb-garamond-marks.woff2\") format(\"woff2\");font-weight:400 800;font-style:normal;font-display:swap;unicode-range:U+2116, U+2190-2192}@font-face{font-family:\"EB Garamond\";src:url(\"/fonts/eb-garamond-italic-marks.woff2\") format(\"woff2\");font-weight:400 800;font-style:italic;font-display:swap;unicode-range:U+2116, U+2190-2192}@font-face{font-family:\"Cormorant Garamond\";src:url(\"/fonts/cormorant-garamond-500-italic.woff2\") format(\"woff2\");font-weight:500;font-style:italic;font-display:swap}@font-face{font-family:\"EB Garamond\";src:url(\"/fonts/eb-garamond-400-italic.woff2\") format(\"woff2\");font-weight:400;font-style:italic;font-display:swap}code,pre,kbd,samp,code span,pre span{font-family:\"Courier Prime\", \"Courier New\", ui-monospace, Menlo, Consolas,\n \"DejaVu Sans Mono\", monospace}@font-face{font-family:\"Source Serif 4\";src:url(\"/fonts/source-serif-4-var.woff2\") format(\"woff2\");font-weight:400 700;font-style:normal;font-display:swap}@font-face{font-family:\"Source Serif 4\";src:url(\"/fonts/source-serif-4-italic-var.woff2\") format(\"woff2\");font-weight:400 700;font-style:italic;font-display:swap}:root{--postern-body: \"Source Serif 4\", \"EB Garamond\", Georgia, serif;--postern-measure: 74ch}#content-area p,#content-area li,#content-area td,#content-area th,#content-area blockquote{font-family:var(--postern-body);font-size:17px;line-height:1.7}#content-area p,#content-area ul,#content-area ol,#content-area blockquote{max-width:var(--postern-measure)}#content-area h2{margin-top:2.75em;margin-bottom:0.6em;letter-spacing:-0.01em}#content-area h3{margin-top:2em;margin-bottom:0.5em}#content-area h2+p,#content-area h3+p{margin-top:0}#content-area table{font-size:15.5px;line-height:1.55}#content-area th{font-size:13px;letter-spacing:0.06em;text-transform:uppercase;opacity:0.72}#content-area td,#content-area th{padding-top:0.6em;padding-bottom:0.6em;vertical-align:baseline}#content-area .prose>table,#content-area table{display:block;overflow-x:auto;max-width:100%}#content-area .callout,#content-area [class*=\"callout\"]{border-width:1px;border-radius:3px}#content-area .callout p,#content-area [class*=\"callout\"] p{font-size:16px;line-height:1.65;max-width:none}#content-area p code,#content-area li code,#content-area td code{font-size:0.94em;padding:0.1em 0.32em;border-radius:2px}#content-area p strong,#content-area li strong,#content-area td strong{font-weight:600;color:var(--postern-strong, inherit)}#content-area h2:has(+p){text-wrap:balance}@media (max-width: 900px){#content-area p,#content-area li,#content-area td,#content-area th,#content-area blockquote{font-size:16px}#content-area h2{margin-top:2.25em}}"}],"page":{"pagePath":"connect/apple-health","slug":"/connect/apple-health","pageMetadata":{"title":"Connect Apple Health","description":"Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.","href":"/connect/apple-health"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{Card,CodeBlock,Columns,Frame,Heading,Info,Note,OptimizedImage,Step,Steps,Table,Warning}=_components;if(!Card)_missingMdxReference(`Card`,true);if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Columns)_missingMdxReference(`Columns`,true);if(!Frame)_missingMdxReference(`Frame`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Info)_missingMdxReference(`Info`,true);if(!Note)_missingMdxReference(`Note`,true);if(!OptimizedImage)_missingMdxReference(`OptimizedImage`,true);if(!Step)_missingMdxReference(`Step`,true);if(!Steps)_missingMdxReference(`Steps`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(Info,{children:[_jsx(_components.p,{children:_jsx(_components.strong,{children:`Before you start`})}),_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`An iPhone, and the Health Auto Export app`}),` from the App Store. Its REST API\nautomations may need the app’s paid tier. That is the app’s paywall, not Postern’s.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Remote access, set up first.`}),` Postern only answers requests from the machine it\nruns on. Your phone is a different machine, so\n`,_jsx(_components.a,{href:`/start/remote-access#your-devices`,children:`open two ports to your own devices`}),` first.\nTailscale’s private tier is enough. Nothing has to be public.`]}),`\n`,_jsxs(_components.li,{children:[_jsxs(_components.strong,{children:[`Postern is up, with the Console open at `,_jsx(_components.code,{children:`http://localhost:8787`}),`.`]}),` Otherwise\n`,_jsx(_components.a,{href:`/start/install`,children:`install Postern`}),` first. Allow about 10 minutes, plus your first\nexport.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Your password manager open.`}),` The Console shows the device secret once.`]}),`\n`]})]}),`\n`,_jsxs(Steps,{children:[_jsxs(Step,{title:`Create the ingest endpoint in the Console`,titleSize:`h2`,id:`create-endpoint`,children:[_jsxs(_components.p,{children:[`Open the Console at `,_jsx(_components.code,{children:`http://localhost:8787`}),`. Go to `,_jsx(_components.strong,{children:`Sources`}),` → `,_jsx(_components.strong,{children:`Add a source`}),` →\n`,_jsx(_components.strong,{children:`Apple Health`}),`.`]}),_jsx(Frame,{caption:`The Apple Health card, before you press the button.`,children:_jsx(OptimizedImage,{src:`https://mintcdn.com/postern/E8R3wfogdGSukifu/images/apple-health-endpoint.png?fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=a50c3d439cf37298d72c30ff3d1ba8f0`,alt:`The Console's Apple Health connect card, headed YOUR INGEST ENDPOINT. Its text: Postern mints a private ingest endpoint and a one-time secret for this device. Nothing is created until you ask. Below the text sits one button, Create your ingest endpoint.`,\"data-og-width\":`576`,width:`576`,\"data-og-height\":`204`,height:`204`,\"data-path\":`images/apple-health-endpoint.png`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/postern/E8R3wfogdGSukifu/images/apple-health-endpoint.png?w=280\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=da67da7730991f82cde970f56e96e3fe 280w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/apple-health-endpoint.png?w=560\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=e19c197654b37094c04f180c967161d6 560w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/apple-health-endpoint.png?w=840\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=ebd1020131ab36f3cd2d6c2c3563c1be 840w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/apple-health-endpoint.png?w=1100\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=853317dce9ce4054545b98f95f6d000e 1100w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/apple-health-endpoint.png?w=1650\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=9a3b9202fb3ce03caaa0b116b84231ee 1650w, https://mintcdn.com/postern/E8R3wfogdGSukifu/images/apple-health-endpoint.png?w=2500\u0026fit=max\u0026auto=format\u0026n=E8R3wfogdGSukifu\u0026q=85\u0026s=000b5efa27cbb512135c8543c1de2584 2500w`})}),_jsx(Warning,{children:_jsxs(_components.p,{children:[`Postern cannot show you this secret again. It keeps a scrambled copy it can check\nagainst, never the secret itself. If you lose it, press `,_jsx(_components.strong,{children:`Rotate secret`}),` on the\nconnection. That makes a new secret and stops every device that still sends the old one.\nOpen your password manager before you press the button.`]})}),_jsxs(_components.p,{children:[`Press `,_jsx(_components.strong,{children:`Create your ingest endpoint`}),`. The card returns two values, then reads\n`,_jsx(_components.em,{children:`waiting for the first push`}),`:`]}),_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Ingest path`}),` — `,_jsx(_components.code,{children:`/api/ingest/\u003cconnection-id>`})]}),`\n`,_jsx(_components.li,{children:_jsx(_components.strong,{children:`Device secret — shown once`})}),`\n`]}),_jsx(_components.p,{children:`Copy the secret into your password manager now.`}),_jsx(_components.p,{children:`The ingest path is not a web address. Postern is never told your remote-access address,\nso you build the address yourself:`}),_jsxs(_components.ol,{children:[`\n`,_jsxs(_components.li,{children:[`On the machine Postern runs on, run `,_jsx(_components.code,{children:`tailscale status`}),`. Read its Tailscale name. It\nlooks like `,_jsx(_components.code,{children:`gateway.example-tailnet.ts.net`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Put `,_jsx(_components.code,{children:`https://`}),`, that name and `,_jsx(_components.code,{children:`:8443`}),` in front of the path, exactly as the Console\nprinted it. Port 8443 is the port you opened in\n`,_jsx(_components.a,{href:`/start/remote-access#your-devices`,children:`Open two ports to your own devices`}),`.`]}),`\n`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`2`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`https://gateway.example-tailnet.ts.net:8443/api/ingest/\u003cconnection-id>`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),_jsx(_components.p,{children:`Copy that address. It goes into your phone in step 3.`}),_jsxs(_components.p,{children:[`Any address you put in front of the path works. The secret is what protects it.\n`,_jsx(_components.a,{href:`/reference/ports#the-push-ingest-route`,children:`The ingest route is the one route Postern serves without a host check`}),`.\nThe Console prints the same example with its own placeholder host,\n`,_jsx(_components.code,{children:`your-mac.tailnet.ts.net:8443`}),`. Both host names are fake. Use yours.`]}),_jsxs(Note,{children:[_jsxs(_components.p,{children:[`Leave this screen and the address stays. The connection page shows it again under\n`,_jsx(_components.strong,{children:`Delivery`}),` → `,_jsx(_components.strong,{children:`Ingest endpoint`}),`, with a copy control. Only the secret is one-time.`]}),_jsxs(_components.p,{children:[`Come back here once a connection exists and the card reads `,_jsx(_components.em,{children:`A device is already pushing\nHealth data here. Create another endpoint only if you’re adding a second device.`}),` Tick\n`,_jsx(_components.em,{children:`Yes — create a second ingest endpoint.`}),` and the button reads `,_jsx(_components.strong,{children:`Create another\nendpoint`}),`. Take it literally: that is a second connection for a second device, not a\nrepair. To fix the phone you already have, use `,_jsx(_components.strong,{children:`Rotate secret`}),` instead.`]})]})]}),_jsxs(Step,{title:`Install Health Auto Export on the iPhone`,titleSize:`h2`,id:`install-app`,children:[_jsxs(_components.p,{children:[`On the iPhone, open the App Store and install `,_jsx(_components.strong,{children:`Health Auto Export`}),`. Open the app.`]}),_jsx(_components.p,{children:`On first launch it asks for access to Apple Health. Grant every category you want\nexported. Without that access every export is empty, and every step after this one\nstill looks like it worked.`}),_jsxs(_components.p,{children:[`The app carries an `,_jsx(_components.strong,{children:`Automations`}),` tab. The next three steps happen there.`]}),_jsxs(Note,{children:[_jsxs(_components.p,{children:[`Any program that can POST Postern’s JSON body with the secret in a header works. The\nConsole’s Apple Health card carries that body under `,_jsx(_components.em,{children:`The JSON body Postern expects`}),`,\nwith a `,_jsx(_components.strong,{children:`Copy the template`}),` button. The same card describes an iPhone Shortcut built\nfrom a `,_jsx(_components.em,{children:`Get Contents of URL`}),` step. Four rules decide whether a payload lands:`]}),_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`Each `,_jsx(_components.code,{children:`metrics[].name`}),` becomes a metric type.`]}),`\n`,_jsxs(_components.li,{children:[`The metric named `,_jsx(_components.code,{children:`sleep_analysis`}),` becomes a sleep session.`]}),`\n`,_jsxs(_components.li,{children:[`Every entry under `,_jsx(_components.code,{children:`workouts[]`}),` needs its own `,_jsx(_components.code,{children:`id`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Dates are `,_jsx(_components.code,{children:`yyyy-MM-dd HH:mm:ss ±hhmm`}),`, not ISO. Postern skips a point it cannot\ndate. It never stores one wrong.`]}),`\n`]}),_jsx(_components.p,{children:`The rest of this page uses the app. That is the path that runs in production.`})]})]}),_jsxs(Step,{title:`Build the metrics automation`,titleSize:`h2`,id:`metrics-automation`,children:[_jsxs(_components.p,{children:[`Open the `,_jsx(_components.strong,{children:`Automations`}),` tab and press `,_jsx(_components.strong,{children:`New Automation`}),`. Set `,_jsx(_components.strong,{children:`Automation Type`}),` to\n`,_jsx(_components.strong,{children:`REST API`}),`. The automation screen opens.`]}),_jsxs(_components.p,{children:[`Put the address you copied in step 1 into `,_jsx(_components.strong,{children:`URL`}),`.`]}),_jsxs(_components.p,{children:[`Under `,_jsx(_components.strong,{children:`Add Headers`}),`, add one header. Its name is `,_jsx(_components.code,{children:`Authorization`}),`. Its value is\n`,_jsx(_components.code,{children:`Bearer \u003cyour secret>`}),` — the word `,_jsx(_components.code,{children:`Bearer`}),`, one space, then the secret from step 1. A header\nnamed `,_jsx(_components.code,{children:`X-PCI-Webhook-Secret`}),` with the secret alone as its value works too, and it is the\none the Console names.`]}),_jsxs(_components.p,{children:[`Under `,_jsx(_components.strong,{children:`Data Type Settings`}),`, open `,_jsx(_components.strong,{children:`Data Type`}),` and choose health metrics. An exported\nautomation records that choice as `,_jsx(_components.code,{children:`\"exportDataType\": \"healthMetrics\"`}),`.`]}),_jsx(Warning,{children:_jsxs(_components.p,{children:[`Set the export version to v2 before you save. Postern reads the shape v2 sends: one\n`,_jsx(_components.code,{children:`data`}),` object that holds `,_jsx(_components.code,{children:`metrics`}),` and `,_jsx(_components.code,{children:`workouts`}),`. Postern refuses a body that carries no\n`,_jsx(_components.code,{children:`data`}),` object at all. Nobody has ever tested a push from an older export version, so\nleave it on v2.`]})}),_jsx(_components.p,{children:`Set the rest to match the phone that runs in production. The names below are the ones\nHealth Auto Export writes into an exported automation, with the values that phone exports:`}),_jsx(CodeBlock,{filename:``,numberOfLines:`23`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`23`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"exportDestination\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"restApi\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"exportDataType\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"healthMetrics\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"exportFormat\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"JSON\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"exportVersion\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"ExportVersion.v2\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"exportFileLength\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"day\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"exportPeriod\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"Default\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"exportAggregation\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"Default\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"batchRequests\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"aggregateData\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"aggregateSleep\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeHealthMetrics\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeWorkouts\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeCycleTracking\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeMedications\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeWorkoutMetadata\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeRoutes\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeECG\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeHeartRateNotifications\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeStateOfMind\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeSymptoms\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"requestTimeout\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`86400`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),_jsxs(Note,{children:[_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Automations`}),`, `,_jsx(_components.strong,{children:`New Automation`}),`, `,_jsx(_components.strong,{children:`Automation Type`}),`, `,_jsx(_components.strong,{children:`REST API`}),`, `,_jsx(_components.strong,{children:`URL`}),`, `,_jsx(_components.strong,{children:`Add\nHeaders`}),`, `,_jsx(_components.strong,{children:`Data Type Settings`}),`, `,_jsx(_components.strong,{children:`Data Type`}),` and `,_jsx(_components.strong,{children:`Timeout Interval`}),` are the app’s\nown words, read off a capture of the phone in production on 1 August 2026.`]}),_jsxs(_components.p,{children:[`Nobody has recorded what the app calls the settings in the block above. Those are the key\nnames an exported automation uses. Match them by what they do, and trust your screen over\nthis page. `,_jsx(_components.code,{children:`requestTimeout`}),` is the app’s `,_jsx(_components.strong,{children:`Timeout Interval`}),` field.`]})]}),_jsx(_components.p,{children:`Select every metric. A day of metrics stays small. The app writes each name into the\nautomation one by one, and the phone in production lists 113 of them. It stores a list, not\na wildcard, so an app update that adds a new metric does not pick it up on its own — come\nback and tick it.`}),_jsxs(_components.p,{children:[`Leave `,_jsx(_components.code,{children:`exportFileLength`}),` on `,_jsx(_components.code,{children:`day`}),` and `,_jsx(_components.code,{children:`batchRequests`}),` off. Postern refuses any\nsingle push over 25 MB with `,_jsx(_components.code,{children:`{\"error\":\"payload too large\"}`}),`, and nothing in a refused push\nlands. One day at a time has never come close on the phone in production.`]}),_jsxs(Note,{children:[_jsx(_components.p,{children:`Both aggregation settings change what an agent gets. Turn them on anyway — this is the\npair the phone in production runs, and it has been healthy for weeks — but know the price.`}),_jsxs(_components.p,{children:[_jsx(_components.code,{children:`aggregateData: true`}),` changes the names. A point that carries a plain `,_jsx(_components.code,{children:`qty`}),` becomes one\nrow under `,_jsx(_components.code,{children:`\u003cname>`}),`. A point that carries Min, Avg or Max instead becomes one row per\nstatistic present: `,_jsx(_components.code,{children:`\u003cname>_min`}),`, `,_jsx(_components.code,{children:`\u003cname>_avg`}),`, `,_jsx(_components.code,{children:`\u003cname>_max`}),`. In production this happens to\nexactly one metric, heart rate: that store holds `,_jsx(_components.code,{children:`heart_rate_min`}),`, `,_jsx(_components.code,{children:`heart_rate_avg`}),` and\n`,_jsx(_components.code,{children:`heart_rate_max`}),`, and no `,_jsx(_components.code,{children:`heart_rate`}),` at all. An agent that asks for `,_jsx(_components.code,{children:`heart_rate`}),` gets\nnothing back. Postern tags every fanned row `,_jsx(_components.code,{children:`aggregation: daily_total`}),`, even when the\nvalue covers one minute. Never sum those rows across a day.`]}),_jsxs(_components.p,{children:[_jsx(_components.code,{children:`aggregateSleep: true`}),` costs you the sleep numbers. Postern reads a night’s length from\ntwo fields that the aggregated shape leaves at `,_jsx(_components.code,{children:`0`}),`, so a sleep session stores\n`,_jsx(_components.code,{children:`time_asleep_seconds`}),` `,_jsx(_components.code,{children:`0`}),`, `,_jsx(_components.code,{children:`time_in_bed_seconds`}),` `,_jsx(_components.code,{children:`0`}),` and an empty `,_jsx(_components.code,{children:`efficiency_pct`}),`. The\nstart and the end are right, and the real total survives under `,_jsx(_components.code,{children:`raw`}),` as `,_jsx(_components.code,{children:`totalSleep`}),`, in\nhours. An agent works the night’s length out from the start and the end.`]})]}),_jsx(_components.p,{children:`The automation exists once it appears in the automations list. Nothing reaches Postern\nuntil it runs.`}),_jsx(Warning,{children:_jsx(_components.p,{children:`An exported automation carries your live address and secret in its JSON, in plain text,\non disk wherever you export it. Share one and you hand someone a live key to your health\ndata. You cannot unshare a secret. Remove the URL and the headers before a config leaves\nyour phone, or rotate the secret afterwards.`})})]}),_jsxs(Step,{title:`Build the workouts automation`,titleSize:`h2`,id:`workouts-automation`,children:[_jsxs(_components.p,{children:[`In the same `,_jsx(_components.strong,{children:`Automations`}),` tab, press `,_jsx(_components.strong,{children:`New Automation`}),` and set `,_jsx(_components.strong,{children:`Automation Type`}),` to\n`,_jsx(_components.strong,{children:`REST API`}),` again. Copy every setting from step 3 — the same address, the same header, and\nthe whole block. Then change the four that differ:`]}),_jsx(CodeBlock,{filename:``,numberOfLines:`6`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`6`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"exportDataType\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"workouts\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeHealthMetrics\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"includeWorkouts\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"requestTimeout\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`60`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),_jsxs(_components.p,{children:[`On screen that is three moves: open `,_jsx(_components.strong,{children:`Data Type`}),` and choose workouts, turn health metrics\noff in the include list, and turn workouts on. Then set `,_jsx(_components.strong,{children:`Timeout Interval`}),` to 60.`]}),_jsxs(_components.p,{children:[_jsx(_components.code,{children:`includeWorkoutMetadata`}),` and `,_jsx(_components.code,{children:`includeRoutes`}),` stay `,_jsx(_components.code,{children:`true`}),` in both automations. They do\nnothing in the metrics one, which carries no workouts.`]}),_jsxs(_components.p,{children:[`You now have two automations, both on `,_jsx(_components.strong,{children:`Automation Type`}),` `,_jsx(_components.strong,{children:`REST API`}),` and both on the same\naddress: one exports health metrics, one exports workouts. Health Auto Export exports one\ndata type per automation. With one automation, workouts never arrive.`]}),_jsxs(_components.p,{children:[`Sleep rides the metrics automation, not this one. Health Auto Export exports sleep as\nthe metric `,_jsx(_components.code,{children:`sleep_analysis`}),`, and Postern turns that metric into sleep sessions.`]}),_jsxs(_components.p,{children:[`Postern stores workout routes and workout metadata in `,_jsx(_components.code,{children:`raw`}),` only. They are not columns\nan agent can query, and they make each push larger. Leave them on anyway: the phone in\nproduction has run `,_jsx(_components.code,{children:`includeRoutes`}),` and `,_jsx(_components.code,{children:`includeWorkoutMetadata`}),` on for weeks, one day per\npush, and no push has been refused on size.`]})]}),_jsxs(Step,{title:`Set the schedule, then run one automation by hand`,titleSize:`h2`,id:`first-push`,children:[_jsxs(_components.p,{children:[`Set each automation’s schedule. Hourly is a good default; the phone in production pushes\nevery hour. Then run one automation by hand in the `,_jsx(_components.strong,{children:`Automations`}),` tab, rather than wait\nfor the schedule.`]}),_jsxs(_components.p,{children:[`In the Console, open `,_jsx(_components.strong,{children:`Sources`}),` → `,_jsx(_components.strong,{children:`Apple Health`}),`. Until the first push lands the\nconnection carries the badge `,_jsx(_components.strong,{children:`Awaiting first push`}),`. When it lands, that badge\ndisappears, the line reads `,_jsx(_components.strong,{children:`Last synced`}),` with a time, and the caption beneath reads\n`,_jsx(_components.strong,{children:`Device controlled`}),`. A healthy connection carries no badge, and no next time is\npredicted.`]}),_jsxs(_components.p,{children:[`Postern stamps `,_jsx(_components.strong,{children:`Last synced`}),` on every push that arrives, even a push that stored no\nrecords.`]}),_jsx(_components.p,{children:`You can run the automation again safely. Health Auto Export’s export windows overlap on\npurpose. Postern gives every sample, sleep session and workout a fixed id. The same\ndata sent twice updates those rows. It never duplicates them.`}),_jsxs(_components.p,{children:[`Keep the gap between pushes under 48 hours. After 48 hours without a push the connection\nreads `,_jsx(_components.strong,{children:`Stale`}),`. `,_jsx(_components.strong,{children:`Stale`}),` is advice, not a fault. Your phone sends data to Postern when it\ndecides to. Postern cannot ask for it, so there is no `,_jsx(_components.strong,{children:`Sync now`}),` button, and Postern\nnever calls a device `,_jsx(_components.strong,{children:`Unreachable`}),`.`]}),_jsxs(Note,{children:[_jsxs(_components.p,{children:[`Nothing arrives, and nothing on the machine Postern runs on explains why. Every\nrejection at the ingest endpoint answers with the same `,_jsx(_components.code,{children:`401`}),` and the same body,\n`,_jsx(_components.code,{children:`{\"error\":\"unauthorized\"}`}),`. The answer never says which cause it was, so check all\nfour:`]}),_jsxs(_components.ol,{children:[`\n`,_jsx(_components.li,{children:`The address carries the connection id the Console printed.`}),`\n`,_jsx(_components.li,{children:`The header holds the secret exactly.`}),`\n`,_jsxs(_components.li,{children:[`The connection is not paused. `,_jsx(_components.strong,{children:`Pause receiving`}),` stops new pushes. Data already\nstored still answers.`]}),`\n`,_jsx(_components.li,{children:`The phone can reach the machine Postern runs on.`}),`\n`]}),_jsxs(_components.p,{children:[_jsx(_components.a,{href:`/reference/ports#the-push-ingest-route`,children:`Every failure answers identically on purpose`}),`.\nA request that never arrives leaves no trace. From the phone, a network problem and a\nwrong secret look the same. The two `,_jsx(_components.code,{children:`curl`}),` checks in\n`,_jsx(_components.a,{href:`/start/remote-access#your-devices`,children:`Open two ports to your own devices`}),` separate them.`]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`confirm-it-works`,children:`Confirm it works`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Console → Sources`}),`: one Apple Health connection, not two.`]}),`\n`,_jsxs(_components.li,{children:[`The connection page: `,_jsx(_components.strong,{children:`Samples cached`}),` reads above zero.`]}),`\n`,_jsxs(_components.li,{children:[`The connection’s `,_jsx(_components.strong,{children:`Delivery`}),` panel: `,_jsx(_components.strong,{children:`Shared secret`}),` reads `,_jsx(_components.code,{children:`•••••••••••• set`}),` and a relative\ntime — `,_jsx(_components.code,{children:`•••••••••••• set 3 days ago`}),`, or `,_jsx(_components.code,{children:`just now`}),` on a secret you made this minute. It is not a\ncalendar date. It must not read `,_jsx(_components.code,{children:`no secret minted yet`}),`, and the button beside it must read\n`,_jsx(_components.strong,{children:`Rotate secret`}),`, not `,_jsx(_components.strong,{children:`Mint secret`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Health Auto Export → Automations`}),`: two automations, both on `,_jsx(_components.strong,{children:`Automation Type`}),`\n`,_jsx(_components.strong,{children:`REST API`}),` and both on the same address. One exports health metrics, one exports\nworkouts.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`if-something-went-wrong`,children:`If something went wrong`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`What you see`}),_jsx(_components.th,{children:`What to do`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`{\"error\":\"unauthorized\"}`}),`, or nothing arrives at all`]}),_jsxs(_components.td,{children:[`Check all four causes; they answer identically. The address carries the connection id the Console printed; the header holds the secret exactly; the connection is not paused (`,_jsx(_components.strong,{children:`Resume receiving`}),` on its `,_jsx(_components.strong,{children:`State`}),` row); the phone reaches port 8443.`]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`The connection reads `,_jsx(_components.strong,{children:`Last synced`}),`, but `,_jsx(_components.strong,{children:`Samples cached`}),` stays at 0`]}),_jsx(_components.td,{children:`The push arrived and stored nothing. Check the export version reads v2 in both automations, then run one by hand.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`{\"error\":\"payload too large\"}`})}),_jsxs(_components.td,{children:[`The push passed 25 MB, and none of it landed. Set the export file length back to one day (`,_jsx(_components.code,{children:`exportFileLength: \"day\"`}),`) and turn batch requests off (`,_jsx(_components.code,{children:`batchRequests: false`}),`) in both automations. Postern records nothing for a push it refuses on size — not even in the record. The app’s own answer on the phone is the only place you will see this.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`You did not copy the secret, or you shared an exported automation that held it`}),_jsxs(_components.td,{children:[`Open the connection → `,_jsx(_components.strong,{children:`Delivery`}),` → `,_jsx(_components.strong,{children:`Rotate secret`}),`. Every device on the old secret stops. Paste the new secret into both automations.`]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`what-you-have-now`,children:`What you have now`}),`\n`,_jsx(_components.p,{children:`Your phone sends health data straight to Postern, with no cloud in between. Postern\nrefuses a push that does not carry the correct secret.`}),`\n`,_jsxs(_components.p,{children:[`An agent can read what your phone has sent since you set this up: sleep, workouts and\nevery metric you selected. Two of those arrive renamed or thinned by the settings this\npage prescribes — `,_jsxs(_components.a,{href:`#metrics-automation`,children:[`what `,_jsx(_components.code,{children:`aggregateData`}),` and `,_jsx(_components.code,{children:`aggregateSleep`}),`\ncost`]}),`. There is no history to backfill. Health is read-only here —\na key granted `,_jsx(_components.strong,{children:`health`}),` reads this data, and there is nothing to act on.`]}),`\n`,_jsxs(_components.p,{children:[`Your phone decides when data moves. If the data looks old, run the automation on your\nphone. The secret lives in those two automations until you rotate it. Rotate it and\nevery device on the old secret stops. `,_jsx(_components.strong,{children:`Pause receiving`}),` stops new pushes; data already\nstored still answers. A paused connection refuses a push exactly as a wrong secret does.`]}),`\n`,_jsx(Heading,{level:`2`,id:`next`,children:`Next`}),`\n`,_jsxs(Columns,{cols:2,children:[_jsx(Card,{title:`Connect Google and Gmail`,href:`/connect/google`,children:_jsx(_components.p,{children:`your own Google Cloud app + one app password · about 20 minutes · weekly re-consent until you\npublish to production`})}),_jsx(Card,{title:`Connect an agent`,href:`/start/connect-an-agent`,children:_jsx(_components.p,{children:`a key you created, granted health · a few minutes, plus a restart of the agent · one config\nblock to paste`})})]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"codeStyling":"system","config":{},"pageMetadata":{"title":"Connect Apple Health","description":"Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.","href":"/connect/apple-health"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Create the ingest endpoint in the Console","slug":"create-endpoint","depth":2,"children":[]},{"title":"Install Health Auto Export on the iPhone","slug":"install-app","depth":2,"children":[]},{"title":"Build the metrics automation","slug":"metrics-automation","depth":2,"children":[]},{"title":"Build the workouts automation","slug":"workouts-automation","depth":2,"children":[]},{"title":"Set the schedule, then run one automation by hand","slug":"first-push","depth":2,"children":[]},{"title":"Confirm it works","slug":"confirm-it-works","depth":2,"children":[]},{"title":"If something went wrong","slug":"if-something-went-wrong","depth":2,"children":[]},{"title":"What you have now","slug":"what-you-have-now","depth":2,"children":[]},{"title":"Next","slug":"next","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"postern","siteName":"Postern","title":"Connect Apple Health - Postern","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-08-07T18:52:32.629Z","originalFileLocation":"connect/apple-health.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"light\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Postern\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Connect Apple Health - Postern\">\u003cmeta data-mint-head property=\"og:description\" content=\"Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Postern\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.getpostern.com/connect/apple-health\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://postern.mintlify.site/_mintlify/api/og/v2?division=Health\u0026amp;title=Connect+Apple+Health\u0026amp;description=Your+iPhone+pushes+health+data+to+an+address+the+Console+creates+for+it.+Postern+never+pulls%2C+and+keeps+what+your+phone+sends+from+the+day+you+start.\u0026amp;theme=4d380beff7db0b8a6a5f24a8\u0026amp;signature=1f75f3d4981349bed28a46fbf5b99ba3a09a65ce0577d60366e7a188dd5977a6\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Connect Apple Health - Postern\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://postern.mintlify.site/_mintlify/api/og/v2?division=Health\u0026amp;title=Connect+Apple+Health\u0026amp;description=Your+iPhone+pushes+health+data+to+an+address+the+Console+creates+for+it.+Postern+never+pulls%2C+and+keeps+what+your+phone+sends+from+the+day+you+start.\u0026amp;theme=4d380beff7db0b8a6a5f24a8\u0026amp;signature=1f75f3d4981349bed28a46fbf5b99ba3a09a65ce0577d60366e7a188dd5977a6\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/postern/qaGaM2kl3nxvvGFE/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Postern\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#E7A42A\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.getpostern.com/connect/apple-health\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/connect/apple-health.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.getpostern.com/#organization\",\"name\":\"Postern\",\"url\":\"https://docs.getpostern.com\"},{\"@type\":\"WebSite\",\"@id\":\"https://docs.getpostern.com#website\",\"name\":\"Postern\",\"url\":\"https://docs.getpostern.com\",\"publisher\":{\"@id\":\"https://docs.getpostern.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.getpostern.com/connect/apple-health#webpage\",\"url\":\"https://docs.getpostern.com/connect/apple-health\",\"name\":\"Connect Apple Health\",\"description\":\"Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.\",\"dateModified\":\"2026-08-07T18:52:32.629Z\",\"isPartOf\":{\"@id\":\"https://docs.getpostern.com#website\"},\"breadcrumb\":{\"@id\":\"https://docs.getpostern.com/connect/apple-health#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.getpostern.com/connect/apple-health#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Connect a source\",\"item\":\"https://docs.getpostern.com/connect/simplefin\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Connect Apple Health\",\"item\":\"https://docs.getpostern.com/connect/apple-health\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.getpostern.com/connect/apple-health#article\",\"headline\":\"Connect Apple Health\",\"name\":\"Connect Apple Health\",\"description\":\"Your iPhone pushes health data to an address the Console creates for it. Postern never pulls, and keeps what your phone sends from the day you start.\",\"url\":\"https://docs.getpostern.com/connect/apple-health\",\"mainEntityOfPage\":{\"@id\":\"https://docs.getpostern.com/connect/apple-health#webpage\"},\"dateModified\":\"2026-08-07T18:52:32.629Z\",\"publisher\":{\"@id\":\"https://docs.getpostern.com/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.getpostern.com#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Postern\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Postern","nav":{"global":null,"groups":[{"group":"Start here","pages":[{"title":"Postern","href":"/index"},{"title":"Install Postern","href":"/start/install"},{"title":"Set up remote access","href":"/start/remote-access"},{"title":"Mint your first key","href":"/start/first-key"},{"title":"Connect an agent","href":"/start/connect-an-agent"},{"title":"Set up with an agent","href":"/start/set-up-with-an-agent"}]},{"group":"Connect a source","pages":[{"group":"Money","pages":[{"title":"Connect SimpleFIN","href":"/connect/simplefin"},{"title":"Connect Plaid","description":"One trip through Plaid's own window links one bank, and each bank becomes its own read-only connection. Plaid bills per bank, so use Plaid for the banks SimpleFIN cannot reach.","href":"/connect/plaid"}]},{"group":"Health","pages":[{"title":"Connect Apple Health","href":"/connect/apple-health"},{"title":"Connect WHOOP","description":"Recovery, sleep, strain, workouts and day-cycles, through a developer app you register once under your own WHOOP account.","href":"/connect/whoop"}]},{"group":"Mail, calendar \u0026 contacts","pages":[{"title":"Connect Google and Gmail","href":"/connect/google"},{"title":"Connect Microsoft","href":"/connect/microsoft"},{"title":"Connect iCloud","href":"/connect/icloud"}]},{"group":"Home","pages":[{"title":"Connect Home Assistant","href":"/connect/home-assistant"}]},{"group":"Agents","pages":[{"title":"Connect ChatGPT","href":"/connect/chatgpt"}]}]},{"group":"Operate","pages":[{"title":"Back up, export and erase","href":"/operate/backup-and-export"}]},{"group":"Reference","pages":[{"group":"Security","pages":[{"title":"Security model","href":"/reference/security-model"},{"title":"Which computers can reach Postern","href":"/reference/ports"},{"title":"The vault and the master key","href":"/reference/vault"},{"title":"Provider sign-ins","href":"/reference/provider-sign-ins"}]},{"group":"Agents, keys and grants","pages":[{"title":"Grants and sectors","href":"/reference/grants-and-sectors"},{"title":"Agent keys: create, expire, revoke, rotate","href":"/reference/agent-keys"},{"title":"Why an agent was refused","href":"/reference/refusals"}]},{"group":"MCP tools","pages":[{"title":"MCP tools","href":"/reference/mcp-primitives"},{"title":"Read data: get_schema, query, get_record","href":"/reference/mcp-reading"},{"title":"Freshness and live reads: describe_context, fetch_live","href":"/reference/mcp-freshness"},{"title":"Act on a source: list_actions, invoke_action","href":"/reference/mcp-actions"},{"title":"The OAuth bridge, for hosted agents","href":"/reference/oauth-bridge"}]},{"group":"The record","pages":[{"title":"The audit log","href":"/reference/audit-log"},{"title":"Read the record","href":"/reference/read-the-record"},{"title":"What the record cannot promise","href":"/reference/record-limits"}]}]},{"group":"About","pages":[{"title":"Frequently asked questions","href":"/about/faq"}]}]},"basePath":"","title":"Connect Apple Health - Postern","buildId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","deploymentId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","dynamics":["07c022d89b","a74be98ba7"],"chunks":["Frame-NdetNziF.js","DynamicMarkdown-CD8713Yy.js","zoom-image-lightbox-DMoL6lHz.js","lightbox-handle-r17bVUz3.js"]}