API Reference
Attunio Clinical Intelligence API
On this page
Quickstart
One authenticated POST turns the timing telemetry you already collect into structured, clinician-reviewable Digital MSE™ signals. Grab a test key from your developer dashboard, send the request below, and read the response — no SDK required.
1 · Send signals
curl https://attuniohealth.com/api/v1/dmse/analyze \ -H "Authorization: Bearer att_sk_test_xxx" \ -H "Content-Type: application/json" \ -d '{ "responseLatencySeconds": 2.6, "speechRateWpm": 98, "psychomotorFidgetEpisodes": 4, "sustainedAttentionPct": 66, "explain": true }'2 · Get intelligence back
{ "ok": true, "requestId": "req_9f2c41d07ab34e8a91b2", "data": { "object": "dmse.analysis", "summary": { "status": "clinical_attention", "headline": "4 of 4 signals outside typical range — Response latency, Speech rate, Psychomotor activity, Sustained attention (high confidence).", "flagged": ["Response latency", "Speech rate", "Psychomotor activity", "Sustained attention"], "requiresClinicianReview": true }, "signalsPresent": 4, "overallConfidence": "high", "signals": [ { "signalKey": "response_latency", "metricValue": 2.6, "interpretation": "clinical_attention", "referenceRange": "<1.8s typical; 1.8–2.4s subclinical; ≥2.5s clinical attention", "mseLine": "Speech: increased response latency (avg ~2.6s)." } // ... psychomotor, speech_rate, sustained_attention ], "mseNarrative": ["Speech: increased response latency (avg ~2.6s).", "..."], "intelligence": { "reasoning": { "narrative": "Current findings suggest a pattern warranting clinical attention. Objective behavioral indicators, including response latency averaging 2.6 seconds against a 2.5-second threshold, demonstrate a consistent departure from typical range across all four measured signals. Confidence is high given complete session data, and the existing evidence supports clinician review of this result.", "generatedAt": "2026-07-03T09:00:00Z" } } }}That is the entire loop. The full field reference, the other four intelligence endpoints, errors, and rate limits are documented below.
Why Attunio — instead of building it yourself
Every endpoint is a deterministic, explainable engine, not a black box. You could wire up your own heuristics — here is what you would have to build, validate, and maintain to match what ships today.
Validated clinical thresholds
Every signal fires against thresholds grounded in clinical literature and tuned on real visit data — not arbitrary cutoffs you would have to defend in a chart review.
MSE-mapped outputs
Outputs map directly to mental-status-exam language clinicians already document, so results drop into existing workflows instead of becoming another uninterpretable score.
Longitudinal baselines & trend
Send patient history and each signal is contextualized against that person's own baseline with mean, SD, and trend direction — the longitudinal layer is the hard part to build well.
HIPAA-ready by construction
No raw audio or video is ever accepted or stored, traffic is TLS 1.3, and every output is decision support for clinician review — never an autonomous diagnosis.
A suite, not a single call
Digital MSE™ is API #1. Medication, Biomarker, Outcome, and Relapse Intelligence reuse the same auth and envelope, so you integrate once and grow into the rest.
Maintained & versioned for you
Threshold updates, new biomarkers, and model improvements ship behind a stable, forward-compatible contract — you get the upgrades without re-validating your own engine.
Overview
The Attunio Clinical Intelligence API is a secure, RESTful service for high-throughput, real-time behavioral telemetry processing. It accepts structured speech- and movement-timing metrics from third-party video visits and returns Digital MSE™ output: four structured signals, an MSE narrative, confidence, and optional per-patient baseline and trend.
Data minimization. The API strictly processes downstream structured numerical tracking coordinates and speech-cadence vectors. No raw video or audio files are ever accepted, stored, or written to disk. Output is decision support for clinician review — not an autonomous diagnosis, biometric ML, or affect recognition.
Base URL: https://attuniohealth.com/api/v1
Intelligence suite
The platform is organized as a suite of clinical-intelligence products that share one authentication model and one response envelope. All five products are live and callable today, each backed by a deterministic, explainable engine — every signal is traceable to its source data, never a black-box prediction. Reference docs for each endpoint follow below.
| Product | Status | Endpoint | What it does |
|---|---|---|---|
| Digital MSE™ | Live | POST /v1/dmse/analyze | Speech & movement timing → mental-status-exam signals. |
| Medication Intelligence™ | Live | POST /v1/medication/analyze | Adherence & response patterns from medication history. |
| Biomarker Intelligence™ | Live | POST /v1/biomarker/analyze | Labs & wearable biomarkers, contextualized. |
| Outcome Intelligence™ | Live | POST /v1/outcome/score | Symptom scales → outcome trajectories. |
| Relapse Prediction™ | Live | POST /v1/relapse/predict | Multi-signal early-warning system. |
Authentication
Authenticate every request with an API key issued from your developer dashboard. Pass it as a Bearer token. Keys are shown once at creation — store them securely and never embed them in client-side code.
Authorization: Bearer att_sk_live_a1b2c3d4e5f6...Test keys (att_sk_test_…) run against the sandbox; live keys (att_sk_live_…) are issued to production apps.
Publishable keys (browser)
Secret keys (att_sk_…) carry full API access and must stay server-side. To call the API directly from the browser — for example from a capture SDK running inside a telehealth video session — use a publishable key (att_pk_…) instead.
Publishable keys are deliberately constrained so they're safe to ship to the client:
- They can only call
POST /v1/dmse/analyze. Every other endpoint returns403 forbidden. - They're honored only when the request
Originis in the key's allow-list, which you manage in the dashboard. A leaked key can't be used from any other site. - The endpoint supports CORS preflight, so browser requests work without a proxy.
// Runs in the browser — publishable keys are safe to ship to the client.// The key only works from origins you allow-list in the dashboard.const res = await fetch("https://attuniohealth.com/api/v1/dmse/analyze", { method: "POST", headers: { "Authorization": "Bearer att_pk_live_xxx", "Content-Type": "application/json", }, body: JSON.stringify({ responseLatencySeconds: 2.6, speechRateWpm: 98, psychomotorFidgetEpisodes: 4, sustainedAttentionPct: 66, }),}) const { data } = await res.json()A request from an origin that isn't allow-listed is rejected:
{ "ok": false, "requestId": "req_7c1e58f2a90b4d3e62f4", "error": { "code": "forbidden", "message": "This origin is not allowed for this publishable key. Add it to the key's allowed origins in the developer dashboard.", "docs": "https://attunio.com/developers/docs#errors" }}Note: raw audio and video never touch Attunio. Extraction happens on-device; only the derived timing telemetry shown above is sent to the API.
Capture SDK (@attunio/capture)
The capture SDK is the fastest way to go from a telehealth video session to Digital MSE™ signals. It takes the WebRTC MediaStream your platform already produces, extracts speech rate and response latency on-device, and calls /v1/dmse/analyze with a publishable key. Raw audio never leaves the browser.
npm install @attunio/captureimport { AttunioSession, fromUserMedia } from '@attunio/capture' // Publishable key — safe to ship in browser code.const session = new AttunioSession({ publishableKey: 'att_pk_live_xxx' }) // Any WebRTC MediaStream works. Here: the patient's mic.const stream = await fromUserMedia({ audio: true })session.attach(stream) // audio is processed on-device // speech rate + response latency are extracted locally.// Optionally merge in video-derived signals you already compute:session.setVideoSignals({ psychomotorFidgetEpisodes: 4, sustainedAttentionPct: 72 }) const dmse = await session.analyze() // POSTs derived numbers, returns Digital MSEsession.stop()Video-derived signals (psychomotor episodes, sustained attention) are not extracted in the browser today — they are API-only. If your own pipeline produces them, pass them through with setVideoSignals()and they're merged into the same call.
Each platform integration is just a one-liner that resolves the stream — the on-device extraction is identical everywhere:
// Each platform just resolves the MediaStream you already hold:import { fromZoomTracks, // Zoom Apps SDK media tracks fromDailyParticipant, // Daily local participant fromTwilioTracks, // Twilio LocalAudioTrack / LocalVideoTrack fromVonagePublisher, // Vonage / OpenTok publisher streamFromTracks, // compose from raw MediaStreamTracks} from '@attunio/capture' session.attach(fromDailyParticipant(call.participants().local))Want to see it work? Run the live demo — it captures your microphone and returns real Digital MSE output. For platform-by-platform setup, see Integrations.
Health check — GET /v1/ping
Confirms your key and connectivity before sending telemetry.
curl https://attuniohealth.com/api/v1/ping \ -H "Authorization: Bearer att_sk_test_xxx"Analyze telemetry — POST /v1/dmse/analyze
Submit timing telemetry for a single session. Provide at least one metric; supply more for a fuller mental-status picture, and include history to unlock baseline and trend.
curl https://attuniohealth.com/api/v1/dmse/analyze \ -H "Authorization: Bearer att_sk_test_xxx" \ -H "Content-Type: application/json" \ -d '{ "responseLatencySeconds": 2.6, "speechRateWpm": 98, "psychomotorFidgetEpisodes": 4, "sustainedAttentionPct": 66, "sessionDurationSeconds": 540, "history": { "response_latency": [3.1, 2.9, 2.7] }, "sessionRef": "visit_8841" }'{ "ok": true, "requestId": "req_9f2c41d07ab34e8a91b2", "data": { "object": "dmse.analysis", "sessionRef": "visit_8841", "generatedAt": "2026-06-28T15:04:05.000Z", "summary": { "status": "clinical_attention", "headline": "4 of 4 signals outside typical range — Response latency, Speech rate, Psychomotor activity, Sustained attention (high confidence).", "flagged": ["Response latency", "Speech rate", "Psychomotor activity", "Sustained attention"], "requiresClinicianReview": true }, "signalsPresent": 4, "overallConfidence": "high", "signals": [ { "signalKey": "response_latency", "label": "Response latency", "present": true, "metricValue": 2.6, "metricUnit": "s", "interpretation": "clinical_attention", "referenceRange": "<1.8s typical; 1.8–2.4s subclinical; ≥2.5s clinical attention", "detail": "Markedly delayed response latency (avg 2.6s).", "confidence": "high", "trendDir": "down", "mseLine": "Speech: increased response latency (avg ~2.6s).", "baseline": { "mean": 2.9, "sd": 0.2, "n": 3 } } // ... psychomotor, speech_rate, sustained_attention ], "mseNarrative": [ "Speech: increased response latency (avg ~2.6s).", "Psychomotor: psychomotor agitation (4 fidget episodes during session)." ], "interpretationScale": { "within_normal": "Within typical range — no action suggested.", "subclinical": "Mildly outside typical range — worth noting, not urgent.", "clinical_attention": "Clearly outside typical range — flag for clinician review." }, "disclaimer": "Deterministic, rules-based decision support derived from numerical timing metrics. Not biometric ML, affect recognition, or an autonomous diagnosis. For clinician review only." }}Request fields
| Field | Type | Description |
|---|---|---|
| psychomotorFidgetEpisodes | number | Discrete fidget / motor-restlessness episodes counted during the session (0–1000). |
| responseLatencySeconds | number | Mean conversational response latency in seconds (0–120). |
| speechRateWpm | number | Mean speech rate in words per minute (0–600). |
| sustainedAttentionPct | number | Share of session with gaze on-screen, 0–100. |
| sessionDurationSeconds | number | Optional. Session length; longer sessions raise confidence. |
| history | object | Optional. Arrays of prior values keyed by signal (psychomotor, response_latency, speech_rate, sustained_attention) for baseline + trend. |
| sessionRef | string | Optional. Your correlation id, echoed back. Never stored. |
Shared objects
The Medication, Outcome, and Relapse endpoints accept the same Assessment objects, Medication accepts Prescription objects, and Biomarker accepts Lab panel objects containing Biomarker readings. They are documented once here and referenced from each endpoint below. All date fields are ISO-8601 strings; ? marks an optional field.
Assessment object
| Field | Type | Description |
|---|---|---|
| instrumentId | string | Stable id for the instrument, e.g. "phq9". 1–64 chars. |
| instrumentName | string | Human-readable name, e.g. "PHQ-9". 1–120 chars. |
| score | number | Raw score for this administration. 0–1000. |
| maxScore | number | Maximum possible score for the instrument. 1–1000. |
| severity | string? | Optional severity band, e.g. "moderately severe". |
| createdAt | ISO date | When the assessment was administered. |
Prescription object
| Field | Type | Description |
|---|---|---|
| id | string | Your prescription id; echoed back as the signal's sourceRef. |
| medicationName | string | Medication name, e.g. "Sertraline". 1–160 chars. |
| dosage | string? | Optional dosage, e.g. "50mg". |
| frequency | string? | Optional frequency, e.g. "daily". |
| status | string | Prescription status, e.g. "active" or "discontinued". |
| prescribedAt | ISO date? | When the medication started. Anchors the before/after symptom comparison. |
Lab panel object
| Field | Type | Description |
|---|---|---|
| id | string | Your lab/panel id; echoed back as the signal's sourceRef. |
| labName | string | Lab or panel name, e.g. "Quest Diagnostics". 1–160 chars. |
| observedAt | ISO date? | When the panel was drawn or observed. |
| results.biomarkers | Biomarker[] | Up to 200 biomarker readings (see Biomarker object). |
Biomarker object
| Field | Type | Description |
|---|---|---|
| name | string | Biomarker name, e.g. "TSH". 1–120 chars. |
| value | number | string | null | The measured value. |
| unit | string? | Unit of measure, e.g. "mIU/L". |
| flag | string? | "high" | "low" | "critical" | "normal". Only abnormal flags produce a signal. |
| referenceRange | string? | Reference range string, e.g. "0.4-4.0". |
| note | string? | Optional free-text note (≤240 chars). |
Medication Intelligence™ — POST /v1/medication/analyze
Correlates each active prescription's start date with the symptom-scale trajectory of the most-covered instrument (before vs. after the med started), plus an optional Digital MSE™ latency change, to produce explainable response and monitoring signals. Send prescriptions, assessments, and optionally dmseLatencyPctChange.
curl https://attuniohealth.com/api/v1/medication/analyze \ -H "Authorization: Bearer att_sk_test_xxx" \ -H "Content-Type: application/json" \ -d '{ "prescriptions": [ { "id": "rx_001", "medicationName": "Sertraline", "dosage": "50mg", "frequency": "daily", "status": "active", "prescribedAt": "2026-03-01T00:00:00Z" } ], "assessments": [ { "instrumentId": "phq9", "instrumentName": "PHQ-9", "score": 18, "maxScore": 27, "createdAt": "2026-02-20T00:00:00Z" }, { "instrumentId": "phq9", "instrumentName": "PHQ-9", "score": 11, "maxScore": 27, "createdAt": "2026-05-15T00:00:00Z" } ], "dmseLatencyPctChange": -14 }'{ "ok": true, "data": { "object": "medication.analysis", "generatedAt": "2026-06-28T15:04:05.000Z", "signalsPresent": 2, "signals": [ { "module": "medication", "signalKey": "med_response_sertraline", "label": "Sertraline — symptom response", "detail": "PHQ-9 improved 18→11 (39% reduction) since Sertraline 50mg started.", "tone": "positive", "metricValue": 11, "metricUnit": "score", "confidence": "moderate", "summaryLine": "On Sertraline 50mg, PHQ-9 improved from 18 to 11 (39% reduction).", "sourceRef": "rx_001", "observedAt": "2026-05-15T00:00:00.000Z" } // ... objective Digital MSE™ corroboration signal ], "disclaimer": "..." }}Request body
| Field | Type | Description |
|---|---|---|
| prescriptions | Prescription[] | Prescriptions to correlate. Up to 100. See Prescription object. |
| assessments | Assessment[] | Symptom-scale administrations. Up to 500. See Assessment object. |
| dmseLatencyPctChange | number? | Optional Digital MSE™ latency % change for objective corroboration. -100 to 1000. |
| sessionRef | string? | Optional correlation id, echoed back. Never stored. |
Biomarker Intelligence™ — POST /v1/biomarker/analyze
Maps abnormally-flagged lab biomarkers to their psychiatric relevance so reversible medical drivers are ruled out before escalating psychotropics. Only readings with a flag of high, low, or critical produce a signal; normal values are ignored.
curl https://attuniohealth.com/api/v1/biomarker/analyze \ -H "Authorization: Bearer att_sk_test_xxx" \ -H "Content-Type: application/json" \ -d '{ "labs": [ { "id": "lab_88", "labName": "Quest Diagnostics", "observedAt": "2026-06-01T00:00:00Z", "results": { "biomarkers": [ { "name": "TSH", "value": 6.8, "unit": "mIU/L", "flag": "high", "referenceRange": "0.4-4.0" }, { "name": "Vitamin D", "value": 18, "unit": "ng/mL", "flag": "low", "referenceRange": "30-100" } ] } } ] }'{ "ok": true, "data": { "object": "biomarker.analysis", "generatedAt": "2026-06-28T15:04:05.000Z", "signalsPresent": 2, "signals": [ { "module": "biomarker", "signalKey": "bio_tsh", "label": "Thyroid (TSH)", "detail": "6.8 mIU/L (ref 0.4-4.0) — elevated TSH can present as depression, fatigue, and cognitive slowing — screen before escalating an antidepressant.", "tone": "watch", "metricValue": 6.8, "metricUnit": "mIU/L", "confidence": "high", "summaryLine": "Thyroid (TSH) elevated at 6.8 mIU/L: ...", "sourceRef": "lab_88", "observedAt": "2026-06-01T00:00:00.000Z" } // ... vitamin D signal ], "disclaimer": "..." }}Request body
| Field | Type | Description |
|---|---|---|
| labs | Lab[] | At least one lab panel (required). Up to 100. See Lab panel object. |
| sessionRef | string? | Optional correlation id, echoed back. Never stored. |
Outcome Intelligence™ — POST /v1/outcome/score
Fuses up to five independent sources into a single treatment-response composite from -100 (worsening) to +100 (strong improvement). Each component's signed contribution is returned, and confidence scales with how many independent sources are available and agree.
curl https://attuniohealth.com/api/v1/outcome/score \ -H "Authorization: Bearer att_sk_test_xxx" \ -H "Content-Type: application/json" \ -d '{ "assessments": [ { "instrumentId": "phq9", "instrumentName": "PHQ-9", "score": 18, "maxScore": 27, "createdAt": "2026-02-20T00:00:00Z" }, { "instrumentId": "phq9", "instrumentName": "PHQ-9", "score": 9, "maxScore": 27, "createdAt": "2026-05-15T00:00:00Z" } ], "dmseLatencyPctChange": -12, "activeMedications": 1, "biomarkerWatchCount": 0, "avgSleepHours": 7.5 }'{ "ok": true, "data": { "object": "outcome.score", "generatedAt": "2026-06-28T15:04:05.000Z", "score": 66, "direction": "improving", "confidence": "high", "components": [ { "key": "symptoms", "label": "Symptom scales", "contribution": 45, "detail": "PHQ-9 improved 18→9 across 2 administrations.", "available": true }, { "key": "dmse", "label": "Digital MSE™ latency", "contribution": 6, "detail": "Response latency changed -12% across visits.", "available": true } // ... medication, biomarker, sleep components ], "summaryLine": "Treatment response is improving (composite +66) across 4 independent data sources.", "sourceCount": 4, "disclaimer": "..." }}Request body
| Field | Type | Description |
|---|---|---|
| assessments | Assessment[] | Symptom-scale administrations. Up to 500. See Assessment object. |
| dmseLatencyPctChange | number? | Optional Digital MSE™ latency % change across visits. -100 to 1000. |
| activeMedications | integer | Count of active medications. 0–100. Default 0. |
| biomarkerWatchCount | integer | Count of flagged biomarker watch-items. 0–100. Default 0. |
| avgSleepHours | number? | Optional average nightly sleep hours from wearables. 0–24. |
| sessionRef | string? | Optional correlation id, echoed back. Never stored. |
Relapse Prediction™ — POST /v1/relapse/predict
The forward-looking layer: uses the velocity and recency of symptom scales, Digital MSE™ latency, wearable sleep, medication-change windows, and visit cadence to compute a transparent, additive 0–100 early-warning score. Every contributing factor and a conservative recommendation are returned — nothing is auto-actioned.
curl https://attuniohealth.com/api/v1/relapse/predict \ -H "Authorization: Bearer att_sk_test_xxx" \ -H "Content-Type: application/json" \ -d '{ "assessments": [ { "instrumentId": "phq9", "instrumentName": "PHQ-9", "score": 12, "maxScore": 27, "createdAt": "2026-05-01T00:00:00Z" }, { "instrumentId": "phq9", "instrumentName": "PHQ-9", "score": 19, "maxScore": 27, "severity": "moderately severe", "createdAt": "2026-06-15T00:00:00Z" } ], "dmseLatencyPctChange": 16, "avgSleepHours": 5.2, "daysSinceMedChange": 12, "hasActiveMedication": true, "daysSinceLastVisit": 38 }'{ "ok": true, "data": { "object": "relapse.prediction", "generatedAt": "2026-06-28T15:04:05.000Z", "score": 68, "level": "elevated", "trend": "rising", "confidence": "high", "topDriver": "Symptom trajectory", "factors": [ { "key": "symptoms", "label": "Symptom trajectory", "points": 34.4, "maxPoints": 38, "tone": "elevated", "available": true, "detail": "PHQ-9 rising 12→19/27 (moderately severe) at the two most recent administrations." }, { "key": "sleep", "label": "Sleep (wearable)", "points": 12, "maxPoints": 18, "tone": "elevated", "available": true, "detail": "Averaging 5.2h/night recently — short sleep is a leading relapse precursor." } // ... dmse, medication, cadence factors ], "summaryLine": "Elevated relapse risk (68/100), signals rising, driven primarily by symptom trajectory.", "recommendation": "Flag for closer monitoring; consider a check-in before the next scheduled visit.", "sourceCount": 5, "disclaimer": "..." }}Request body
| Field | Type | Description |
|---|---|---|
| assessments | Assessment[] | Symptom-scale administrations. Up to 500. See Assessment object. |
| dmseLatencyPctChange | number? | Optional Digital MSE™ latency % change across visits. -100 to 1000. |
| avgSleepHours | number? | Optional average nightly sleep hours from wearables. 0–24. |
| daysSinceMedChange | integer? | Days since the last medication change. 0��3650. |
| hasActiveMedication | boolean | Whether the patient is on an active medication. Default false. |
| daysSinceLastVisit | integer? | Days since the last clinical visit. 0–3650. |
| sessionRef | string? | Optional correlation id, echoed back. Never stored. |
Lab orders — /v1/labs
Unlike the Intelligence endpoints, the lab and wearable resources are stateful: they persist records keyed by your own patientRef — the identifier you already use for the patient in your system. These endpoints require a secret key (att_sk_); publishable browser keys are rejected.
1. Browse the catalog. GET /v1/labs/panels returns every orderable panel and its panelId. Filter with ?category=.
curl https://attuniohealth.com/api/v1/labs/panels \ -H "Authorization: Bearer att_sk_test_xxx"{ "ok": true, "data": { "object": "list", "resource": "lab_panel", "count": 24, "panels": [ { "panelId": "thyroid-panel", "name": "Thyroid panel (TSH)", "category": "Nutrients & hormones", "description": "Thyroid dysfunction can mimic depression, anxiety, and fatigue.", "priceCents": 5900, "turnaround": "1-2 days", "useCases": ["Depression", "Fatigue"] } // ... more panels ] }}2. Create an order. POST /v1/labs/orders places the order in the requested state. A provider still authorizes it before specimen collection; the status advances through authorized → scheduled → collected → resulted → completed.
curl https://attuniohealth.com/api/v1/labs/orders \ -H "Authorization: Bearer att_sk_test_xxx" \ -H "Content-Type: application/json" \ -d '{ "patientRef": "patient_8842", "panelId": "thyroid-panel", "note": "Baseline before starting an SSRI.", "metadata": { "encounterId": "enc_1201" } }'{ "ok": true, "data": { "object": "lab_order", "id": "labord_9f3c2a1b7d8e4f60a1c2d3e4f5061728", "patientRef": "patient_8842", "panelId": "thyroid-panel", "panelName": "Thyroid panel (TSH)", "category": "Nutrients & hormones", "priceCents": 5900, "status": "requested", "note": "Baseline before starting an SSRI.", "requisitionNumber": "ATN-LZ4K9-061728", "metadata": { "encounterId": "enc_1201" }, "createdAt": "2026-07-03T15:04:05.000Z", "updatedAt": "2026-07-03T15:04:05.000Z", "notice": "Lab orders created through the API still require provider authorization ..." }}Request body
| Field | Type | Description |
|---|---|---|
| patientRef | string | Your own patient identifier. Opaque to Attunio; used to scope and list the patient's orders. 1–128 chars. |
| panelId | string | Id of an orderable panel from GET /v1/labs/panels, e.g. "thyroid-panel". |
| note | string? | Optional PHI-free note stored with the order. Up to 500 chars. |
| metadata | object? | Optional free-form key/value metadata, echoed back on the order. |
3. List & retrieve. GET /v1/labs/orders lists your orders (filter with ?patientRef= and ?limit=, 1–200), and GET /v1/labs/orders/{id} fetches a single order. Both are scoped to your account.
curl "https://attuniohealth.com/api/v1/labs/orders?patientRef=patient_8842" \ -H "Authorization: Bearer att_sk_test_xxx"Wearable connections — /v1/wearables
Connect a patient's wearable (Apple Health, Garmin, Fitbit, Oura, WHOOP, Google Fit, Withings, and more) so their heart-rate, sleep, activity, and stress data can flow into Attunio. Connections are persisted per patientRef and require a secret key.
1. List supported providers. GET /v1/wearables/providers returns the provider codes you can pass to the connect endpoint.
2. Start a connection. POST /v1/wearables/connect returns a pending connection with a connect.url — an Attunio-hosted URL that carries the selected providers. Send the patient there (redirect or embed) to authorize their device; no third-party URL is exposed.
curl https://attuniohealth.com/api/v1/wearables/connect \ -H "Authorization: Bearer att_sk_test_xxx" \ -H "Content-Type: application/json" \ -d '{ "patientRef": "patient_8842", "providers": ["APPLE", "GARMIN", "OURA"], "language": "en" }'{ "ok": true, "data": { "object": "wearable_connection", "id": "wear_1a2b3c4d5e6f708192a3b4c5d6e7f809", "patientRef": "patient_8842", "status": "pending", "provider": null, "providerDisplayName": null, "connect": { "url": "https://attuniohealth.com/connect/wearable/wear_1a2b3c4d5e6f708192a3b4c5d6e7f809?providers=APPLE,GARMIN,OURA" }, "connectedAt": null, "createdAt": "2026-07-03T15:04:05.000Z", "updatedAt": "2026-07-03T15:04:05.000Z", "notice": "... wearable connections require the patient to complete device authorization ..." }}Request body
| Field | Type | Description |
|---|---|---|
| patientRef | string | Your own patient identifier. Used to scope and list the patient's connections. 1–128 chars. |
| providers | string[]? | Optional subset of providers to offer, e.g. ["APPLE","GARMIN"]. Defaults to all supported providers. |
| language | string? | Optional connect-page language (ISO 639-1, 2 letters). Defaults to "en". |
| metadata | object? | Optional free-form key/value metadata, echoed back on the connection. |
3. Poll status. GET /v1/wearables/connections reflects the current state — pending, connected, or disconnected. Once the patient finishes authorizing, the connection flips to connected and the resolved provider is populated.
curl "https://attuniohealth.com/api/v1/wearables/connections?patientRef=patient_8842" \ -H "Authorization: Bearer att_sk_test_xxx"{ "ok": true, "data": { "object": "list", "resource": "wearable_connection", "count": 1, "patientRef": "patient_8842", "connections": [ { "object": "wearable_connection", "id": "wear_1a2b3c4d5e6f708192a3b4c5d6e7f809", "patientRef": "patient_8842", "status": "connected", "provider": "OURA", "providerDisplayName": "Oura Ring", "connectedAt": "2026-07-03T15:22:10.000Z" // ... connect, lastSyncAt, timestamps } ] }}Model versioning & calibration
Every scored response carries a model block so you always know exactly which version produced a result. The engines ship with clinically-reasoned default parameters, and Attunio periodically calibrates them against real, de-identified outcomes — but calibration only ever proposes a new version. Nothing changes what your integration receives until a licensed reviewer promotes it.
{ "ok": true, "data": { "object": "relapse.prediction", "model": { "key": "relapse_risk", "version": 4, // null until a calibrated version is promoted "source": "calibrated" // "default" = shipped constants, "calibrated" = clinician-promoted }, "score": 68, "level": "elevated" // ... factors, recommendation, disclaimer }}model object
| Field | Type | Description |
|---|---|---|
| model.key | string | Stable id of the engine, e.g. "relapse_risk", "outcome_score", "medication_response", "biomarker_interpretation". |
| model.version | integer | null | The active version that produced this response. null means the engine is running the shipped defaults — no calibrated version has been promoted. |
| model.source | string | "default" = the clinically-reasoned constants that ship with the API. "calibrated" = a version a licensed admin reviewed and promoted. |
How calibration works — and what it is not. This is not autonomous learning or self-modifying clinical logic. It is a transparent, human-supervised tuning loop, and several guardrails keep it that way:
- Defaults are identical until a human acts. A freshly provisioned engine returns
source: "default"and behaves exactly as documented. Promotion is a deliberate, audited action by a licensed admin. - Calibration proposes, it never promotes. The job reads aggregate prediction-vs-outcome concordance and registers a candidate version with its measured metrics. A reviewer compares it against the active version and decides.
- The same explainable engine, retuned. Calibration only adjusts bounded weights and thresholds within fixed safety caps — it does not introduce a black-box model. Every signal stays traceable to its source data, and abnormal-lab safety floors cannot be lowered.
- Versioned and reversible. Exactly one version is active per engine at a time, and any prior version can be re-promoted instantly. The
model.versionon each response makes results auditable after the fact.
Pin to behavior you have validated by recording the model.version you tested against. Outputs remain clinician-reviewable decision support at every version — calibration sharpens the thresholds, it never turns a score into a diagnosis or an auto-action.
Unified intelligence schema
Every intelligence endpoint — Digital MSE™, Biomarker Intelligence™, Medication Intelligence™, Outcome Intelligence™, and Relapse Prediction™ — returns the same intelligence block alongside its endpoint-specific fields. Learn the six sub-blocks once and you can read every response on the platform. Existing fields are untouched: the block is purely additive, so current integrations keep working.
intelligence.* — identical on all five endpoints
| Field | Type | Description |
|---|---|---|
| schema | string | Always "attunio.intelligence/v1". Bump only on breaking changes. |
| engine | string | Which engine produced this: "dmse", "biomarker", "medication", "outcome", or "relapse". |
| summary | object | status, one-sentence headline, flagged items (worst first), and requiresClinicianReview. |
| confidence | object | level (high/moderate/low), score 0–1, and human-readable reasons for the rating. |
| dataQuality | object | completeness 0–1, missingInputs[], and freshness (newest input timestamp + stale inputs). |
| provenance | object | generatedAt, engineVersion, method, and every input with its value, timestamp, and whether it contributed. |
| codes | array | SNOMED CT / LOINC / RxNorm mappings with mapsTo pointers into the response. |
| change | object | null | Why the result differs from your previous call. Present only when the request includes `previous`. |
| interpretationScale | object | Legend defining every status value this engine can return. |
| reasoning | object? | Opt-in plain-language interpretation of the findings. Present only when the request sets `explain: true`; null if generation failed. |
"intelligence": { "schema": "attunio.intelligence/v1", "engine": "relapse", "summary": { "status": "moderate", "headline": "Relapse risk 38/100 (moderate) — sleep restored, follow-up gap closed.", "flagged": ["Assessment trend"], "requiresClinicianReview": true }, "confidence": { "level": "moderate", "score": 0.62, "reasons": [ "5 of 6 inputs supplied", "Assessment history covers 2 points over 31 days" ] }, "dataQuality": { "completeness": 0.83, "missingInputs": ["biomarkerWatchCount"], "freshness": { "newestInputAt": "2026-06-01T00:00:00Z", "staleInputs": [] } }, "provenance": { "generatedAt": "2026-07-03T15:04:05.000Z", "engineVersion": "default", "method": "deterministic_rules", "inputs": [ { "name": "assessments", "value": "2 PHQ-9 points", "observedAt": "2026-06-01T00:00:00Z", "contributed": true }, { "name": "avgSleepHours", "value": 7.5, "observedAt": null, "contributed": true } ] }, "codes": [ { "system": "http://snomed.info/sct", "code": "225444004", "display": "At risk for relapse", "mapsTo": "summary.status" } ], "change": { "direction": "improved", "delta": -6, "previousStatus": "elevated", "currentStatus": "moderate", "explanations": [ "Score moved from 44 to 38 (-6).", "avgSleepHours improved from 5.1 to 7.5 — sleep disruption factor no longer firing.", "daysSinceLastVisit dropped from 30 to 12 — follow-up gap factor cleared." ], "previousComputedAt": "2026-05-01T00:00:00Z" }, "interpretationScale": { "low": "Low risk — routine monitoring.", "moderate": "Moderate risk — monitor and review at next visit.", "elevated": "Elevated risk — consider proactive outreach.", "high": "High risk — flag for prompt clinician review." }}Confidence and data quality are computed the same way everywhere: confidence blends input coverage with history depth, and completeness is the fraction of accepted inputs you actually supplied. That consistency means a single quality-gate in your code — if (intelligence.dataQuality.completeness < 0.5) ... — works across the entire suite.
Reasoning — explain: true
Pass explain: true in any intelligence request and the response's intelligence.reasoning field carries a plain-language interpretation of the deterministic findings. The interpretation is constrained to what the engine already computed — signals, thresholds, confidence, data-quality gaps, and change factors — and never adds diagnosis or treatment suggestions, so the deterministic posture is unchanged. Expect roughly 1–3 seconds of added latency; if generation fails or times out, the analysis still returns normally with reasoning: null.
intelligence.reasoning — present only when explain: true
| Field | Type | Description |
|---|---|---|
| narrative | string | 2–4 clinician-readable sentences interpreting the structured findings, including why the result changed when `previous` was supplied. |
| generatedAt | string | ISO timestamp of the interpretation. |
Change tracking — why did the result move?
The API is stateless: we never store your clinical results. To get a change explanation, include the prior result as previous in any intelligence request. The response's intelligence.change block then explains the movement — score delta, status band transitions, and per-input comparisons.
previous — request field (all five endpoints)
| Field | Type | Description |
|---|---|---|
| previous.score | number? | The prior numeric score, if the endpoint returns one. |
| previous.status | string? | The prior status/level string, exactly as returned before. |
| previous.computedAt | string? | ISO timestamp of the prior computation. Echoed back in change.previousComputedAt. |
| previous.inputs | object? | Prior input values keyed by field name. Enables per-input explanations like 'avgSleepHours improved from 5.1 to 7.5'. |
{ "assessments": [ ... ], "avgSleepHours": 7.5, "daysSinceLastVisit": 12, "previous": { "score": 44, "status": "elevated", "computedAt": "2026-05-01T00:00:00Z", "inputs": { "avgSleepHours": 5.1, "daysSinceLastVisit": 30 } }}Explanations are deterministic and ordered by impact. If previous is omitted, change is null — nothing else in the response is affected.
Terminology codes — SNOMED CT, LOINC, RxNorm
Where a standardized clinical terminology exists for a concept in the response, the intelligence.codes array carries the mapping. Each entry names the code system, the code, a display string, and a mapsTo pointer to the response field it annotates — so you can chart results in an EHR without maintaining your own crosswalk.
Code systems in use
| Field | Type | Description |
|---|---|---|
| http://snomed.info/sct | SNOMED CT | Clinical findings: MSE observations (e.g. 48767010 psychomotor agitation), risk states, medication response. |
| http://loinc.org | LOINC | Lab analytes on Biomarker Intelligence™ (e.g. 3016-3 TSH, 1989-3 Vitamin D) and instruments like 44261-6 PHQ-9. |
| http://www.nlm.nih.gov/research/umls/rxnorm | RxNorm | Medication ingredients on Medication Intelligence™ (e.g. 36437 sertraline). |
Codes are additive metadata: they never change scoring, and unmapped concepts simply have no entry. Coverage grows over releases without a version bump.
Webhooks & event streaming
Long-running resources — lab orders and wearable connections — emit signed webhook events so you never poll. Configure endpoint URLs per environment in the developer dashboard; each endpoint gets its own signing secret (whsec_...).
Event types
| Field | Type | Description |
|---|---|---|
| lab_order.results_ready | event | Structured results are parsed and available on the order. |
| lab_order.status_changed | event | Any status transition (collected, in_transit, processing...). |
| wearable.connected | event | Patient completed the provider connect flow. |
| wearable.sync_completed | event | A data sync finished; fresh metrics are queryable. |
| wearable.disconnected | event | Patient or provider revoked the connection. |
{ "id": "evt_7f3a91c24b8d4e0f92a1", "object": "event", "type": "lab_order.results_ready", "apiVersion": "2026-07-01", "createdAt": "2026-07-03T15:04:05.000Z", "data": { "object": "lab_order", "id": "ord_x82k", "status": "results_ready", "patientRef": "pt_4491" }}Every delivery is signed with an Attunio-Signature header (HMAC-SHA256 over {timestamp}.{payload}). Verify the signature and reject stale timestamps before trusting any event:
import { createHmac, timingSafeEqual } from "node:crypto" export function verifyWebhook(payload: string, header: string, secret: string): boolean { // Header format: t=<unix_ts>,v1=<hex_signature> const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("="))) const expected = createHmac("sha256", secret) .update(`${parts.t}.${payload}`) .digest("hex") // Reject events older than 5 minutes to prevent replay. if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))}Deliveries retry with exponential backoff for up to 24 hours until your endpoint returns a 2xx. Events are also queryable for 30 days via GET /v1/events for reconciliation, and you can stream them in order with the cursor parameter — useful for rebuilding state after downtime.
FHIR mappings & SMART on FHIR
Intelligence results map cleanly onto FHIR R4 Observation resources: the numeric score becomes valueInteger, summary.status drives interpretation, the headline lands in note, and entries from intelligence.codes populate code.coding — no local terminology work needed.
{ "resourceType": "Observation", "status": "final", "category": [{ "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "survey", "display": "Survey" }] }], "code": { "coding": [{ "system": "http://snomed.info/sct", "code": "225444004", "display": "At risk for relapse" }], "text": "Attunio Relapse Prediction" }, "subject": { "reference": "Patient/pt_4491" }, "effectiveDateTime": "2026-07-03T15:04:05Z", "valueInteger": 38, "interpretation": [{ "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", "code": "N", "display": "Normal" }], "text": "moderate" }], "note": [{ "text": "Relapse risk 38/100 (moderate) — sleep restored, follow-up gap closed." }], "device": { "display": "Attunio Relapse Prediction (deterministic_rules, engine default)" }}Field mapping — unified schema → FHIR R4
| Field | Type | Description |
|---|---|---|
| summary.status | Observation.interpretation | Status band mapped to v3-ObservationInterpretation, original string kept in .text. |
| summary.headline | Observation.note[0].text | The human-readable one-liner, verbatim. |
| score / level | Observation.valueInteger | valueString | Numeric engines use valueInteger; categorical engines use valueString. |
| codes[] | Observation.code.coding[] | SNOMED/LOINC/RxNorm entries carried through with system + code + display. |
| provenance.generatedAt | Observation.effectiveDateTime | When the intelligence was computed. |
| provenance.engineVersion | Observation.device.display | Engine + version string for auditability. |
Inside an EHR, use a standard SMART on FHIR launch: read charted assessments, score them through Attunio server-side, and write the Observation back to the chart.
import FHIR from "fhirclient"import { Attunio } from "@attunio/sdk" // 1. SMART on FHIR EHR launch (works in Epic, Cerner, Athena sandboxes)const fhirClient = await FHIR.oauth2.ready()const patient = await fhirClient.patient.read() // 2. Pull PHQ-9 scores already charted in the EHRconst bundle = await fhirClient.request( `Observation?patient=${patient.id}&code=http://loinc.org|44261-6&_sort=date`)const assessments = bundle.entry.map((e: any) => ({ instrumentId: "phq9", instrumentName: "PHQ-9", score: e.resource.valueQuantity.value, maxScore: 27, createdAt: e.resource.effectiveDateTime,})) // 3. Score relapse risk with Attunio (server-side proxy holds the secret key)const client = new Attunio({ apiKey: process.env.ATTUNIO_API_KEY! })const prediction = await client.relapse.predict({ assessments }) // 4. Write the result back to the chart as a FHIR Observationawait fhirClient.create(toFhirObservation(prediction, patient.id))End-to-end integration guide
The typical path from zero to production, distilled from real integrations. Most teams complete steps 1–4 in their first session.
1. Get keys
Create a developer account and grab a sandbox key (att_sk_test_...) from the dashboard. Sandbox is free and rate-limited; production keys require plan approval.
2. Health check
GET /v1/ping with your key. A 200 confirms auth, plan, and connectivity in one call.
3. First analysis
POST the telemetry you already have to the closest endpoint — most teams start with /v1/dmse/analyze or /v1/outcome/score. Read intelligence.summary first.
4. Wire change tracking
Store each response's score, status, and generatedAt. Pass them as `previous` on the next call and render intelligence.change.explanations in your UI.
5. Handle the edges
Branch on error.code (not HTTP status alone), log requestId, respect Retry-After on 429, and retry 5xx with backoff — or use an official SDK which does all four.
6. Go longitudinal
Add webhooks for lab results and wearable syncs, map outputs to FHIR Observations if you chart in an EHR, and enable more endpoints — the schema is already familiar.
Prefer working code over prose? Open the interactive explorer to run every endpoint with editable sample payloads, or start from a sample application.
Sample applications
Reference implementations demonstrating the three most common integration shapes. Each uses an official SDK, handles errors with request IDs, and renders the unified intelligence block.
Telehealth session sidebar
Next.js + TypeScript SDK
Captures session timing telemetry with @attunio/capture, streams it to /v1/dmse/analyze, and renders the intelligence.summary block live next to the video call.
Population risk dashboard
Python + FastAPI
Nightly batch job scores every active patient with /v1/relapse/predict, passes yesterday's result as `previous`, and surfaces the change.explanations feed to care managers.
EHR-embedded SMART app
SMART on FHIR + Java SDK
Launches inside the EHR, reads charted PHQ-9 Observations, scores outcomes with /v1/outcome/score, and writes results back as FHIR Observations with SNOMED codes.
Versioning & deprecation policy
The API is versioned at two levels, both designed so an integration you ship today keeps working without maintenance.
Versioning contract
| Field | Type | Description |
|---|---|---|
| URL version (/v1/) | major | Breaking changes only. A new major version is a new URL prefix; /v1/ continues to run in parallel. |
| intelligence.schema | string | The unified block is versioned independently ("attunio.intelligence/v1"). Additive fields never bump it. |
| provenance.engineVersion | string | Scoring-engine calibration version on every response — pin results to the exact model that produced them. |
What we will never do within /v1:
- Remove or rename an existing response field
- Change the type or meaning of an existing field
- Add a new required request field
- Repurpose an existing error code
What we may do without notice (additive):
- Add new response fields, endpoints, event types, and terminology codes
- Add new optional request fields
- Improve engine calibration (always visible via provenance.engineVersion)
Deprecation windows: when a capability is scheduled for removal, it is announced in the changelog and response headers (Deprecation + Sunset, per RFC 8594) at least 12 months before sunset for GA endpoints and 3 months for beta endpoints. Deprecated-but-live endpoints keep full SLAs until their sunset date.
Performance — latency, uptime, rate limits
All intelligence endpoints are deterministic rule engines — no model inference in the request path — so latency is dominated by network transit and stays flat under load.
Service targets
| Field | Type | Description |
|---|---|---|
| Latency (p50) | ~40 ms | Server processing time for intelligence endpoints, excluding network transit. |
| Latency (p99) | < 250 ms | Includes cold paths and calibrated-model lookups. |
| Uptime SLA | 99.9% | Production plans. Live status and 90-day history at the status page. |
| Payload limit | 1 MB | Per request body. Batch large histories into the documented array caps. |
| Timeout | 30 s | Server-side hard limit; requests never hang beyond it. |
Rate limits by plan
| Field | Type | Description |
|---|---|---|
| Sandbox | 100 req/day | Per key, all endpoints pooled. Resets at 00:00 UTC. Free. |
| Launch | 10 req/s · 50k/mo | Burst to 20 req/s for 10 s. Overage billed per request. |
| Scale | 50 req/s · 500k/mo | Burst to 100 req/s. Dedicated support channel. |
| Enterprise | Custom | Custom sustained/burst limits, private networking, BAA. |
Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. On 429 you also get Retry-After in seconds — the official SDKs honor it automatically. Measure real-world latency yourself: every response's X-Request-Id pairs with the server timing so support can trace any slow call.
Errors
Errors return a non-2xx status and a consistent JSON envelope.
{ "ok": false, "requestId": "req_2ab90c37e51f4d6c80a1", "error": { "code": "invalid_request", "message": "Telemetry validation failed.", "docs": "https://attunio.com/developers/docs#errors", "issues": [ { "path": "responseLatencySeconds", "message": "Number must be ≤ 120" } ] }}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Body is not valid JSON, or the payload failed schema validation. |
| 401 | unauthorized | Missing or malformed Authorization header. |
| 401 | invalid_token | API key is invalid or has been revoked. |
| 402 | payment_required | A production key without an active paid plan and approved production access. |
| 403 | forbidden | The key is valid but not permitted to call this resource. |
| 405 | method_not_allowed | Wrong HTTP method for the endpoint. |
| 429 | rate_limited | Daily sandbox quota exceeded. Upgrade a plan to raise limits. |
| 500 | server_error | Unexpected error processing the request. |
Plans, rate limits & billing
Sandbox (test) keys are free and limited to 500 requests per UTC day per account. Exceeding the limit returns 429 rate_limited.
Production (live) keys require a paid plan and approved production access. Paid plans include a monthly call allowance and are never hard-blocked — calls beyond the allowance are billed as metered overage at the end of each cycle. A live key used without an active plan and approved access returns 402 payment_required.
Manage your plan, view real-time usage against your allowance, and request production access from the Billing tab of your developer dashboard. For custom or high-volume agreements, contact hello@attuniohealth.com.
Roadmap
All five intelligence endpoints, API-key authentication, self-serve subscription plans, and usage-based overage billing are live today. A full OAuth 2.0 client-credentials flow and webhook delivery of asynchronous results are next on the roadmap; the Bearer-token header contract above is forward-compatible with the OAuth bearer model, so no breaking change is expected when it ships.
Get your API keys