SNOW integration guide — for any integrator
SNOW integration guide
Section titled “SNOW integration guide”This guide is everything you need to integrate any clinical app with SNOW. One API key, one webhook receiver, four supported flows.
Reading this top-to-bottom should answer every integration question. If something isn’t covered, it’s a docs gap — please flag it.
What SNOW is
Section titled “What SNOW is”SNOW is a clinical-summarization platform. You give it some combination of patient questions, uploaded reports, or pre-collected structured data; SNOW returns a structured clinical summary suitable for a doctor’s review.
There are two orthogonal axes you’ll work with:
Axis 1 — the flow (how data gets in)
Section titled “Axis 1 — the flow (how data gets in)”A small, finite set of integration shapes. You will use one or more:
| # | Flow | Who enters the data |
|---|---|---|
| 1 | Hosted form, fixed questions — patient gets a link, fills a single-submit form | Patient (via SNOW-hosted UI) |
| 2 | Hosted form, full UX — questions + uploads + voice, iterative | Patient (via SNOW-hosted UI) |
| 3 | Direct API — you POST everything you have | Your app’s backend |
| 4 | Hosted form, dynamic questions — LLM asks questions one at a time | Patient + LLM (via SNOW-hosted UI) |
The same API key covers all four.
Axis 2 — the summary type (what comes out)
Section titled “Axis 2 — the summary type (what comes out)”An open and growing list of summarization products. Each is selected
by the type field on /v1/sessions (or by the specialized endpoints
under /v1/summarize/*). New types ship without breaking existing
integrations:
| Type | Purpose | Specialized endpoint |
|---|---|---|
pre-consult | Pre-visit summary before an appointment | (use /v1/sessions) |
clinical-note | SOAP note from doctor input | (use /v1/sessions) |
emergency | Emergency triage card | /v1/emergency |
discharge | Discharge summary | /v1/discharge |
referral | Referral letter to a specialist | /v1/referral |
lab-requisition | Lab order | /v1/lab-requisition |
medication | Medication / pharmacy order | /v1/pharmacy-requisition |
patient-instructions | Plain-language patient handout (10+ Indian languages) | /v1/patient-instructions |
radiology | Radiology summary | (use /v1/sessions) |
care-plan | Longitudinal care plan | (use /v1/sessions) |
transfer | Inter-facility transfer summary | (use /v1/sessions) |
history | Uploads-only history extraction | (use /v1/sessions) |
international | International Patient Summary (IPS, FHIR-bundled) | /v1/patient-summary?standard=ips |
This list grows. As SNOW ships new summary products, they appear here as
new type values and (where useful) new specialized endpoints. Existing
integrations continue working unchanged when new types are added — you
only need to opt in to a new type when you want to use it.
Per-type recipe pages live in docs/integrations/recipes/<type>.md (one
page per summary type, added as each ships). This document covers the
shared mechanics (auth, flows, webhooks, error model). Use this guide
plus the recipe(s) you need.
Authentication
Section titled “Authentication”One key for all endpoints
Section titled “One key for all endpoints”Authorization: Bearer sn_live_<your_key>Mint the key in the SNOW developer console (platform.snowmed.health → API
keys). Console access is gated by Google login. The key itself, once minted,
is what you pass on every server-to-server call — never a Google token.
Test vs live:
sn_test_*— sandbox; emits webhooks, does not run the real LLM pipeline. Use during development.sn_live_*— production; real summarization, real billing.
Never expose the key in your mobile or web client. All calls must go through your backend.
Optional but recommended headers
Section titled “Optional but recommended headers”| Header | Purpose |
|---|---|
Idempotency-Key: <uuid> | Replays return the cached response (24h TTL). Body mismatches return 409. |
Content-Type: application/json | Required on POST/PUT |
Tenant context
Section titled “Tenant context”Your API key is tenant-scoped. Every call is attributed to your tenant automatically. You don’t pass a tenant ID.
The 4 flows — pick yours
Section titled “The 4 flows — pick yours”Decision tree
Section titled “Decision tree”Does your app already have all the data you want summarized? ├── Yes → Flow 3 (direct API, no patient UI) └── No → patient enters data via a SNOW-hosted form │ ├── Just questions, no uploads, no voice → Flow 1 (intake-sessions) ├── Questions + uploads + voice, iterative → Flow 2 (sessions, patient-link) └── Dynamic LLM-driven questions + uploads → Flow 4 (sessions, patient-link, no template)You can use multiple flows. Example: emergency triage = Flow 3, scheduled pre-visit = Flow 1, doctor-driven deep-dive = Flow 4.
Flow 1 — hosted form, fixed questions, single-submit
Section titled “Flow 1 — hosted form, fixed questions, single-submit”Use when: you want a low-friction WhatsApp/SMS/email link that drops the patient into a one-page form. No uploads, no voice.
Request
Section titled “Request”POST /v1/intake-sessionsAuthorization: Bearer sn_live_<your_key>Content-Type: application/jsonIdempotency-Key: <uuid> # optional
{ "specialty": "obstetrics", "patient_id": "<your-patient-uuid>", # optional "appointment_id": "<your-appt-uuid>", # optional "expires_in_minutes": 60, # optional, default 60 "surface": "previsit" # optional, default previsit}Response
Section titled “Response”{ "session_id": "aae5c6ed-51ad-4773-b73b-62101b82e29a", "token": "9jiGQhmCV9VEDNR0bK4UT6gI", "intake_url": "https://previsit.summary.to/intake/9jiGQhmCV9VEDNR0bK4UT6gI", "specialty": "obstetrics", "expires_at": "2026-05-08T09:09:22.706Z", "created_at": "2026-05-08T08:09:22.709Z"}You then deliver intake_url to the patient via WhatsApp / SMS / email.
What the patient sees
Section titled “What the patient sees”A single-page form rendered from a SNOW-catalog template matching the
specialty (obstetrics → anc_first_visit, multilingual). Patient fills,
submits, sees a thank-you screen.
Webhooks you receive
Section titled “Webhooks you receive”| Event | When |
|---|---|
intake_session.created | Right after your POST |
intake_session.submitted | Patient submits the form |
intake_session.completed | SNOW finishes summarization (~30-60s after submit) |
intake_session.failed | Processing fails |
Then GET /v1/intake-sessions/<token> returns the full result.
Specialties supported (form templates seeded today)
Section titled “Specialties supported (form templates seeded today)”obstetrics, gynecology, pediatrics, dermatology, ent, psychiatry,
ophthalmology, dentistry, cardiology, orthopedics, general. Unknown
specialties fall back to general.
Flow 2 — hosted form, full UX (questions + uploads + voice)
Section titled “Flow 2 — hosted form, full UX (questions + uploads + voice)”Use when: patients should be able to upload prior reports (PDFs, lab images, USG scans) and/or speak their answers via mic.
Request
Section titled “Request”POST /v1/sessionsAuthorization: Bearer sn_live_<your_key>Content-Type: application/json
{ "type": "pre-consult", "specialty": "obstetrics", "initiator": "patient", "template_code": "anc_first_visit", # optional — pin a specific template "patient_id": "<your-patient-uuid>", "appointment_id":"<your-appt-uuid>", "metadata": { "source": "<your-app-name>" }}Response
Section titled “Response”{ "session_id": "3b6f1970-9528-49e9-9d96-7934b8b6b2de", "token": "bRJIY1oc8BgtMCucjWfPJk3P", "type": "pre-consult", "specialty": "obstetrics", "session_url": "https://previsit.summary.to/obstetrics/bRJIY1oc8BgtMCucjWfPJk3P", "expires_at": "2026-05-08T11:24:59.665Z", "ui_type": "patient"}What the patient sees
Section titled “What the patient sees”A multi-step page:
- Intro — clinic + doctor info
- Iterative Q&A — one question at a time, voice input optional
- Uploads — camera + file picker for prior reports
- Review + submit
Voice transcription, file uploads to R2, idempotent submit — all built in.
Webhooks
Section titled “Webhooks”session.completed (or .failed) with the structured summary.
Flow 3 — direct API, you have the data
Section titled “Flow 3 — direct API, you have the data”Use when: your app already collected the data — from your own UI, your DB, your AVI red-flag detection — and you just want a summary back. No patient-facing URL.
Request
Section titled “Request”POST /v1/sessions{ "type": "pre-consult", # or "emergency", "discharge", etc. "specialty": "obstetrics", "initiator": "system", # or "doctor", "patient" "patient": { "id": "<your-patient-uuid>", "name": "Optional", "age": 28, "gravida": 2, "para": 1, "lmp": "2026-02-14" }, "document_ids": [ "<doc-uuid-1>", "<doc-uuid-2>" ], # see Documents API "custom_questions": [ { "id": "chief_complaint", "question": "Reason for visit", "answer": "Routine ANC checkup" } ], "metadata": { "source": "<your-app-name>", "user_id": "<your-user-id>", "urgency": "emergency" # only for emergency type }, "red_flags": [ # optional, accepted for emergency { "type": "bleeding", "severity": "high" } ]}Response
Section titled “Response”{ "session_id": "...", "token": "...", # included even though you may not use it "type": "pre-consult", "specialty": "obstetrics", "expires_at": "...", "ui_type": "clinician"}You don’t need to share the URL with anyone. Either wait for the
session.completed webhook, or poll:
GET /v1/sessions/<session_id>Valid type values
Section titled “Valid type values”pre-consult, clinical-note, discharge, referral, transfer,medication, lab-requisition, history, international,emergency, emergency-summary, pre-anc-visit, pre-visit-clinic,care-plan, radiologyAliases auto-mapped server-side: emergency-summary → emergency,
pre-anc-visit → pre-consult.
Emergency lane
Section titled “Emergency lane”Set metadata.urgency: "emergency" to route the session through the
priority queue (target SLA < 30s).
Flow 4 — hosted form, LLM-driven dynamic questions
Section titled “Flow 4 — hosted form, LLM-driven dynamic questions”Use when: you want the question list to adapt based on prior answers,
rather than being fixed. Same hosted UI as Flow 2; the difference is
no template_code and no custom_questions[] — the server generates
each next question via LLM.
Request
Section titled “Request”POST /v1/sessions{ "type": "pre-consult", "specialty": "obstetrics", "purpose": "second-trimester checkup", # optional, biases the LLM "language": "en", # or "hi", "te", "mr" "initiator": "doctor", "patient_id": "<your-patient-uuid>", "metadata": { "source": "<your-app-name>" }}Response and patient UX are identical to Flow 2. Internally, each turn calls
GET /v1/sessions/:token/next-question which produces the next question
conditioned on prior answers.
Documents API — uploading reports from your backend
Section titled “Documents API — uploading reports from your backend”Used in Flow 3 to attach prior reports to a session.
Step 1 — get a presigned URL
Section titled “Step 1 — get a presigned URL”POST /v1/documents/upload-url{ "filename": "prior_usg.pdf", "mimeType": "application/pdf", "size_bytes": 245678}Response:
{ "upload_url": "https://snow-files.<...>.r2.cloudflarestorage.com/...?X-Amz-Signature=...", "document_id": "99382880-cdde-4dae-8174-d03984fec4e7", "storage_key": "<tenant>/<doc>/<filename>"}Step 2 — upload the file
Section titled “Step 2 — upload the file”PUT <upload_url>Content-Type: application/pdf
<file bytes>No auth header — the presigned URL is the credential.
Step 3 — trigger processing
Section titled “Step 3 — trigger processing”POST /v1/documents/<document_id>/processStep 4 — fetch result
Section titled “Step 4 — fetch result”GET /v1/documents/<document_id>Status: pending → processing → completed. Once complete, pass
document_id into a /v1/sessions body (Flow 3).
Patient-driven uploads (Flow 2)
Section titled “Patient-driven uploads (Flow 2)”When the patient uploads from the hosted form, they hit
POST /v1/sessions/:token/uploads/presign directly — no auth needed; the
session token authenticates. Files attach to the session automatically.
Webhooks
Section titled “Webhooks”Configure once
Section titled “Configure once”Via the developer console (platform.snowmed.health → Webhooks) or via API:
PUT /v1/me/webhook{ "url": "https://your-app.example.com/webhooks/snow", "secret": "whsec_<auto-generated>"}You receive a whsec_* secret on creation. Save it immediately — it
won’t be shown again. Used for HMAC verification.
Headers on every delivery
Section titled “Headers on every delivery”X-Snow-Signature: v1,t=<unix-ts>,sig=<hex-hmac>X-Snow-Event: session.completedX-Snow-Delivery-Id: <uuid>Content-Type: application/jsonVerifying the signature
Section titled “Verifying the signature”import crypto from "crypto"
function verify(body: string, header: string, secret: string): boolean { const m = header.match(/v1,t=([^,]+),sig=([a-f0-9]+)/) if (!m) return false const [, ts, sig] = m
const age = Date.now() / 1000 - Number(ts) if (age > 300) return false // 5-minute replay window
const expected = crypto .createHmac("sha256", secret) .update(`${ts}.${body}`) .digest("hex")
return crypto.timingSafeEqual( Buffer.from(sig, "hex"), Buffer.from(expected, "hex"), )}Body MUST be the raw bytes (don’t JSON.parse then re-stringify).
Events
Section titled “Events”| Event | Fires when | Key fields |
|---|---|---|
session.completed | /v1/sessions summarization done | session_id, type, summary, triage_tier |
session.failed | Same, on failure | session_id, error |
session.test | Daily test cron (opt-in) | static test payload |
intake_session.created | After POST /v1/intake-sessions | session_id, token, intake_url |
intake_session.submitted | Patient submits hosted form | session_id |
intake_session.processing | Worker picks up job | session_id |
intake_session.completed | Summarization done | session_id, summary, triage_tier |
intake_session.failed | Processing fails | session_id, error |
Idempotency on your end
Section titled “Idempotency on your end”Every delivery has X-Snow-Delivery-Id. Dedupe on it — SNOW retries with
the same ID on transient failures.
Delivery model
Section titled “Delivery model”- Inline first attempt at event-emit time
- Recovery sweep retries every 5 min for failed/in-flight deliveries up to 24h
- Delivery log visible in the developer console
- Replay manually via
POST /v1/me/webhook/deliveries/:id/replay
Lifecycle examples
Section titled “Lifecycle examples”Flow 1 (intake-sessions, single-submit)
Section titled “Flow 1 (intake-sessions, single-submit)”Your backend SNOW───────────── ─────────1. POST /v1/intake-sessions ──────────────────────► 201 + token + intake_url ◄─── webhook intake_session.created2. Send WhatsApp link3. (patient fills form, submits) ◄─── webhook intake_session.submitted ◄─── webhook intake_session.completed4. GET /v1/intake-sessions/<token> ────────────────► 200 + result5. Render summary in your UIFlow 3 (direct, emergency)
Section titled “Flow 3 (direct, emergency)”1. (your app detects emergency from AVI / red-flags)2. POST /v1/sessions {type:"emergency",...} ─────────► 201 + session_id ◄─── webhook session.completed (priority lane, ~30s)3. Render triage card in patient + doctor viewsCommon gotchas
Section titled “Common gotchas”- Don’t expose the API key client-side. Mobile/web clients must call your backend, which calls SNOW.
- Idempotency-Key on POSTs. Network retries are common; without Idempotency-Key you may double-create sessions.
- Use raw body for HMAC verification. Re-stringifying parsed JSON gives a different byte sequence and breaks verification.
- Token expiry is 60 minutes by default. Patients on slow flows may need
extension; pass
expires_in_minutesto override. - Specialty fallback. If you pass an unsupported specialty, SNOW falls
back to the
generaltemplate silently. Pass a known one (see the specialty list above). - Webhook URL must be HTTPS in production. HTTP is rejected.
- Replay window: 5 min. Webhook deliveries older than 5 min should be rejected by your verifier.
Smoke-test checklist
Section titled “Smoke-test checklist”Before going live, confirm each of these end-to-end:
Auth + console
Section titled “Auth + console”- Logged into
platform.snowmed.healthvia Google - Minted a
sn_test_*key - Minted a
sn_live_*key (separate label) - Configured webhook URL + saved the
whsec_*secret
Flow 1 (if you’ll use intake-sessions)
Section titled “Flow 1 (if you’ll use intake-sessions)”-
POST /v1/intake-sessionsreturns 201 + token + URL - Opening
intake_urlin a browser renders the form - Submitting the form returns the thank-you screen
-
intake_session.submittedwebhook arrives within 5s - HMAC signature verifies with your secret
-
intake_session.completedwebhook arrives within 60s -
GET /v1/intake-sessions/<token>returns the result - Idempotency-Key replay returns the same body
Flow 2 / Flow 4 (if you’ll use sessions patient-link)
Section titled “Flow 2 / Flow 4 (if you’ll use sessions patient-link)”-
POST /v1/sessionsreturns 201 +session_url - Opening
session_urlrenders the iterative form - Voice/upload steps work on a real device
-
session.completedwebhook arrives
Flow 3 (if you’ll use sessions direct mode)
Section titled “Flow 3 (if you’ll use sessions direct mode)”-
POST /v1/sessions(withpatient,document_ids,metadata) returns 201 -
session.completedwebhook arrives (orGET /v1/sessions/<id>returns the result)
Webhook receiver
Section titled “Webhook receiver”- Receives
session.testfrom the daily cron (or viaPOST /v1/me/webhook/test) - HMAC verifies
- Handler is idempotent on
X-Snow-Delivery-Id - Returns 2xx within 5s (otherwise SNOW retries)
API reference (quick)
Section titled “API reference (quick)”| Endpoint | Auth | Used in |
|---|---|---|
POST /v1/intake-sessions | API key | Flow 1 |
GET /v1/intake-sessions/:token | API key | Flow 1 (poll result) |
GET /v1/intake-sessions/:token/info | none | Flow 1 (browser, public) |
POST /v1/intake-sessions/:token/submit | none | Flow 1 (browser, public) |
POST /v1/sessions | API key | Flows 2, 3, 4 |
GET /v1/sessions/:id | API key | Flow 3 (poll result) |
GET /v1/sessions/:token/info | none | Flows 2, 4 (browser, public) |
GET /v1/sessions/:token/next-question | none | Flow 4 (LLM dynamic) |
POST /v1/sessions/:token/answer | none | Flow 2/4 (per turn) |
POST /v1/sessions/:token/uploads/presign | none | Flow 2 (browser uploads) |
POST /v1/sessions/:token/submit | none | Flow 2/4 final submit |
POST /v1/documents/upload-url | API key | Flow 3 prep |
POST /v1/documents/:id/process | API key | Flow 3 prep |
GET /v1/documents/:id | API key | Flow 3 prep |
PUT /v1/me/webhook | API key + dev role | Setup |
GET /v1/me/webhook/deliveries | API key | Debugging |
POST /v1/me/webhook/deliveries/:id/replay | API key | Recovery |
Full reference: see per-endpoint pages in the developer console.
Pilot validation matrix
Section titled “Pilot validation matrix”This guide is being validated against three live integrators. Each one exercises a different combination of flows. If any of them can’t fully self-serve from this doc, the doc has a gap.
| Integrator | Flow 1 | Flow 2 | Flow 3 | Flow 4 | Status |
|---|---|---|---|---|---|
| preg.care (PregCare patient app) | ✓ ANC pre-visit | optional | ✓ Emergency triage | optional | Pilot |
| PHR-Connect (care.healthreports.online) | optional | ✓ Patient uploads + voice | ✓ Direct mode | optional | Pilot |
| clinic.preg.care (Klarity Gynaec) | optional | ✓ Hosted form with custom_questions | ✓ Doctor-typed clinical-note | ✓ Dynamic deep-dive | Pilot |
Track per-pilot blockers in
docs/integrations/integration-readiness-status.md.
How this guide extends as SNOW ships new summary types
Section titled “How this guide extends as SNOW ships new summary types”This document describes the mechanics — auth, the 4 flows, webhooks, documents, error model. These don’t change as new summary types ship.
Each new summary type ships with one new recipe page under
docs/integrations/recipes/:
docs/integrations/├── integration-guide.md ← you are here (mechanics, stable)└── recipes/ ├── pre-consult.md ← per-type recipe (request body, output schema, examples) ├── emergency.md ├── discharge.md ├── referral.md ├── lab-requisition.md ├── medication.md ├── patient-instructions.md └── ... ← new types added here without touching this guideA recipe page covers what’s specific to that summary type:
- Recommended
typevalue and any aliases - Required fields beyond the shared ones (e.g.,
dischargemay needadmit_date,discharge_date) - Output schema (what the
summaryfield looks like for this type) - Example request + example response
- Specialty-specific notes (if applicable)
- Pricing tier (if different from base)
When a new summary product ships, integrators get an update like:
“SNOW just shipped
lab-requisitionsummarization. Seedocs/integrations/recipes/lab-requisition.md. Existing integrations are unaffected — opt in by passingtype: "lab-requisition"on/v1/sessions(or use the/v1/lab-requisitionendpoint directly).”
You never need to re-read this whole guide when a new type ships. Read the recipe; everything else stays the same.
When this doc is wrong or incomplete
Section titled “When this doc is wrong or incomplete”- Wrong endpoint shape → file an issue with
session_id+ curl reproducer - Missing flow → describe the use case; we’ll either map it to an existing flow or add a fifth
- Webhook delivery problem → share
delivery_id; we can trace through the delivery log
This doc is the source of truth. Per-integrator notes (e.g.,
pregcare-cutover-2026-05-07.md) are negotiation snapshots, not contracts.