# YOU(th) docs, in full > Every page of the docs, one after another. # Overview > What partners get from YOU(th): events, FHIR, a read API and SDKs, and how they fit together. > > Preview: proposed, may change. YOU(th) runs health checkups and turns them into biomarkers and measurements. These docs are for partners who connect their own systems to it: a clinic's software that files results, an insurer's app that shows them, a coaching platform that starts work when a checkup is done. What these pages describe is a preview: proposed, and liable to change before it is live. ## What you get - **Events.** A [webhook](/docs/webhooks) tells your system when something happens to one of your members, such as a completed checkup. Each delivery is signed, and by default carries ids only. - **FHIR.** A checkup is a [FHIR bundle](/docs/data/fhir) and a member a `Patient`, ready to store in a system that speaks FHIR. - **A read API.** Your backend [reads a checkup or a member](/docs/webhooks/catch-up) with a short-lived [token](/docs/authentication), and lists the checkups it missed after an outage. - **SDKs.** The [SDKs](/docs/sdks) bring the guided captures into your own iOS or Flutter app. Planned, and not described here yet: a checkup's documents, such as PDFs, and a way for you to tell YOU(th) when you have new results. ## How it fits together 1. A member completes a checkup. 2. YOU(th) sends your webhook receiver a signed `Checkup.completed` delivery, with the checkup's id and where to read it. 3. Your receiver [verifies the signature](/docs/webhooks/verify) and answers at once. 4. Your backend gets a token with its client credentials, and reads the checkup's bundle from the delivery's `resource_url`. ```json { "event_id": "evt_e560fc965cf89b3d5a8c3ed27406806f", "event_type": "Checkup.completed", "event_version": "2", "occurred_at": "2026-10-01T09:30:00.375000Z", "patient_id": "cfefb422-ac79-4cb9-a849-e80f8a6f21cd", "checkup_id": "64d866f3-0c88-485c-bfe6-3339df1d224a", "resource_url": "https://api.example.com/v1/fhir/checkups/64d866f3-0c88-485c-bfe6-3339df1d224a" } ``` ## How to start 1. Ask your YOU(th) contact for an integration. It comes with a client id and secret for the read API, and a signing secret for your webhook. 2. Follow the [quickstart](/docs/quickstart): receive a test delivery, verify it, read a checkup. 3. Read [Retries and duplicates](/docs/webhooks/retries) before you go live. Hosts in the examples, such as `api.example.com`, are placeholders until the real ones are chosen. # Quickstart > Receive a test event, verify it, and read a checkup's FHIR bundle. > > Preview: proposed, may change. This walks through an integration end to end: a test delivery reaches your receiver, your receiver checks it is from YOU(th), and your backend reads a checkup. You need the three secrets your YOU(th) contact gives you, kept on your server: | Variable | Holds | | ---------------------- | -------------------------------------------------------- | | `YOUTH_CLIENT_ID` | Your integration's client id | | `YOUTH_CLIENT_SECRET` | Its client secret, to get [tokens](/docs/authentication) | | `YOUTH_WEBHOOK_SECRET` | The secret your deliveries are signed with | ## 1. Receive a delivery Give your YOU(th) contact an HTTPS address for your receiver, and ask for a test delivery. YOU(th) sends a `Ping.check` event: signed like every other delivery, and naming no real member. ```typescript import { createServer } from "node:http"; import { verifyDelivery } from "./verify-delivery"; createServer((request, response) => { const chunks: Buffer[] = []; request.on("data", (chunk: Buffer) => chunks.push(chunk)); request.on("end", () => { const body = Buffer.concat(chunks); const signed = verifyDelivery( process.env.YOUTH_WEBHOOK_SECRET ?? "", String(request.headers["x-youth-signature"] ?? ""), body, ); response.writeHead(signed ? 200 : 401).end(); if (signed) console.log(JSON.parse(body.toString("utf8")).event_type); }); }).listen(8080); ``` ## 2. Verify it `verifyDelivery` is on [Verify a delivery](/docs/webhooks/verify). It checks the signature over the raw body, exactly as it arrived, and refuses a delivery more than five minutes old. Your receiver logs `Ping.check` once it works. A `Ping.check` needs nothing more: answer `2xx`, and do nothing else with it. ## 3. Read a checkup When a member completes a checkup, YOU(th) sends `Checkup.completed`, with the address of the checkup's bundle in `resource_url`. Get a token, then read it: ```bash TOKEN=$(curl --silent https://auth.example.com/oauth2/token \ --data grant_type=client_credentials \ --data client_id="$YOUTH_CLIENT_ID" \ --data client_secret="$YOUTH_CLIENT_SECRET" | jq --raw-output .access_token) curl https://api.example.com/v1/fhir/checkups/64d866f3-0c88-485c-bfe6-3339df1d224a \ --header "Authorization: Bearer $TOKEN" ``` The answer is the checkup as a FHIR `Bundle`: the member, the checkup and every measurement. See [FHIR](/docs/data/fhir). ## Next - Answer fast and take each delivery once: [Retries and duplicates](/docs/webhooks/retries). - Know every event and what it carries: [Events](/docs/webhooks). - List what you missed while your receiver was down: [Catch up after an outage](/docs/webhooks/catch-up). # Concepts > Organizations, members, checkups, biomarkers and measurements: the words these docs use. > > Preview: proposed, may change. The words the webhook, the read API and these docs use, in the order a checkup meets them. ## Your organization The partner YOU(th) answers for. Your token says which organization you are, and the read API answers only your organization's members and checkups: anything else answers `404`, as if it did not exist. Your webhook hears only about your own members. ## Member A person whose checkups your organization receives. A member has an id that stays the same, and may have a birth date and a gender. In FHIR terms a member is a `Patient`, because FHIR names any person that way; it does not make them anyone's patient. So the wire keeps FHIR's words: a member's id is `patient_id`, and a new member is the `Patient.registered` event. ## Checkup What a member completes, turned into measurements. Each checkup has a type: `digital`, `lab`, `wearable` or `doctor`. Your YOU(th) contact can limit your webhook to the types you want. ## Measurement and biomarker A measurement is one value a checkup measured, such as a heart rate of 64 bpm. Its biomarker is what it measures, named by a code such as `heart_rate` or `blood_pressure`. A value is a number in its unit, a category such as `low`, or a blood pressure with its systolic and diastolic values. A category is a classification, never a diagnosis. ## Codes are open Biomarker codes are stable, and new ones can appear at any time. Keep a code you do not know rather than refusing the checkup. # Authentication > Get a short-lived token with OAuth2 client credentials, and call the API with it. > > Preview: proposed, may change. Your backend calls the API with a short-lived access token. It gets the token with the standard OAuth2 **client-credentials** grant: your client id and secret in, a token out. No person or browser is involved, and any OAuth2 library that supports the grant does it for you. ## Get a token ```bash curl https://auth.example.com/oauth2/token \ --data grant_type=client_credentials \ --data client_id="$YOUTH_CLIENT_ID" \ --data client_secret="$YOUTH_CLIENT_SECRET" ``` The answer holds the token and how many seconds it lasts: ```json { "access_token": "eyJhbGciOiJSUzI1NiIs…", "expires_in": 300, "token_type": "Bearer" } ``` ## Call the API with it Send the token in the `Authorization` header of every request: ```bash curl https://api.example.com/v1/checkups \ --header "Authorization: Bearer $YOUTH_ACCESS_TOKEN" ``` A token lasts five minutes. Reuse it until shortly before it expires, then get a new one; do not get a token for every request. ### In TypeScript ```typescript const TOKEN_URL = "https://auth.example.com/oauth2/token"; let cached: { token: string; expiresAt: number } | undefined; /** A token for the API, reused until a minute before it expires. */ export async function accessToken(now = Date.now()): Promise { if (cached && now < cached.expiresAt) return cached.token; const response = await fetch(TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: process.env.YOUTH_CLIENT_ID ?? "", client_secret: process.env.YOUTH_CLIENT_SECRET ?? "", }), }); if (!response.ok) throw new Error(`The token was refused: ${response.status}`); const answer: { access_token: string; expires_in: number } = await response.json(); cached = { token: answer.access_token, expiresAt: now + (answer.expires_in - 60) * 1000 }; return cached.token; } ``` ### In Python With [requests-oauthlib](https://requests-oauthlib.readthedocs.io): ```python import os from oauthlib.oauth2 import BackendApplicationClient from requests_oauthlib import OAuth2Session TOKEN_URL = "https://auth.example.com/oauth2/token" client_id = os.environ["YOUTH_CLIENT_ID"] session = OAuth2Session(client=BackendApplicationClient(client_id=client_id)) session.fetch_token( token_url=TOKEN_URL, client_id=client_id, client_secret=os.environ["YOUTH_CLIENT_SECRET"], ) response = session.get("https://api.example.com/v1/checkups") response.raise_for_status() ``` When the token has expired, the session raises `TokenExpiredError`: fetch a new token and try again. ## When a token is refused | Answer | Means | | ------ | ------------------------------------------------------------- | | `401` | No token, or an expired or unknown one: get a new token | | `403` | The token is valid, but this route is not open to your client | A token names your organization, and the API answers only its data: see [Your organization](/docs/concepts#your-organization). ## Keep the secret safe - Keep the client secret on your server, never in an app or a browser. - Ask your YOU(th) contact for a new secret when one may have leaked. A token already issued keeps working until it expires, at most five minutes. > **Not decided yet:** the hosts of the API and of the token endpoint, which the examples show as > `api.example.com` and `auth.example.com`, and a partner portal where your administrators would > create and revoke credentials themselves. Until then, YOU(th) issues them. # Events > What YOU(th) tells your system, and what each delivery carries. > > Preview: proposed, may change. When something happens to one of your organization's members, YOU(th) tells your system with an event: a signed `POST` to the HTTPS address you registered, for each event you subscribed to. A delivery says what happened and where to read it. ## Events | `event_type` | Sent when | Resource | | -------------------- | -------------------------------------------------- | ---------------- | | `Checkup.completed` | A member of yours completed a checkup | `Bundle` (FHIR) | | `Patient.registered` | A person registered as a member of yours | `Patient` (FHIR) | | `Ping.check` | You asked your YOU(th) contact for a test delivery | None | Each event type documents its own `resource`. New event types can appear: answer `2xx` to one you do not handle. When your integration is set up, your YOU(th) contact sets: - your webhook's address; - which events you receive; - which checkup types you receive (`digital`, `lab`, `wearable`, `doctor`). Without a restriction, every type is sent; - what a delivery carries: ids only (recommended), or ids and the resource. See [What a delivery carries](#what-a-delivery-carries). ## The body Every event shares one envelope: ```json { "event_id": "evt_e560fc965cf89b3d5a8c3ed27406806f", "event_type": "Checkup.completed", "event_version": "2", "occurred_at": "2026-10-01T09:30:00.375000Z", "patient_id": "cfefb422-ac79-4cb9-a849-e80f8a6f21cd", "checkup_id": "64d866f3-0c88-485c-bfe6-3339df1d224a", "resource_url": "https://api.example.com/v1/fhir/checkups/64d866f3-0c88-485c-bfe6-3339df1d224a" } ``` | Field | Means | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `event_id` | The event, `evt_` and 32 hex digits (`evt_ping_` and 32 for a `Ping.check`). The same on every retry: deduplicate on it | | `event_type` | From the table above | | `event_version` | The envelope's version, a string. Compare it exactly; a value you do not know means the envelope changed in a way that could break your receiver | | `occurred_at` | When it happened, ISO 8601 in UTC | | `patient_id` | The member | | `checkup_id` | The checkup; only in `Checkup.completed` | | `resource_url` | Where to read the resource: the checkup's bundle, or the member's `Patient` | | `resource` | Only when your deliveries carry the resource: the checkup's FHIR `Bundle`, or the member's `Patient` | - `Patient.registered` has no `checkup_id`; its `resource_url` points at the `Patient`. - `Ping.check` names no real member: its `patient_id` starts with `test_ping_`. It has no `checkup_id`, `resource_url` or `resource`, and uses the same envelope as every other event. Verify it, answer `2xx`, and do nothing else. - Ids are opaque strings. Store them as they are; do not rely on their length or format. - New fields and new event types can appear. Ignore fields you do not know; answer `2xx` to an event you do not handle. ## What a delivery carries | Setting | The body holds | Your system | | ---------------------- | ------------------------------------ | ----------------------------------------------------------------- | | Ids only (recommended) | The fields above, without `resource` | Reads `resource_url` with an [access token](/docs/authentication) | | Ids and the resource | The fields above, with `resource` | Can store the delivery as it is; reads the API only to catch up | - Ids only is recommended: a delivery sent to a wrong address, or kept in a gateway's or a queue's logs, then holds no health data, and every read is checked against your client's access when it happens. - The resource is fine too: there is no extra request per event, and deliveries keep working while the read API is unavailable. - A checkup's bundle is built once, when the event happens. Every retry sends the same bundle, and reading `resource_url` later answers the same bundle. - A `Patient` in a delivery is as it was when the event happened; reading `resource_url` answers it as it is now. ## The request | | | | --------- | --------------------------------------------------------------------------- | | Method | `POST`, to your HTTPS address | | Body | JSON, compact, keys sorted, UTF-8. Verify the raw bytes; never re-serialize | | Success | Any `2xx` within ten seconds | | Redirects | Not followed. A `3xx` counts as a failure that is not retried | | Order | None. Deliveries can arrive in any order and more than once | Headers (their names are case-insensitive): | Header | Holds | | -------------------------- | --------------------------------------------------------------------------------- | | `X-youth-event-id` | The `event_id` | | `X-youth-event-type` | The `event_type` | | `X-youth-event-version` | The `event_version` | | `X-youth-timestamp` | When this attempt was sent, ISO 8601 in UTC | | `X-youth-delivery-id` | This attempt: it changes on every retry. Do not deduplicate on it | | `X-youth-delivery-attempt` | `1` for the first attempt, then `2`, `3` and so on | | `X-youth-signature` | `t=,v1=`: see [Verify a delivery](/docs/webhooks/verify) | > **Not decided yet:** the `Content-Type` of deliveries, and the value of `event_version` when the > webhook goes live; the examples show `2`. Parse the body as JSON whatever the header says, and > compare `event_version` as a string. Verify every delivery before you act on it, and answer fast: see [Retries and duplicates](/docs/webhooks/retries). ## Examples The bodies below are pretty-printed for reading. On the wire a body is compact, with its keys sorted, as in the first example; verify the bytes as you received them. ### A whole request, ids only This request is really signed: the fixture secret on [Verify a delivery](/docs/webhooks/verify#try-it) verifies it. ```http POST /youth-webhook HTTP/1.1 Host: partner.example.com X-youth-event-id: evt_e560fc965cf89b3d5a8c3ed27406806f X-youth-event-type: Checkup.completed X-youth-event-version: 2 X-youth-timestamp: 2026-10-01T09:30:02Z X-youth-delivery-id: dlv_4354549c5be143f4b3e2ef0b30eccb02_1 X-youth-delivery-attempt: 1 X-youth-signature: t=1790847002,v1=cf7f342026cf7c2997b6af983998c8eb9c9e3c5deb2e84cc9adab60948caf378 {"checkup_id":"64d866f3-0c88-485c-bfe6-3339df1d224a","event_id":"evt_e560fc965cf89b3d5a8c3ed27406806f","event_type":"Checkup.completed","event_version":"2","occurred_at":"2026-10-01T09:30:00.375000Z","patient_id":"cfefb422-ac79-4cb9-a849-e80f8a6f21cd","resource_url":"https://api.example.com/v1/fhir/checkups/64d866f3-0c88-485c-bfe6-3339df1d224a"} ``` ### `Checkup.completed` with the resource The same body, with the checkup's bundle in `resource`. Shortened here: [FHIR](/docs/data/fhir) has the whole bundle. ```json { "event_id": "evt_e560fc965cf89b3d5a8c3ed27406806f", "event_type": "Checkup.completed", "event_version": "2", "occurred_at": "2026-10-01T09:30:00.375000Z", "patient_id": "cfefb422-ac79-4cb9-a849-e80f8a6f21cd", "checkup_id": "64d866f3-0c88-485c-bfe6-3339df1d224a", "resource_url": "https://api.example.com/v1/fhir/checkups/64d866f3-0c88-485c-bfe6-3339df1d224a", "resource": { "resourceType": "Bundle", "id": "90970514-fbcb-4c07-aee3-3778f4dc594a", "type": "collection", "timestamp": "2026-10-01T09:30:00Z", "entry": [ { "fullUrl": "urn:uuid:c47f1291-702c-42a1-a0f6-bbf4cb4eee15", "resource": { "resourceType": "Observation", "id": "119d03ca-128d-4a26-8924-fa76fa92fbfe", "status": "final", "code": { "coding": [ { "system": "https://example.com/fhir/CodeSystem/biomarkers", "code": "heart_rate" } ] }, "subject": { "reference": "urn:uuid:797790be-4a7b-4aeb-a554-e2685ee57942" }, "effectiveDateTime": "2026-10-01T09:24:00Z", "valueQuantity": { "value": 64, "unit": "bpm", "system": "http://unitsofmeasure.org", "code": "/min" } } } ] } } ``` ### `Patient.registered`, ids only ```json { "event_id": "evt_3b9e0f6a1c2d4e5f8a7b6c5d4e3f2a1b", "event_type": "Patient.registered", "event_version": "2", "occurred_at": "2026-10-01T08:12:40.120000Z", "patient_id": "cfefb422-ac79-4cb9-a849-e80f8a6f21cd", "resource_url": "https://api.example.com/v1/fhir/Patient/cfefb422-ac79-4cb9-a849-e80f8a6f21cd" } ``` ### `Ping.check` ```json { "event_id": "evt_ping_9f1c2b7e4d3a4c5b8e6f0a1b2c3d4e5f", "event_type": "Ping.check", "event_version": "2", "occurred_at": "2026-10-01T09:00:00.000000Z", "patient_id": "test_ping_4a7c9e21" } ``` # Verify a delivery > Check each delivery's signature, in Node or Python. > > Preview: proposed, may change. Anyone can send a request to your receiver's address. A delivery from YOU(th) carries a signature only someone with your signing secret could make; check it before you act on a delivery. ## How a delivery is signed - Your signing secret is a string that starts with `wh_sec_live_` (or `wh_sec_test_` for a test integration). YOU(th) shows it once, when your integration is set up. - The key is the whole secret, prefix included, as UTF-8 bytes. It is not base64, and is not decoded. - The signed message is the `t` value of `X-youth-signature`, a dot, and the raw body exactly as it arrived: `.`. - The signature is the HMAC-SHA256 of that message, as lowercase hex, after `v1=`. - Each delivery carries one signature. When your secret is replaced, YOU(th) agrees a switch-over time with you; around it, accept a signature made with either secret. ## Verify it Compute the signature over the raw body, before any JSON parsing, and compare it in constant time. Refuse a delivery whose `t` is more than 300 seconds from your clock, so a recorded request cannot be played again later. ```typescript import { createHmac, timingSafeEqual } from "node:crypto"; /** Whether a delivery was signed with your secret within the last five minutes. */ export function verifyDelivery( secret: string, signatureHeader: string, rawBody: Buffer, now = Math.floor(Date.now() / 1000), ): boolean { const fields = new Map( signatureHeader.split(",").map((part) => { const at = part.indexOf("="); return [part.slice(0, at), part.slice(at + 1)] as const; }), ); const timestamp = fields.get("t") ?? ""; if (!/^\d+$/.test(timestamp) || Math.abs(now - Number(timestamp)) > 300) return false; const expected = createHmac("sha256", secret) .update(Buffer.concat([Buffer.from(`${timestamp}.`), rawBody])) .digest(); const supplied = Buffer.from(fields.get("v1") ?? "", "hex"); return supplied.length === expected.length && timingSafeEqual(supplied, expected); } ``` ```python import hashlib import hmac import time from typing import Optional def verify_delivery( secret: str, signature_header: str, raw_body: bytes, now: Optional[int] = None, ) -> bool: """Whether a delivery was signed with your secret within the last five minutes.""" fields = dict(part.split("=", 1) for part in signature_header.split(",") if "=" in part) timestamp = fields.get("t", "") if not timestamp.isdigit(): return False if abs((int(time.time()) if now is None else now) - int(timestamp)) > 300: return False signed = f"{timestamp}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(fields.get("v1", ""), expected) ``` Answer `401` to a delivery that fails, and do nothing else with it. That answer is not retried. ## Try it This request was signed with the fixture secret `wh_sec_test_Zm9vYmFyYmF6cXV4LWV4YW1wbGUtc2VjcmV0LXZhbHVl`. Your check should accept it when your clock is set to its `t`, and refuse it when one byte of the body changes. ```http POST /youth-webhook HTTP/1.1 Host: partner.example.com X-youth-event-id: evt_e560fc965cf89b3d5a8c3ed27406806f X-youth-event-type: Checkup.completed X-youth-event-version: 2 X-youth-timestamp: 2026-10-01T09:30:02Z X-youth-delivery-id: dlv_4354549c5be143f4b3e2ef0b30eccb02_1 X-youth-delivery-attempt: 1 X-youth-signature: t=1790847002,v1=cf7f342026cf7c2997b6af983998c8eb9c9e3c5deb2e84cc9adab60948caf378 {"checkup_id":"64d866f3-0c88-485c-bfe6-3339df1d224a","event_id":"evt_e560fc965cf89b3d5a8c3ed27406806f","event_type":"Checkup.completed","event_version":"2","occurred_at":"2026-10-01T09:30:00.375000Z","patient_id":"cfefb422-ac79-4cb9-a849-e80f8a6f21cd","resource_url":"https://api.example.com/v1/fhir/checkups/64d866f3-0c88-485c-bfe6-3339df1d224a"} ``` ## Read the raw body Most web frameworks parse JSON before your code runs. Parsed and written again, the body is no longer the bytes YOU(th) signed, and the signature never matches. Read the body as raw bytes on your webhook route, verify it, then parse it. # Retries and duplicates > Answer fast, take each event once, and know when YOU(th) tries again. > > Preview: proposed, may change. YOU(th) delivers every event at least once. A delivery your receiver does not take comes again, and one it took can come again too, after a network error. Build your receiver for both. ## Answer fast A delivery counts as taken when your receiver answers any `2xx` within ten seconds. 1. [Verify the signature](/docs/webhooks/verify). 2. If you have seen its `event_id` before, answer `200` and stop. 3. Store the event, answer `200`, and do the work afterwards, outside the request. Answering fast matters more than finishing the work: a receiver that reads the checkup before it answers can run past ten seconds, and then the same event comes again. ## When YOU(th) tries again | Answer | Retried | | ---------------------------------------------- | ------- | | A timeout, a network error, `429` or any `5xx` | Yes | | Any other `4xx`, or any `3xx` | No | Answer `4xx` only when a delivery can never be taken, such as one whose signature fails. YOU(th) makes up to eight attempts. The waits between them double from about one minute: about 1, 2, 4, 8, 16, 32 and 64 minutes, each varied by up to 20%, about two hours in all. After the eighth attempt, YOU(th) stops trying that event. Your YOU(th) contact can send it again on request, and the read API [lists what you missed](/docs/webhooks/catch-up). ## Take each event once An event's `event_id`, also in the `X-youth-event-id` header, stays the same on every attempt. Keep the ids you have handled, for a few days at least, and skip one you have seen. Do not deduplicate on `X-youth-delivery-id`, which changes on every attempt. ## Order Deliveries can arrive in any order: a retry of an older event can come after a newer one. Read the current state from the API rather than relying on the order events came in. ## Test your receiver Ask your YOU(th) contact for a `Ping.check` delivery at any time. It is signed like every other and names no real member, so your receiver can answer it without reading anything. # Catch up after an outage > Read a checkup or a member with the read API, and list the checkups your system missed. > > Preview: proposed, may change. A webhook is a hint, not the record. Your backend reads the record from the read API: a checkup's bundle, a member as they are now, and the checkups you missed while your receiver was down. ## The read API Your backend calls it with an OAuth2 client-credentials token, as [Authentication](/docs/authentication) describes: one client per integration, and tokens that last five minutes. | Route | Answers | | ------------------------------------------------ | ---------------------------------------------------------------------------- | | `GET /v1/fhir/checkups/{checkup_id}` | The checkup's FHIR `Bundle`: the same one its webhook carried or would carry | | `GET /v1/fhir/Patient/{patient_id}` | The member's FHIR `Patient`, as it is now | | `GET /v1/checkups?completed_from=&after=&limit=` | Your completed checkups, a page at a time, to catch up after an outage | A checkup or member that is not your organization's answers `404`, as if it did not exist. What the bundle and the `Patient` hold is on [FHIR](/docs/data/fhir). > **Not decided yet:** the hosts and exact paths; what each item of the catch-up list holds; whether > the list goes back before your webhook was switched on; the error body; the rate limits. ## List what you missed Ask for the checkups completed since your receiver last took a delivery: ```bash curl "https://api.example.com/v1/checkups?completed_from=2026-10-01T00:00:00Z" \ --header "Authorization: Bearer $YOUTH_ACCESS_TOKEN" ``` Then read the bundle of each checkup you do not have yet. ## Skip what you already have A checkup you stored from a delivery comes again in the list. Skip the checkup ids you already have, as you skip an `event_id` you have seen. ## Make it routine Run the same list on a schedule, every hour for example, from the newest checkup you have. Then an event lost on the way, or a receiver that answered `200` but failed afterwards, costs a delay and never a checkup. # FHIR > A checkup as a FHIR bundle and a member as a Patient: the resources, codes and values. > > Preview: proposed, may change. A checkup reaches you as a FHIR `Bundle`, and a member as a FHIR `Patient`: in a delivery that carries the resource, or from the [read API](/docs/webhooks/catch-up#the-read-api). ```bash curl https://api.example.com/v1/fhir/checkups/64d866f3-0c88-485c-bfe6-3339df1d224a \ --header "Authorization: Bearer $YOUTH_ACCESS_TOKEN" ``` Resources are FHIR R4, checked against the R4B structures. YOU(th) never sends or answers a resource that fails that check. Passing it does not certify a resource against any profile or for clinical use. ## A checkup: `Bundle` A `Bundle` of type `collection`, with a `timestamp`. The bundle only packs the resources together: its own `id` does not name the checkup. | Resource | One for | Holds | | ------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Patient` | The member | `id`, an `identifier`, `active`, and `birthDate` and `gender` where known | | `DiagnosticReport` | The checkup | `id` and `identifier`: the checkup's id. `status` `final`. `category`: the checkup type. `code.text`: the checkup's name. `effectiveDateTime`: when it was completed. `issued`. `result`: its Observations | | `Observation` | Each measurement | The biomarker's code, its value, and `effectiveDateTime`: when it was measured | - Every entry has a `fullUrl` of the form `urn:uuid:`, stable for that resource. References between resources use it, so they resolve inside the bundle. Resource `id`s are YOU(th)'s ids. - A measurement's `code` is the biomarker's code, such as `heart_rate`, in YOU(th)'s biomarker code system; [Biomarkers](/docs/data/biomarkers) lists them. Codes are open: new biomarkers can appear at any time; keep a code you do not know. - A number is a `valueQuantity`: the unit as text, plus `system` `http://unitsofmeasure.org` and the UCUM `code` where the unit is valid UCUM (`bpm` is `/min`, `mmHg` is `mm[Hg]`). - A category, such as a risk level `low`, is a `valueCodeableConcept`, coded in a YOU(th) code system. A category is a classification, never a diagnosis. - Blood pressure is one Observation: code LOINC `85354-9` beside YOU(th)'s `blood_pressure`, with two components, systolic LOINC `8480-6` and diastolic LOINC `8462-4`. When only one side was measured, both components are still there, and the missing one has a `dataAbsentReason` of `unknown` (system `http://terminology.hl7.org/CodeSystem/data-absent-reason`) instead of a value. In this example, shortened, the blood pressure was measured on one side only: ```json { "resourceType": "Bundle", "id": "90970514-fbcb-4c07-aee3-3778f4dc594a", "type": "collection", "timestamp": "2026-10-01T09:30:00Z", "entry": [ { "fullUrl": "urn:uuid:797790be-4a7b-4aeb-a554-e2685ee57942", "resource": { "resourceType": "Patient", "id": "cfefb422-ac79-4cb9-a849-e80f8a6f21cd", "identifier": [ { "system": "https://example.com/fhir/patients", "value": "cfefb422-ac79-4cb9-a849-e80f8a6f21cd" } ], "active": true, "birthDate": "1988-04-12", "gender": "female" } }, { "fullUrl": "urn:uuid:3bc83773-8e40-465d-b194-5b52c797f59d", "resource": { "resourceType": "DiagnosticReport", "id": "64d866f3-0c88-485c-bfe6-3339df1d224a", "identifier": [ { "system": "https://example.com/fhir/checkups", "value": "64d866f3-0c88-485c-bfe6-3339df1d224a" } ], "status": "final", "category": [ { "coding": [ { "system": "https://example.com/fhir/CodeSystem/checkup-type", "code": "digital", "display": "Digital checkup" } ] } ], "code": { "text": "Digital checkup" }, "subject": { "reference": "urn:uuid:797790be-4a7b-4aeb-a554-e2685ee57942" }, "effectiveDateTime": "2026-10-01T09:28:00Z", "issued": "2026-10-01T09:30:00Z", "result": [ { "reference": "urn:uuid:c47f1291-702c-42a1-a0f6-bbf4cb4eee15" }, { "reference": "urn:uuid:d0851610-7047-40c5-a486-a80dacdbe56c" }, { "reference": "urn:uuid:960272da-d72c-413b-92ff-83acdf46709e" } ] } }, { "fullUrl": "urn:uuid:c47f1291-702c-42a1-a0f6-bbf4cb4eee15", "resource": { "resourceType": "Observation", "id": "119d03ca-128d-4a26-8924-fa76fa92fbfe", "status": "final", "code": { "coding": [ { "system": "https://example.com/fhir/CodeSystem/biomarkers", "code": "heart_rate" } ] }, "subject": { "reference": "urn:uuid:797790be-4a7b-4aeb-a554-e2685ee57942" }, "effectiveDateTime": "2026-10-01T09:24:00Z", "valueQuantity": { "value": 64, "unit": "bpm", "system": "http://unitsofmeasure.org", "code": "/min" } } }, { "fullUrl": "urn:uuid:d0851610-7047-40c5-a486-a80dacdbe56c", "resource": { "resourceType": "Observation", "id": "c3a1f0d2-8e4b-5b7a-9f61-2d0e7c5b4a38", "status": "final", "code": { "coding": [ { "system": "https://example.com/fhir/CodeSystem/biomarkers", "code": "blood_pressure" }, { "system": "http://loinc.org", "code": "85354-9" } ] }, "subject": { "reference": "urn:uuid:797790be-4a7b-4aeb-a554-e2685ee57942" }, "effectiveDateTime": "2026-10-01T09:26:00Z", "component": [ { "code": { "coding": [{ "system": "http://loinc.org", "code": "8480-6" }] }, "valueQuantity": { "value": 122, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]" } }, { "code": { "coding": [{ "system": "http://loinc.org", "code": "8462-4" }] }, "dataAbsentReason": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/data-absent-reason", "code": "unknown" } ] } } ] } }, { "fullUrl": "urn:uuid:960272da-d72c-413b-92ff-83acdf46709e", "resource": { "resourceType": "Observation", "id": "cd127997-4727-4795-b34c-227ac825d3e0", "status": "final", "code": { "coding": [ { "system": "https://example.com/fhir/CodeSystem/biomarkers", "code": "high_fasting_glucose_risk" } ] }, "subject": { "reference": "urn:uuid:797790be-4a7b-4aeb-a554-e2685ee57942" }, "effectiveDateTime": "2026-10-01T09:25:00Z", "valueCodeableConcept": { "coding": [{ "system": "https://example.com/fhir/CodeSystem/risk-level", "code": "low" }], "text": "low" } } } ] } ``` ## A member: `Patient` `GET /v1/fhir/Patient/{patient_id}` and `Patient.registered` give the same `Patient` as in the bundle: `id`, `identifier`, `active`, and `birthDate` and `gender` where known. No name, and no extensions. > **Not decided yet:** the addresses of YOU(th)'s code and identifier systems, shown under > `example.com`; whether every checkup bundle carries the `Patient`; and whether measurements other > than blood pressure will carry LOINC codes beside YOU(th)'s own. Adding a coding never breaks a > receiver that ignores codings it does not know. # Biomarkers > Every biomarker code a checkup's bundle can carry. > > Preview: proposed, may change. Every biomarker code a checkup's bundle can carry today, grouped by what it is about. A checkup carries the ones it measured, each as an [Observation](/docs/data/fhir#a-checkup-bundle). - **Code**: the Observation's code in YOU(th)'s biomarker code system. Codes are open: new ones can appear at any time; keep one you do not know rather than refusing the checkup. - **Value**: a number in its unit, as a `valueQuantity`, or a category, as a `valueCodeableConcept`. A category is a classification, never a diagnosis. - **UCUM**: the unit's UCUM code, sent beside the unit's text. "none" means the unit has no UCUM form and is sent as text only; a category has no unit. - **Checkups**: the checkup types that can measure it. ## Activity | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `daily_steps` | **Daily steps**: Steps in a day | steps | `{steps}` | wearable | | `workout_days_7d` | **Workout days (7 days)**: Days with a recorded workout in the past 7 days | days | `d` | wearable | ## Advanced glycation | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `advanced_glycation_endproducts` | **Advanced glycation endproducts (AGE)**: Compounds formed when sugars bind to proteins or lipids; they build up with high blood sugar and with age | AU | `[arb'U]` | lab | ## Aging | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `daily_bio_age_acceleration` | **Daily bio age acceleration**: Daily difference between biological age and chronological age | years | `a` | digital | | `resilience` | **Resilience**: Recovery time from stressful events | weeks | `wk` | digital | ## Blood | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `basophil_count` | **Basophil count**: Absolute number of basophils involved in allergic responses | 10⁹/L | `10*9/L` | lab | | `basophils_differential_count` | **Basophils %**: Percent of white cells that are basophils | % | `%` | lab | | `eosinophil_count` | **Eosinophil count**: Absolute number of eosinophils tied to allergy and parasite defense | 10⁹/L | `10*9/L` | lab | | `eosinophil_differential_count` | **Eosinophils %**: Percent of white cells that are eosinophils | % | `%` | lab | | `hematocrit` | **Hematocrit (HCT)**: Percentage of the blood made up of red blood cells | % | `%` | lab | | `hemoglobin` | **Hemoglobin**: Distributes oxygen to muscles and tissues | g/dL | `g/dL` | digital, lab | | `low_hemoglobin_risk` | **Low hemoglobin risk**: An index for patterns associated with low hemoglobin | category: low, high | — | digital | | `lymphocyte_count` | **Lymphocyte count**: Absolute number of lymphocytes that support adaptive immunity | 10⁹/L | `10*9/L` | lab | | `lymphocytes_differential_count` | **Lymphocytes %**: Percent of white cells that are lymphocytes | % | `%` | lab | | `monocyte_count` | **Monocyte count**: Absolute number of monocytes that mature into tissue macrophages | 10⁹/L | `10*9/L` | lab | | `monocytes_differential_count` | **Monocytes %**: Percent of white cells that are monocytes | % | `%` | lab | | `neutrophil_count` | **Neutrophil count**: Absolute number of neutrophils that fight bacterial infections | 10⁹/L | `10*9/L` | lab | | `neutrophil_differential_count` | **Neutrophils %**: Percent of white cells that are neutrophils | % | `%` | lab | | `platelet_count` | **Platelet count**: Number of platelets, which help the blood clot | 10⁹/L | `10*9/L` | lab | | `red_cell_count` | **Red cell count**: Number of red blood cells in circulation | 10¹²/L | `10*12/L` | lab | | `white_cell_count` | **White cell count**: Total number of white blood cells, which support the immune system | 10⁹/L | `10*9/L` | lab | ## Body composition | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `appendicular_lean_mass_percentage` | **Appendicular lean mass (ALM)**: Estimated percentage of lean mass in the arms and legs | % | `%` | digital | | `body_composition_body_fat_percentage` | **Body fat percentage**: Estimated percentage of body weight that is fat | % | `%` | wearable | | `body_composition_body_mass_index` | **BMI**: Weight relative to height | kg/m² | `kg/m2` | wearable | | `body_composition_total_body_water_percentage` | **Total body water**: Share of body weight that is water | % | `%` | wearable | | `metabolic_age_years` | **Metabolic age**: How young or old the metabolism is, compared with chronological age | years | `a` | digital | | `skeletal_muscle_mass_percentage` | **Skeletal muscle mass (SMM)**: Estimated percentage of body weight made up of skeletal muscle | % | `%` | digital | | `vat_percentage` | **Visceral adipose tissue (VAT) %**: Estimated percentage of visceral fat around the internal organs | % | `%` | digital | ## Body composition (bioimpedance) | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `appendicular_skeletal_muscle_mass` | **Appendicular skeletal muscle mass**: Muscle mass in limbs (arms + legs), sarcopenia screening indicator | kg | `kg` | lab | | `basal_metabolic_rate` | **Basal metabolic rate (BMR)**: Calories burned at rest | kcal/day | `kcal/d` | lab | | `body_fat_percentage` | **Body fat percentage**: Fat mass relative to total body weight | % | `%` | lab | | `body_mass_index` | **Body mass index (BMI)**: Weight-to-height ratio, a basic proxy for adiposity | kg/m² | `kg/m2` | lab | | `lean_body_mass` | **Lean body mass %**: Non-fat tissue as share of total body weight | % | `%` | lab | | `total_body_water` | **Total body water (%)**: Hydration status relative to total body mass | % | `%` | lab | | `visceral_fat_index` | **Visceral fat index**: Fat surrounding internal organs, key metabolic risk marker | UI (units) | none | lab | ## Body skin | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `abnormal_skin_index` | **Abnormal skin index**: An index from a skin photo for visible skin anomalies that may need attention | category: low, high | — | digital | | `acne_index` | **Acne index**: An index from a skin photo for patterns associated with acne-prone or inflamed skin | category: low, high | — | digital | | `benign_nevus_index` | **Benign nevus index**: An index from a skin photo for patterns associated with benign moles | category: low, high | — | digital | | `dermatitis_index` | **Dermatitis index**: An index from a skin photo for patterns associated with skin irritation or inflammation, such as from contact allergens or irritants | category: low, high | — | digital | | `eczema_index` | **Eczema index**: An index from a skin photo for patterns associated with eczema, such as inflammation, dryness or irritation | category: low, high | — | digital | | `fungal_infection_index` | **Fungal infections index**: An index from a skin photo for patterns associated with fungal skin infections, such as ringworm or candidiasis | category: low, high | — | digital | | `herpesvirus_index` | **Herpesvirus index**: An index from a skin photo for patterns associated with skin signs of herpes viruses, such as HSV or shingles | category: low, high | — | digital | | `no_pathologies_index` | **No pathologies index**: An index from a skin photo for whether no skin condition is visible | category: low, high | — | digital | | `papulosquamous_index` | **Papulosquamous index**: An index from a skin photo for patterns associated with scaly skin conditions, such as psoriasis or lichen planus | category: low, high | — | digital | | `pre_abnormal_skin_index` | **Pre-abnormal skin index**: An index from a skin photo for early skin irregularities that may develop further | category: low, high | — | digital | | `viral_index` | **Viral index**: An index from a skin photo for patterns associated with viral skin conditions, such as warts or molluscum contagiosum | category: low, high | — | digital | ## Cardiovascular | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `apolipoprotein_a` | **Apo A**: Major protein component of HDL particles | g/L | `g/L` | lab | | `apolipoprotein_b` | **Apo B**: Primary protein on atherogenic particles like LDL and VLDL | g/L | `g/L` | lab | | `ascvd_risk` | **ASCVD risk**: Estimated atherosclerotic cardiovascular disease risk | % | `%` | digital | | `blood_pressure` | **Blood pressure**: Systolic and diastolic, as two components | mmHg, two components: systolic (LOINC `8480-6`) and diastolic (LOINC `8462-4`); LOINC `85354-9` | `mm[Hg]` | digital, wearable | | `hdl_cholesterol` | **HDL cholesterol**: High-density lipoprotein that helps clear cholesterol from the bloodstream | mmol/L | `mmol/L` | lab | | `heart_age` | **Heart age**: Estimated age of the cardiovascular system | years | `a` | digital | | `heart_rate` | **Resting heart rate**: Heart beats per minute while awake, calm, sitting or lying down, and still | bpm | `/min` | digital, wearable | | `heart_rate_recovery_1m` | **1-minute heart rate recovery**: How far the heart rate falls in the first minute after exercise | bpm | `/min` | wearable | | `high_total_cholesterol_risk` | **High total cholesterol index**: Proxy for the body's cholesterol balance | category: low, medium, high | — | digital | | `hrv_lf_hf` | **LF/HF**: Ratio of low- to high-frequency heart rate variability | ms | none | digital | | `hrv_rmssd` | **RMSSD**: Beat-to-beat variation in heart rate | ms | `ms` | digital, wearable, lab | | `hrv_rri` | **Mean RRi**: Average time between heartbeats | ms | `ms` | digital | | `hrv_sd1` | **SD1**: Short-term heart rate variability | ms | `ms` | digital | | `hrv_sd2` | **SD2**: Long-term heart rate variability | ms | `ms` | digital | | `hrv_sdnn` | **HRV SDNN**: Variation in time between heart beats | ms | `ms` | digital, wearable, lab | | `hypertension_risk` | **Hypertension index**: An index for patterns associated with hypertension | category: low, medium, high | — | digital | | `ldl_cholesterol` | **LDL cholesterol**: Low-density lipoprotein that delivers cholesterol to tissues | mmol/L | `mmol/L` | lab | | `stress_level` | **Heart stress index**: The body's reaction to a challenge or demand | category: low, normal, mild, high, very_high, extreme | — | digital | | `total_cholesterol` | **Total cholesterol**: Sum of cholesterol carried by LDL, HDL, and other lipoproteins | mmol/L | `mmol/L` | lab | | `triglycerides` | **Triglycerides**: Blood fats used for energy; high levels can raise cardiovascular risk | mmol/L | `mmol/L` | lab | | `wellness_index` | **Cardiorespiratory wellness**: An indication of cardiovascular wellness over the next 5 to 10 years | number, no unit | none | digital | | `wellness_level` | **Wellness level**: Cardiorespiratory wellness as a level | category: low, medium, high | — | digital | ## Energy and stress | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `baevsky_stress_index` | **Baevsky stress index**: Body's reaction to a challenge or demand | number, no unit | none | digital | | `cortisol` | **Cortisol**: Stress hormone that helps regulate energy, blood pressure, and inflammation | nmol/L | `nmol/L` | lab | | `normalized_stress_index` | **Normalized stress index**: Stress index on a 0–100% scale | % | `%` | digital, wearable | | `pns_index` | **PNS index**: The body's recovery ability, in zones | number, no unit | none | digital | | `recovery_ability` | **Recovery ability**: Ability to recover and regulate bodily functions after stressful events | category: low, medium, high | — | digital | | `sns_index` | **SNS index**: The body's stress response, in zones | number, no unit | none | digital | | `stress_response` | **Stress response**: Physiological reaction to an imminent stressful event | category: low, medium, high | — | digital | ## Face skin | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `face_skin_acne_index` | **Acne index**: Presence and severity of visible acne spots | % | `%` | digital | | `face_skin_eye_age` | **Eye age**: Visual age of the skin around both eyes | years | `a` | digital | | `face_skin_eye_area_condition` | **Eye area condition**: Visual health of the skin area under both eyes | % | `%` | digital | | `face_skin_hydration` | **Skin hydration**: Visible signs of dehydrated skin on the face | % | `%` | digital | | `face_skin_perceived_age` | **Perceived age**: Visual age of the skin on the face | years | `a` | digital | | `face_skin_pigmentation_index` | **Pigmentation index**: Size and severity of localized skin darkening caused by excess melanin | % | `%` | digital | | `face_skin_pores_health` | **Pores health**: Visible presence and density of pores | % | `%` | digital | | `face_skin_redness` | **Skin redness**: Visible presence and intensity of skin redness or irritation spots | % | `%` | digital | | `face_skin_uniformness` | **Skin uniformness**: Visible unevenness of skin texture, such as eruptions, age spots and freckles | % | `%` | digital | | `face_skin_wrinkles_index` | **Lines (wrinkles) index**: Presence and severity of wrinkles caused by the loss of natural moisture and elasticity of the skin | % | `%` | digital | ## Fatty acids | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `arachidonic_acid_20_4_w6` | **Arachidonic acid (20:4, ω6)**: An omega-6 fatty acid used to make signaling molecules that drive inflammation and repair | %(rel. quant.) | `%` | lab | | `dihomo_gamma_linolenic_acid_dgla_20_3_w6` | **Dihomo-γ-linolenic acid (DGLA, 20:3, ω6)**: An omega-6 fat that can form anti-inflammatory series-1 prostaglandins | %(rel. quant.) | `%` | lab | | `docosahexaenoic_acid_dha_22_6_w3` | **Docosahexaenoic acid (DHA, 22:6, ω3)**: A brain- and retina-rich omega-3 fat important for cognition and vision | %(rel. quant.) | `%` | lab | | `eicosapentaenoic_acid_epa_20_5_w3` | **Eicosapentaenoic acid (EPA, 20:5, ω3)**: A marine omega-3 fatty acid with anti-inflammatory and heart-protective effects | %(rel. quant.) | `%` | lab | | `omega3_index_epa_plus_dha` | **Omega-3 index (EPA + DHA)**: Relative percent of EPA and DHA, a marker of omega-3 status | %(rel. quant.) | `%` | lab | | `omega6_to_omega3_ratio` | **Omega-6 / omega-3 ratio**: Balance between omega-6 and omega-3 fats, an indicator of inflammatory tone | %(rel. quant.) | none | lab | | `palmitoleic_acid_16_1` | **Palmitoleic acid (16:1)**: An omega-7 monounsaturated fat; higher levels may reflect liver fat production | %(rel. quant.) | `%` | lab | | `peroxidation_index_pi` | **Peroxidation index (PI)**: How susceptible the fatty-acid pool is to oxidative damage | %(rel. quant.) | `%` | lab | | `sfa_to_mufa_ratio` | **SFA/MUFA ratio**: Membrane saturation: more SFA means stiffer membranes, more MUFA more fluid ones | %(rel. quant.) | none | lab | | `total_sfas` | **Total SFAs (saturated fatty acids)**: Combined share of saturated fats in blood lipids | %(rel. quant.) | `%` | lab | | `trans_18_1` | **Trans 18:1**: Trans-monounsaturated fatty acids, often a dietary marker of industrial trans fats | %(rel. quant.) | `%` | lab | | `trans_20_4` | **Trans 20:4**: Trans isomers of arachidonic-like fatty acids; generally undesirable | %(rel. quant.) | `%` | lab | | `unsaturation_index_ui` | **Unsaturation index (UI)**: Weighted count of double bonds across fatty acids; a higher UI means more fluid membranes | %(rel. quant.) | `%` | lab | ## Inflammation | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `c_reactive_protein` | **C-reactive protein (CRP)**: Protein made by the liver that rises with inflammation | mg/L | `mg/L` | lab | ## Mental | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `anxiety_index_mental` | **Psychological resilience index**: Capacity to recover from psychological stress and adversity | category: low, medium, high | — | digital | | `depression_index_mental` | **Psychological wellbeing index**: Emotional wellbeing, mood stability, and motivational drive | category: low, medium, high | — | digital | | `mental_fitness_score` | **Mental fitness score**: Cognitive performance, sustained attention, and mental endurance | number, no unit | none | digital | | `speech_pause_duration` | **Pause duration**: Average duration of pauses between speech sounds | sec | `s` | digital | | `speech_rate` | **Speech rate**: Number of words spoken per minute | words/min | `{words}/min` | digital | | `voice_clarity` | **Voice clarity**: Range of sounds made while speaking by changing the shape of tongue, mouth and throat | kHz² | `kHz2` | digital | | `voice_control` | **Voice control**: Regularity of voice pitch amplitude | % | `%` | digital | | `voice_crispness` | **Voice crispness**: Average time of single speech sounds measured from beginning to end of vocal cord vibration | ms | `ms` | digital | | `voice_energy_range` | **Voice energy range**: Range of vocal intensity used while speaking | dB | `dB` | digital | | `voice_liveliness` | **Voice liveliness**: Range of vocal pitch used while speaking | octaves | none | digital | | `voice_smoothness` | **Voice smoothness**: Regularity of voice pitch periods | % | `%` | digital | ## Metabolic | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `blood_glucose` | **Blood glucose**: Sugar level in the blood at the time of the test | mmol/L | `mmol/L` | lab | | `diabetes_risk` | **Metabolic index**: Proxy for metabolic wellness, and an index for patterns associated with diabetes | category: low, medium, high | — | digital | | `hba1c` | **Average glucose index**: Proxy for average blood sugar in the last 3 months | % | `%` | digital, lab | | `high_fasting_glucose_risk` | **High fasting glucose index**: Proxy for the body's glucose balance | category: low, high | — | digital | ## Oxidative stress | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `plasma_antioxidant_capacity` | **Plasma antioxidant capacity**: Overall antioxidant capacity of plasma, the body's ability to neutralize free radicals | μmol/L | `umol/L` | lab | | `reactive_oxygen_metabolites` | **Reactive oxygen metabolites**: Circulating oxidants, an estimate of oxidative stress load | U-CARR | none | lab | ## Respiratory | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `breathing_rate` | **Breathing rate**: Number of breaths taken per minute | bpm | `/min` | digital, wearable | | `oxygen_saturation` | **Oxygen saturation index**: % of hemoglobin in the blood carrying oxygen | % | `%` | digital, wearable | | `prq` | **Pulse respiratory quotient (PRQ)**: Captures the states of cardiorespiratory integrative activity | number, no unit | none | digital | | `respiratory_fitness` | **Respiratory fitness**: Proxy for respiratory wellness | number, no unit | none | digital | | `respiratory_symptoms_score` | **Respiratory symptoms score**: Signs of respiratory symptoms in the voice | number, no unit | none | digital | | `vo2_max` | **VO₂ max**: How efficiently the body uses oxygen during hard effort | mL/kg/min | `mL/kg/min` | wearable | ## Sleep | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `sleep_average_hrv` | **Sleep HRV**: Heart-rate variability reported during the main sleep session | ms | `ms` | wearable | | `sleep_breathing_rate` | **Sleep breathing rate**: Breathing rate reported during the main sleep session | bpm | `/min` | wearable | | `sleep_efficiency` | **Sleep efficiency**: Share of time in bed spent asleep during the main sleep session | ratio | `1` | wearable | | `sleep_latency` | **Sleep latency**: Time from going to bed until sleep begins | min | `min` | wearable | | `sleep_resting_heart_rate` | **Sleep resting heart rate**: Resting heart rate during the main sleep session | bpm | `/min` | wearable | | `sleep_score` | **Sleep score**: Sleep quality for the main sleep session | number, no unit | none | wearable | | `sleep_total_sleep_time` | **Total sleep time**: Total sleep time reported for the main sleep session | hours | `h` | wearable | ## Vitamins | Code | Measures | Value | UCUM | Checkups | | --- | --- | --- | --- | --- | | `cobalamin` | **Vitamin B12**: Needed for red blood cells, nerves and DNA synthesis | pmol/L | `pmol/L` | lab | | `vitamin_d` | **Vitamin D (25-OH)**: Supports bone, muscle and immune health | nmol/L | `nmol/L` | lab | # Overview > Run the checkup's captures inside your own app: what the SDKs do, and how to get them. The SDKs run the checkup's guided captures inside your own app: the face video, the voice recording, the selfie and skin photos, and readings from wearables. Your app shows them in its own screens and receives the measured biomarkers. | SDK | For | | ----------------------------- | -------------------------------------------------- | | [iOS](/docs/sdks/ios) | A native iOS app, as a Swift package | | [Flutter](/docs/sdks/flutter) | A Flutter app on iOS or Android, as a Dart package | | [Web](/docs/sdks/web) | A browser: planned | ## How they fit with the API The SDKs and the API serve different needs: - **The SDKs** measure in your app and hand the results to your app, as data points. They do not send them to the YOU(th) API, so no webhook announces them. - **The API** answers checkups members complete with YOU(th): at a YOU(th) kiosk or in the YOU(th) Health Hub app. ## What you need - **Access to the SDK.** The SDKs and their sample apps are shared with each partner directly while their public distribution is prepared. Ask your YOU(th) contact. - **Credentials from your backend.** The SDK measures with services YOU(th) works with, and needs their credentials when your app starts. YOU(th) issues them to your backend; your app fetches them from there and hands them to the SDK. Never build them into the app. - **The camera and the microphone.** The captures ask the person for them as they start. > **Not decided yet:** the SDKs' licence, and whether results measured in your app can reach the > YOU(th) API as checkups. # iOS > The SDK for native iOS apps, as a Swift package. The iOS SDK brings the checkup's captures into a native iOS app. It is a Swift package that wraps the [Flutter SDK](/docs/sdks/flutter), so both measure the same way and take the same credentials. ## Add it to your app 1. Ask your YOU(th) contact for access to the package and its sample app. 2. In Xcode, choose **File → Add Package Dependencies**, enter the package's address, and add it to your app's target. 3. Follow the sample app's guide for the rest of the setup: it adds a bridging header and the generated files the package needs, and the entries your `Info.plist` needs. The sample app shows each capture running, from start to results, and is the quickest way to see what your app will receive. ## Before a capture - Fetch the SDK's credentials from your backend when your app starts, and hand them to the SDK before the first capture: see [What you need](/docs/sdks#what-you-need). - Add the camera and microphone usage descriptions to your `Info.plist`; iOS shows them when a capture asks for access. > **Not decided yet:** the SDK's licence, its public distribution, and the iOS versions it supports. # Flutter > The SDK for Flutter apps, as a Dart package. The Flutter SDK brings the checkup's captures into a Flutter app, on iOS and Android. It is a Dart package, `youth_biomarkers_sdk`, for Dart 3.9 or later. ## Add it to your app Ask your YOU(th) contact for access, then add the package to your `pubspec.yaml` as they describe. ## Start it Hand the SDK its credentials, which your app fetched from your backend, then initialise it for the person and their language: ```dart import 'package:youth_biomarkers_sdk/youth_sdk_exports.dart'; final sdk = YouthSdk(); // The credentials your app fetched from your backend. sdk.configure(credentials); // `apiKey` and `userId` as your YOU(th) contact describes them. await sdk.init(apiKey, userId, locale: const Locale('de')); ``` `YouthSdk` is one instance for the whole app. The SDK speaks English, German and Italian, and falls back to English for any other language. ## Run a capture Each capture has its own controller, which your screen drives and listens to: | Capture | Controller | | --------------- | ---------------------------- | | Face video | `YouthVideoController` | | Selfie | `YouthSelfieController` | | Voice recording | `YouthVoiceController` | | Body skin photo | `YouthBodySkinController` | | Photo | `YouthCameraPhotoController` | | Wearables | `YouthWearablesController` | A capture's results come back to your app as data points: a biomarker, its value and its unit. > **Not decided yet:** the SDK's licence, its public distribution, and the platform versions it > supports. The sample app your YOU(th) contact shares shows each controller in use. # Web > A web SDK, to run the captures in a browser, is planned. > > Preview: proposed, may change. A web SDK, to run the checkup's captures in a browser, is planned. YOU(th)'s own kiosk already runs its captures in a browser, and the web SDK would bring the same to your web app. Until then, the [iOS](/docs/sdks/ios) and [Flutter](/docs/sdks/flutter) SDKs run the captures in an app, and the [read API](/docs/webhooks/catch-up#the-read-api) answers checkups members complete with YOU(th). If your product needs captures in a browser, tell your YOU(th) contact what it should do: which captures, on which devices, and where the results should go. That shapes what comes first. # Versions and changelog > How the read API and the webhook are versioned, and what has changed. > > Preview: proposed, may change. ## Versions The read API is versioned in its path: every route starts with `/v1`. The webhook is versioned by each delivery's `event_version`. Within a version, only routes, events and optional fields are added. A change that could break your system comes as a new version. So that new fields never break you: - Ignore fields you do not know. - Keep codes you do not know, such as a new biomarker. - Answer `2xx` to a webhook event you do not handle. ## While it is a preview What these pages describe is a preview: proposed, and liable to change before it is live. Until it is, a change can break what these pages describe, and the promise above starts the day it is live. Each change is listed below. ## Changelog ### October 2026 - The webhook, the read API and FHIR, as these docs describe them, are published as a preview. # Errors and rate limits > What a refused request answers, and the limits a client meets. > > Preview: proposed, may change. ## Errors A refused request answers one of these statuses: | Status | Means | | ------ | ------------------------------------------------------------------------------------------------- | | `401` | No token, or an expired or unknown one: get a new one, see [Authentication](/docs/authentication) | | `403` | The token is valid, but this route is not open to your client | | `404` | Nothing of yours at this address: an unknown id, or another organization's | | `429` | Too many requests: wait for `Retry-After` seconds | Act on the status. ## Rate limits A client that sends too many requests gets `429` with a `Retry-After` header: wait that many seconds, then try again. > **Not decided yet:** the error body; the rate limits. # Use the docs with AI > Hand a page or the whole docs to an assistant or a coding agent. These docs are written to be read by people and by assistants alike. Hand an assistant or a coding agent what it needs, and ask it to build your integration from there. ## One page Every page has the same row under its title: | Action | Does | | --------------- | --------------------------------------------------------------------------- | | Copy page | Puts the page on your clipboard as Markdown, to paste into any assistant | | Markdown | Opens the page as Markdown: its address is the page's with `.md` at the end | | Open in ChatGPT | Starts a chat that asks ChatGPT to read the page's Markdown | | Open in Claude | Starts a chat that asks Claude to read the page's Markdown | ## The whole docs | File | Holds | | ---------------------------------- | ---------------------------------------------------------------------- | | [`/llms.txt`](/llms.txt) | An index of every page's Markdown, by section, as llmstxt.org proposes | | [`/llms-full.txt`](/llms-full.txt) | Every page in one Markdown file | Give a coding agent `/llms-full.txt` for the whole picture. ## A prompt to start from Put this site's address in place of ``, and your stack in place of the other: ```text Read /llms-full.txt, the docs of the YOU(th) webhook and read API. Build a webhook receiver for my backend in that: - verifies each delivery's X-youth-signature with the secret in YOUTH_WEBHOOK_SECRET, over the raw body, and refuses one older than five minutes; - skips an event_id it has already handled, and answers 200 within ten seconds; - for Checkup.completed, reads the bundle at resource_url with an OAuth2 client-credentials token from YOUTH_CLIENT_ID and YOUTH_CLIENT_SECRET, reusing the token until it expires. Write tests for each of these, and keep every secret out of the code. ``` What these pages describe is a preview: tell your assistant so, and check its work against them.