Skip to content

SNOW integration guide — for any integrator

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.


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:

A small, finite set of integration shapes. You will use one or more:

#FlowWho enters the data
1Hosted form, fixed questions — patient gets a link, fills a single-submit formPatient (via SNOW-hosted UI)
2Hosted form, full UX — questions + uploads + voice, iterativePatient (via SNOW-hosted UI)
3Direct API — you POST everything you haveYour app’s backend
4Hosted form, dynamic questions — LLM asks questions one at a timePatient + 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:

TypePurposeSpecialized endpoint
pre-consultPre-visit summary before an appointment(use /v1/sessions)
clinical-noteSOAP note from doctor input(use /v1/sessions)
emergencyEmergency triage card/v1/emergency
dischargeDischarge summary/v1/discharge
referralReferral letter to a specialist/v1/referral
lab-requisitionLab order/v1/lab-requisition
medicationMedication / pharmacy order/v1/pharmacy-requisition
patient-instructionsPlain-language patient handout (10+ Indian languages)/v1/patient-instructions
radiologyRadiology summary(use /v1/sessions)
care-planLongitudinal care plan(use /v1/sessions)
transferInter-facility transfer summary(use /v1/sessions)
historyUploads-only history extraction(use /v1/sessions)
internationalInternational 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.


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.

HeaderPurpose
Idempotency-Key: <uuid>Replays return the cached response (24h TTL). Body mismatches return 409.
Content-Type: application/jsonRequired on POST/PUT

Your API key is tenant-scoped. Every call is attributed to your tenant automatically. You don’t pass a tenant ID.


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.

Terminal window
POST /v1/intake-sessions
Authorization: Bearer sn_live_<your_key>
Content-Type: application/json
Idempotency-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
}
{
"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.

A single-page form rendered from a SNOW-catalog template matching the specialty (obstetricsanc_first_visit, multilingual). Patient fills, submits, sees a thank-you screen.

EventWhen
intake_session.createdRight after your POST
intake_session.submittedPatient submits the form
intake_session.completedSNOW finishes summarization (~30-60s after submit)
intake_session.failedProcessing 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.

Terminal window
POST /v1/sessions
Authorization: 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>" }
}
{
"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"
}

A multi-step page:

  1. Intro — clinic + doctor info
  2. Iterative Q&A — one question at a time, voice input optional
  3. Uploads — camera + file picker for prior reports
  4. Review + submit

Voice transcription, file uploads to R2, idempotent submit — all built in.

session.completed (or .failed) with the structured summary.


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.

Terminal window
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" }
]
}
{
"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:

Terminal window
GET /v1/sessions/<session_id>
pre-consult, clinical-note, discharge, referral, transfer,
medication, lab-requisition, history, international,
emergency, emergency-summary, pre-anc-visit, pre-visit-clinic,
care-plan, radiology

Aliases auto-mapped server-side: emergency-summary → emergency, pre-anc-visit → pre-consult.

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.

Terminal window
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.

Terminal window
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>"
}
Terminal window
PUT <upload_url>
Content-Type: application/pdf
<file bytes>

No auth header — the presigned URL is the credential.

Terminal window
POST /v1/documents/<document_id>/process
Terminal window
GET /v1/documents/<document_id>

Status: pendingprocessingcompleted. Once complete, pass document_id into a /v1/sessions body (Flow 3).

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.


Via the developer console (platform.snowmed.health → Webhooks) or via API:

Terminal window
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.

X-Snow-Signature: v1,t=<unix-ts>,sig=<hex-hmac>
X-Snow-Event: session.completed
X-Snow-Delivery-Id: <uuid>
Content-Type: application/json
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).

EventFires whenKey fields
session.completed/v1/sessions summarization donesession_id, type, summary, triage_tier
session.failedSame, on failuresession_id, error
session.testDaily test cron (opt-in)static test payload
intake_session.createdAfter POST /v1/intake-sessionssession_id, token, intake_url
intake_session.submittedPatient submits hosted formsession_id
intake_session.processingWorker picks up jobsession_id
intake_session.completedSummarization donesession_id, summary, triage_tier
intake_session.failedProcessing failssession_id, error

Every delivery has X-Snow-Delivery-Id. Dedupe on it — SNOW retries with the same ID on transient failures.

  • 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

Your backend SNOW
───────────── ─────────
1. POST /v1/intake-sessions ──────────────────────► 201 + token + intake_url
◄─── webhook intake_session.created
2. Send WhatsApp link
3. (patient fills form, submits)
◄─── webhook intake_session.submitted
◄─── webhook intake_session.completed
4. GET /v1/intake-sessions/<token> ────────────────► 200 + result
5. Render summary in your UI
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 views

  • 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_minutes to override.
  • Specialty fallback. If you pass an unsupported specialty, SNOW falls back to the general template 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.

Before going live, confirm each of these end-to-end:

  • Logged into platform.snowmed.health via Google
  • Minted a sn_test_* key
  • Minted a sn_live_* key (separate label)
  • Configured webhook URL + saved the whsec_* secret
  • POST /v1/intake-sessions returns 201 + token + URL
  • Opening intake_url in a browser renders the form
  • Submitting the form returns the thank-you screen
  • intake_session.submitted webhook arrives within 5s
  • HMAC signature verifies with your secret
  • intake_session.completed webhook arrives within 60s
  • GET /v1/intake-sessions/<token> returns the result
  • Idempotency-Key replay returns the same body
Section titled “Flow 2 / Flow 4 (if you’ll use sessions patient-link)”
  • POST /v1/sessions returns 201 + session_url
  • Opening session_url renders the iterative form
  • Voice/upload steps work on a real device
  • session.completed webhook arrives

Flow 3 (if you’ll use sessions direct mode)

Section titled “Flow 3 (if you’ll use sessions direct mode)”
  • POST /v1/sessions (with patient, document_ids, metadata) returns 201
  • session.completed webhook arrives (or GET /v1/sessions/<id> returns the result)
  • Receives session.test from the daily cron (or via POST /v1/me/webhook/test)
  • HMAC verifies
  • Handler is idempotent on X-Snow-Delivery-Id
  • Returns 2xx within 5s (otherwise SNOW retries)

EndpointAuthUsed in
POST /v1/intake-sessionsAPI keyFlow 1
GET /v1/intake-sessions/:tokenAPI keyFlow 1 (poll result)
GET /v1/intake-sessions/:token/infononeFlow 1 (browser, public)
POST /v1/intake-sessions/:token/submitnoneFlow 1 (browser, public)
POST /v1/sessionsAPI keyFlows 2, 3, 4
GET /v1/sessions/:idAPI keyFlow 3 (poll result)
GET /v1/sessions/:token/infononeFlows 2, 4 (browser, public)
GET /v1/sessions/:token/next-questionnoneFlow 4 (LLM dynamic)
POST /v1/sessions/:token/answernoneFlow 2/4 (per turn)
POST /v1/sessions/:token/uploads/presignnoneFlow 2 (browser uploads)
POST /v1/sessions/:token/submitnoneFlow 2/4 final submit
POST /v1/documents/upload-urlAPI keyFlow 3 prep
POST /v1/documents/:id/processAPI keyFlow 3 prep
GET /v1/documents/:idAPI keyFlow 3 prep
PUT /v1/me/webhookAPI key + dev roleSetup
GET /v1/me/webhook/deliveriesAPI keyDebugging
POST /v1/me/webhook/deliveries/:id/replayAPI keyRecovery

Full reference: see per-endpoint pages in the developer console.


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.

IntegratorFlow 1Flow 2Flow 3Flow 4Status
preg.care (PregCare patient app)✓ ANC pre-visitoptional✓ Emergency triageoptionalPilot
PHR-Connect (care.healthreports.online)optional✓ Patient uploads + voice✓ Direct modeoptionalPilot
clinic.preg.care (Klarity Gynaec)optional✓ Hosted form with custom_questions✓ Doctor-typed clinical-note✓ Dynamic deep-divePilot

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 guide

A recipe page covers what’s specific to that summary type:

  • Recommended type value and any aliases
  • Required fields beyond the shared ones (e.g., discharge may need admit_date, discharge_date)
  • Output schema (what the summary field 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-requisition summarization. See docs/integrations/recipes/lab-requisition.md. Existing integrations are unaffected — opt in by passing type: "lab-requisition" on /v1/sessions (or use the /v1/lab-requisition endpoint directly).”

You never need to re-read this whole guide when a new type ships. Read the recipe; everything else stays the same.

  • 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.