SNOW integration guide
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, open a docs issue — one click, no account needed.
Using an AI assistant? Paste llms-full.txt into context for the complete API reference in one file.
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 POST /v1/sessions. New types ship without breaking
existing integrations — opt in by passing the new type value.
Available session types:
type value | Purpose | Path |
|---|---|---|
pre-consult | Pre-visit summary — patient fills before appointment | POST /v1/sessions/previsit/patient |
pre-visit-clinic | Pre-visit summary — staff fills in-clinic | POST /v1/sessions/previsit/provider |
pre-anc-visit | ANC pre-visit (alias) | use pre-consult + template_code: previsit_anc_v1 |
Additional session types are in active development — contact us if your workflow requires something not listed here.
SDKs: Native SDKs are not yet available. All examples use raw HTTP. A TypeScript SDK is planned for the first multi-type GA release.
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; runs the same summarization pipeline and emits real webhooks, but is exempt from daily quota and credit billing. Use during development — responses are real, not mocked.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.
Supported specialties
Section titled “Supported specialties”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/previsit/patient{ "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>" }}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:
curl
curl https://api.snowmed.io/v1/sessions/<session_id> \ -H "Authorization: Bearer sn_live_your_key"Python
import requests, osresp = requests.get( f"https://api.snowmed.io/v1/sessions/{session_id}", headers={"Authorization": f"Bearer {os.environ['SNOW_API_KEY']}"},)resp.raise_for_status()result = resp.json()# result["safety"]["requires_review"] — check before surfacing to doctorJava (OkHttp)
Request request = new Request.Builder() .url("https://api.snowmed.io/v1/sessions/" + sessionId) .addHeader("Authorization", "Bearer " + System.getenv("SNOW_API_KEY")) .build();try (Response response = client.newCall(request).execute()) { JSONObject result = new JSONObject(response.body().string()); boolean requiresReview = result.getJSONObject("safety").getBoolean("requires_review");}Valid type values
Section titled “Valid type values”pre-consult, pre-anc-visit, pre-visit-clinicAlias auto-mapped server-side: pre-anc-visit → pre-consult + template_code: previsit_anc_v1.
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: t=<unix_seconds>,v1=<hmac_sha256_hex>[,v1=<hmac_sha256_hex>]X-Snow-Delivery: <delivery_id>Content-Type: application/jsonDuring a secret-rotation window (24h), two v1= values may appear in the same
header — verify against either.
Verifying the signature
Section titled “Verifying the signature”import crypto from "crypto"
function verify(rawBody: string, header: string, secret: string): boolean { const [tPart, ...vParts] = header.split(",") const ts = tPart.replace("t=", "") const sigs = vParts.map(p => p.replace("v1=", ""))
const age = Date.now() / 1000 - Number(ts) if (age > 300) return false // 5-minute replay window
const expected = crypto .createHmac("sha256", secret) .update(`${ts}.${rawBody}`) .digest("hex")
return sigs.some(sig => sig.length === expected.length && 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 an emergency from its own triage logic)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.
Rate limits and quotas
Section titled “Rate limits and quotas”Two enforcement layers run on every authenticated POST.
Daily quota — hard cap per tenant, resets at UTC midnight.
X-Quota-Limit: 1000X-Quota-Remaining: 847X-Quota-Reset: <seconds-to-next-utc-midnight>On exhaust: 429 { "code": "daily_quota_exceeded", "limit": 1000, "used": 1000 } + Retry-After header.
Per-minute rate limit — flow control derived from your daily quota.
X-RateLimit-Limit: NX-RateLimit-Remaining: NX-RateLimit-Reset: <unix>On exhaust: 429 { "code": 1003 } + Retry-After header.
Idempotent replays (cached responses via Idempotency-Key) consume neither layer. sn_test_* keys use a separate quota bucket.
Quota limits are set per tenant at onboarding. Contact SNOW to review or raise your limits.
Error handling
Section titled “Error handling”See the Error reference for the full list of HTTP codes, error shapes, and retry guidance.
Quick rules:
4xx— fix the request, do not retry429— wait forRetry-After, then retry5xx— retry with exponential backoff (1s → 5s → 30s)- Always use
Idempotency-Keyon POST so retries don’t duplicate sessions
Compliance and data handling
Section titled “Compliance and data handling”SNOW is designed for clinical data in the Indian healthcare market.
| Topic | Current state |
|---|---|
| Data residency | API and DB hosted on Railway (AWS ap-south-1 Mumbai) |
| Encryption in transit | TLS 1.2+ on all endpoints |
| Encryption at rest | Cloudflare R2 (AES-256) for documents; Neon Postgres for structured data |
| PHI risk detection | Logged and flagged on every LLM call (non-blocking) |
| Data retention | Configurable per tenant; default policy at docs/security/data-retention-policy.md |
| DPA | Template available — contact SNOW before going live with real patient data |
| HIPAA / BAA | Roadmap (Vertex AI HIPAA-eligible products; targeted before real-patient traffic) |
| DPDP Act (India) | Aligned; formal certification roadmap in 2026 H2 |
If your organisation requires a signed DPA or BAA before integration, contact SNOW before starting development.
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.