Pogosh CDS API — Integration Guide
1. What the API is, and what it is not
Pogosh CDS is a JSON API. You send a clinical question (symptoms, a condition, or a clinical note); it returns structured, coded recommendations: which labs (LOINC), which imaging (RadLex), which specialist, when to escalate, and whether the note describes a time-critical emergency.
The API returns data, not screens. Your EHR decides how to show it: a side panel, an order-set suggestion, a banner, a pre-filled referral. Pogosh never writes to your chart and never stores your patient's data.
| You get from the API | You build (or use ours) |
|---|---|
| Condition match from symptoms or a note | The place in your workflow where it appears |
| Labs and imaging with priority and rationale | Buttons to accept, dismiss or order |
| Specialist routing and escalation triggers | Referral creation |
| Emergency safety check on a note | The alert UI |
| Patient-specific AI analysis against the profile | Where that text is shown |
| Note analyzer (paste a note → conditions, emergency check, history flags) | — |
| Note writer (New Encounter / Follow-up / Disposition drafts) | — |
| Chart-read copilot (chart + brief input → HPI, alerts, chart-weighted differential, workup, plan; follow-up and discharge notes) | — |
| Clinical calculators offered for the matched conditions (CURB-65, Wells, HEART, MELD 3.0…) with references | The form and the Calculate button (the definition endpoint describes every input) |
Two ready-made clients exist: the OpenEMR module (oe-module-pogosh-cds) and the browser tool at pogosh.com/try. An EHR that is neither integrates the API directly; §9 shows the recommended flow. The copilot endpoints (§4) are the same code that runs pogosh.com/try and the OpenEMR copilot, so an EHR gets the tested behaviour without rebuilding it.
2. Base URL, authentication, limits
| Item | Value |
|---|---|
| Base URL | https://api.pogosh.com |
| Auth | Authorization: Bearer <key> on every request. (A ?key= query parameter is also accepted; do not use it — it lands in proxy logs.) |
| Key handling | Server-side only. Never in browser code, mobile apps or a public repository. |
| Rate limit | Per key, per rolling hour (default 1,000; validation partners 2,000). Over the limit → HTTP 429 with {"error":"Rate limit exceeded. Try again later.","limit":N}. No rate-limit headers are sent. |
| Note size | /cds/emergency and note endpoints: 8,000 characters maximum (HTTP 413 above that). |
| CORS | Browser calls are allowed only from pogosh.com and gighz.com origins. Call the API from your server, not from the browser. |
| Transport | HTTPS only, TLS 1.2+. |
| Timeouts | Corpus endpoints respond in well under a second. AI endpoints call a language model: /cds/emergency and /cds/analyze 2–10 s; /cds/note/* 5–15 s; /cds/chart/* 10–25 s. The gateway's hard limit is 30 s; set a 35 s client timeout and retry once on a gateway 5xx. Send extracted_facts back on later chart calls (§4.3) to skip re-extraction. |
| Versioning | Every response carries "version": "4.22". Fields are added, never renamed or removed, without a version change. |
| Errors | Always JSON: {"error": "<message>"} with HTTP 400 (bad input), 401 (no/invalid key), 403 (tier), 404, 413, 429, 500/502/503. |
Tiers: demo (limited, strips rationale, no AI endpoints) and premium (everything below). Validation partners are premium.
HIPAA, PHI and the BAA
- Sign the BAA before sending PHI. Partners sign the Business Associate Agreement online during onboarding; GigHz LLC acts as your business associate, or as a subcontractor if you are an EHR vendor serving covered entities.
- Where it runs: AWS (US East), HIPAA-eligible services only, under the AWS Business Associate Addendum. The language model runs under OpenAI's Business Associate Agreement with Zero Data Retention.
- What is stored: no clinical content. Requests are processed in memory and discarded; logs never contain request bodies or note text.
- Minimize what you send: no names, dates of birth, record numbers or contact details in structured fields. Identifier patterns are redacted before model calls, but that is minimization, not HIPAA de-identification.
- The free demo at pogosh.com/try is not covered by a BAA. Use this API for anything containing PHI.
Full details, retention table and FAQ: pogosh.com/security.
3. Endpoints
3.0 Tell us where the request comes from (recommended on every call)
Routing and disposition are adjusted to the requester and the patient when you send context:
| Field | Where | Values |
|---|---|---|
context.setting |
body of /cds/evaluate, /cds/note/analyze, /cds/chart/analyze; ?setting= on /cds/condition/:id |
ed, urgent_care, primary_care, inpatient, specialty_clinic, telehealth |
context.specialty |
same places; ?specialty= |
the requesting clinician's specialty, free text (e.g. family medicine) |
| age and sex | demographics on /cds/evaluate, patient on /cds/note/analyze, chart.demographics on /cds/chart/analyze; ?age=&sex= |
years; female / male |
What changes:
- The requester is never sent back to itself. From family or internal medicine, primary-care referrals are dropped; from the ED, "go to the ED" becomes what happens next (admission level or discharge with follow-up) plus the consultants, and Emergency Medicine is not listed as a consult.
- Age-appropriate services. Adults are never routed to pediatric or newborn services (a pediatric service is replaced by its adult counterpart); children get the pediatric counterpart (Pediatric Cardiology, PICU…). If age is unknown, the response says so.
- Sex-appropriate services. No obstetrics/gynecology for a male, no andrology for a female.
- Each adjusted profile carries
context_appliedlisting every removal and replacement, anddisposition.nextwith the plain-language next step;routing[].specialists_displayholds the adjusted, expanded specialist names (the originalspecialistscodes are kept).
3.1 GET /cds/health — no auth
{ "status": "ok", "version": "4.22", "platform": "aws", "engine": "Clinical Decision Support API",
"counts": { "conditions_detailed": 413, "conditions_triage": 2630, "conditions_total_distinct": 3039, "triage_linked_to_detailed": 4 },
"endpoints": [ ... ] }
Use it for uptime monitoring. counts reports what the engine serves: detailed profiles (§3.2) and triage-layer records (§3.10). conditions_total_distinct does not double-count a triage record that is the same condition as a detailed profile.
3.2 GET /cds/conditions — the catalog
Returns every condition the engine knows, for autocomplete, mapping and your own validation.
{ "version": "4.22", "count": 413,
"conditions": [ { "id": "community_acquired_pneumonia", "name": "Community-acquired pneumonia",
"icd10": "J18.9", "disposition": "ed", "urgency": "urgent", "category": "Pulmonary" }, ... ] }
Cache it; it changes only when the corpus is updated.
3.3 GET /cds/lookup?q=<term> — resolve a word to conditions
Substring match over the alias table (≥ 2 characters, 20 aliases max). Use it to map a free-text complaint or a problem-list entry to condition_ids before calling /cds/evaluate or /cds/condition/:id.
{ "version": "4.22", "query": "uti",
"groups": { "uti": { "conditions": ["urinary_tract_infection"], "aliases": ["uti", "urinary tract infection", "bladder infection"] } } }
3.4 POST /cds/evaluate — symptoms → matched conditions with full profiles
Request
{ "symptoms": ["dysuria", "flank pain", "fever"],
"existing_labs": ["Urinalysis"],
"location": "flank", "demographics": {"age": 34, "sex": "F"}, "vitals": {"temp_c": 38.6}, "context": "urgent care" }
symptoms (required): exact alias or condition-name strings — run them through /cds/lookup first; the match is exact, not fuzzy. existing_labs: names already resulted; they are removed from the lab recommendations. location, demographics, vitals, context: echoed back only in version 4.22; they do not change the match (they will, in a later version — send them now so your integration is ready).
Response (one entry per matched condition, up to the engine's result cap)
{ "version": "4.22", "match_count": 2,
"matches": [ {
"condition": "Pyelonephritis", "condition_id": "pyelonephritis",
"icd10": { "system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "N10", "display": "Acute pyelonephritis" },
"disposition": { "setting": "ed", "urgency": "urgent" },
"labs": [ { "name": "Urine culture", "loinc": "630-4", "loinc_display": "Bacteria identified in Urine by Culture",
"priority": "high_yield", "rationale": "Guides antibiotic selection; obtain before antibiotics" } ],
"imaging": [ { "name": "CT abdomen/pelvis without contrast", "radlex": { "system": "http://radlex.org", "code": "RID10321", "display": "..." },
"status": "current", "priority": "ancillary", "rationale": "If obstruction, abscess or failure to improve at 48–72 h" } ],
"routing": [ { "specialists": ["Urology"], "finding": "Obstructing stone with infection", "urgency": "emergent" } ],
"escalation": [ { "to": "ed", "if": "Sepsis criteria, inability to tolerate oral intake, obstruction" } ]
} ],
"disambiguation": { "...": "rule returned only when several conditions share a group" },
"query": { "symptoms": [...], "location": "...", "demographics": {...}, "context": "..." },
"_meta": { "response_ms": 41 } }
Triage-layer matches. A term with no exact match in the detailed profiles is also looked up in the triage layer (§3.10). Matching records come back in triage_matches (at most 5, same context rules, each labelled "assurance": "model_enriched"), with triage_match_count and a triage_note. A term that matched a detailed profile only loosely (a phrase or word match) adds triage records only when a triage title matches the term exactly. A term with an exact detailed match adds none. triage_matches is an empty array when nothing applies.
priority is high_yield or ancillary. disposition.setting is one of Outpatient, UrgentCare_or_ED, ED; disposition.urgency is routine, urgent or emergent. imaging.status is empty, non_radiology or generic_requires_context. routing[].urgency is currently empty in the corpus. At most 5 matches are returned.
3.5 GET /cds/condition/:id — one condition's full profile
Same content as a single /cds/evaluate match (labs, imaging, routing, escalation, ICD-10, disposition) with flat codes: labs[].loinc and imaging[].radlex are plain strings here, and icd10 is {code, display}. Use when you already know the diagnosis, e.g. from the problem list. Demo tier: 15 unique conditions per hour, rationale omitted.
3.6 GET /cds/procedures?category=&q= — procedure reference
category is imaging, lab or procedure; q is a name substring; 50 rows max.
{ "version": "4.22", "count": 1, "procedures": [ { "key": "percutaneous_nephrostomy", "name": "Percutaneous nephrostomy", "category": "procedure",
"specialties": ["Interventional Radiology"] } ] }
(sir_risk is reserved and currently empty.)
3.7 POST /cds/emergency — emergency safety check on a note (premium)
Sends the note text to the safety model with the 100-entry emergency backstop. Deliberately strict: it flags only when the defining acute findings are present, applies negation, and never flags on risk factors alone. Returns null when unsure.
{ "note": "34M motorcycle crash, right knee dislocated and reduced on scene; foot now cool and pale, DP pulse faint, ABI 0.7." }
{ "version": "4.22", "emergency": {
"id": "knee_dislocation_vascular", "name": "Knee dislocation with popliteal artery injury", "tier": "E0",
"imaging": "Pulse exam and Doppler pressures (ankle-brachial index) on every knee dislocation ...",
"routing": "Emergency Medicine + Orthopedic Surgery + Vascular Surgery",
"action": "Reduce promptly. Check pulses and measure Doppler pressures (ABI) before and after reduction ...",
"confidence": 0.95 } }
tier: E0 = immediate threat to life, limb or sight; E1 = same-day emergency evaluation. Treat any non-null result as a banner the clinician must see. The note is used for the check and discarded; it is not stored.
3.8 POST /cds/analyze — patient-specific AI analysis against a profile (premium)
{ "condition_id": "pyelonephritis",
"analysis_type": "labs", // full | labs | imaging | interpretation | routing
"patient_data": { "vitals": {...}, "history": "...", "medications": [...], "allergies": [...], "existing_labs": [...], "existing_imaging": [...] },
"ordered_labs": ["Urinalysis", "CBC"], // for labs: what was ordered — the engine names missed high-yield labs
"ordered_imaging": [], // for imaging
"lab_results": [ { "name": "Creatinine", "value": 2.1, "unit": "mg/dL", "reference": "0.6-1.2", "abnormal": true } ],
"imaging_results": [ { "name": "CT A/P", "finding": "...", "status": "final" } ] }
{ "version": "4.22", "condition_id": "pyelonephritis", "analysis_type": "labs",
"analysis": "Markdown text: what was missed, why it matters for this patient, how results change the algorithm ...",
"model_used": "gpt-4o", "tokens": { "prompt_tokens": 1830, "completion_tokens": 410 }, "response_ms": 4100 }
The model is confined to the condition profile ("never recommend anything not in the profile; never fabricate values"). Direct identifiers in patient_data (name, DOB, MRN, SSN, address, phone, email) are stripped before the model call; send clinical values only.
3.9 POST /cds/feedback — tell us when we are wrong (or right)
Current shape (condition-level, free text):
{ "condition_id": "pyelonephritis", "condition": "Pyelonephritis", "type": "missing_lab", "text": "Should include lactate when hypotensive", "source": "blaylar" }
type is a short tag you choose (incorrect, missing_lab, missing_imaging, routing, other). Per-response thumbs up/down and item-level accept/dismiss signals are live: see §8.
3.10 Triage layer: GET /cds/triage?q=<term> and GET /cds/triage/:id (every tier)
Beyond the 413 detailed profiles, the engine serves 2,630 triage-layer conditions: rare, pediatric and specialty conditions with an urgency tier, a disposition, age- and sex-appropriate routing and, for conditions seen in the ED, what happens after the ED. Each record is labelled "assurance": "model_enriched": drafted and independently checked by language models, then approved by the Pogosh physician owner for triage use. A triage record is not a detailed profile. It has no LOINC-coded labs or RadLex-coded imaging, and it needs clinician review like everything else here. When a triage title is clearly the same condition as a detailed profile (exact name or alias), detailed_condition points to /cds/condition/:id; use that profile for workup.
GET /cds/triage?q= searches titles and aliases by whole words: an exact title first, then the term as whole words inside a title (or a title inside the term), then all words in any order. q must be at least 3 characters; limit is 1–25 (default 10). Each result has matched_by: { method: "exact" | "phrase" | "words", term, via } and the full record. GET /cds/triage/:id returns one record. Both accept ?setting=&specialty=&age=&sex= (§3.0).
{ "version": "4.22", "triage_id": "diaphragmatic_hernias", "title": "Diaphragmatic Hernias",
"one_line": "A diaphragmatic defect permits abdominal organs to enter the chest, causing pulmonary compression and respiratory compromise.",
"assurance": "model_enriched", "assurance_note": "Triage-layer record: model-drafted and model-checked, approved by the Pogosh owner for triage use. ...",
"urgency": { "new_or_changed": "E0", "known_stable": "routine",
"caveat": "First presentation or a change in symptoms: E0. Known, stable and unchanged: routine.",
"escalate_if": "hypoxemia, severe respiratory distress, shock, or suspected strangulation of herniated viscera" },
"disposition": { "setting": "ED", "urgency": "E0", "at_recommended_setting": true, "next_source": "ed_course",
"next": "After the ED: OR / procedure — Adult service: General Surgery, Thoracic Surgery, Pulmonology. Escalate to ..." },
"population": "all_ages", "sex_specific": "none",
"routing": [ { "applies_to": "adult", "specialists": ["General Surgery", "Thoracic Surgery", "Pulmonology"] } ],
"ed_course": { "likely": "OR / procedure", "service": "...", "escalate_to_higher_level_if": "...", "step_down_if": "..." },
"owner_approval": { "approved": true, "layer_approved": "2026-10-11", "e0_approved": "2026-10-11" },
"review": { "model_checked": true, "checker_agreed": true, "routing_checker_agreed": true },
"detailed_condition": null,
"context_applied": { "setting": "ed", "specialty": null, "age": 52, "sex": "male", "removed": [], "replaced": [ "..." ], "notes": [] } }
| Field | Meaning |
|---|---|
urgency.new_or_changed |
Tier for a first presentation or a change: E0 (immediate threat to life, limb or sight; minutes to hours), E1 (ED evaluation the same day), urgent (24–72 h), routine (scheduled outpatient care). |
urgency.known_stable, caveat |
The lower tier for a known, stable, unchanged condition, where one applies (null for conditions that are always acute). Do not show a single tier for a chronic condition without the caveat. |
urgency.escalate_if |
Findings that make the presentation more urgent than its tier. |
disposition.setting |
ED, urgent_care, primary_care or specialist_outpatient. With setting context, disposition.next is the plain-language next step relative to the requester. |
routing[] |
Groups of {applies_to, specialists}, where applies_to is adult or child. Age unknown: both groups. Adults never get a pediatric or newborn service. A pediatric_only condition routes to pediatric services whatever the age, with a confirm-age note. Sex filtering and requester removal work as in §3.0. |
population, sex_specific |
all_ages, pediatric_only or adult_only; none, female_only or male_only. |
ed_course |
For conditions handled through the ED: the likely level after the ED (ICU, admit floor, observation, OR / procedure, discharge with follow-up), the admitting service, and when to escalate or step down. For an ED request (setting=ed) it becomes disposition.next. For an adult, pediatric services are removed from the admitting-service text. |
owner_approval |
approved is true for every record (the field holds the approval date); e0_approved is set where the owner approved an E0 tier individually. |
review |
Whether the independent model checks agreed with the draft and with the routing. |
The triage layer also feeds /cds/evaluate (triage_matches, §3.4) and the copilot when no detailed profile matches (§4.1, §4.3).
4. Copilot endpoints (premium)
These are the pogosh.com/try note tools and the OpenEMR chart copilot, exposed on the API. Each response carries a response_id, and every lab, imaging and routing line carries an item_id (for the ratings and usage signals in §8). All copilot endpoints are POST, JSON in and out, and use the model hop covered by the OpenAI BAA.
4.1 POST /cds/note/analyze — a note in, the whole picture out
{ "note": "58F with CKD 3b and remote TB (treated 2009) presents with 3 days of dysuria, left flank pain, fever 38.9 and rigors. On apixaban for AF. HR 112, BP 104/62. Left CVA tenderness. UA: pyuria, nitrite positive." }
{ "version": "4.22", "response_id": "0c1e…", "matched": true,
"chief_complaint": "Dysuria and left flank pain with fever",
"emergency": null,
"history_flags": [],
"primary": {
"condition": "Pyelonephritis", "condition_id": "pyelonephritis", "icd10": { "code": "N10", "display": "Acute pyelonephritis" },
"disposition": { "setting": "UrgentCare_or_ED", "urgency": "urgent" },
"confidence": 0.92, "reason": "Flank pain, fever, CVA tenderness and positive UA.",
"status": "presenting", "flag": null,
"labs": [ { "item_id": "lab:630-4", "name": "Urine culture", "loinc": "630-4", "priority": "high_yield", "rationale": "..." } ],
"imaging": [ { "item_id": "img:RID10321", "name": "CT abdomen/pelvis", "radlex": "RID10321", "priority": "ancillary", "rationale": "...",
"acr_guidance": "Consider", "dose": { "mSv": 8 } } ],
"routing": [ { "item_id": "route:urology", "specialists": ["Urology"], "finding": "Obstruction or abscess" } ],
"escalation": [ { "to": "ED", "if": "Sepsis criteria, obstruction, failure to tolerate oral intake" } ] },
"others": [ { "condition_id": "atrial_fibrillation", "status": "active_comorbidity", "...": "same shape" } ],
"_meta": { "response_ms": 6100, "source": "llm" } }
What it does, in order: maps the note to 1–3 corpus conditions (strict; never invents ids) and extracts the chief complaint; runs the 100-entry emergency check (same strict rules as /cds/emergency); classifies each matched condition as presenting, active_comorbidity or history from the note itself; attaches the full profile; adds ACR appropriateness guidance (Preferred / Consider / Avoid) and a typical radiation dose to the primary condition's imaging. An emergency pattern that belongs to the history, not the current presentation, is returned in history_flags instead of emergency, so it stays visible without triaging the visit. Conditions with status: "history" carry flag.flagged: true.
If nothing matches, matched is false and message says why; emergency is still evaluated. When no detailed profile matches, up to 3 triage-layer records (§3.10, model_enriched) found from the chief complaint and the note's Assessment, Impression or Diagnosis lines come back in triage_matches (an empty array otherwise). Only the matched ids are logged, never the terms.
4.2 POST /cds/note/write — draft the note
{ "note": "<the same source note>", "analysis": { "...the /cds/note/analyze response..." }, "note_type": "encounter" }
note_type: encounter (H&P with assessment and plan), followup (interval history, assessment, plan), disposition (discharge summary sections). Returns { "response_id", "note_type", "note": "<plain text>", "model_used", "tokens" }. Rules the writer is held to: facts only from the source note; no invented negatives ("denies…", "no history of…") unless the note states them; the plan comes from the structured recommendations in analysis. The last line is always "Pogosh CDS decision-support draft. Verify before signing."
4.3 POST /cds/chart/analyze — the chart-read copilot
Send the chart (the EHR already has it) and the clinician's brief input. The engine extracts the clinically significant facts, builds the HPI, applies the anatomy and active-problem rules, generates alert flags, a chart-weighted differential, workup and plan, then maps the result to Pogosh profiles and runs the emergency check.
{ "clinician_input": "c/o dysuria and L flank pain x3d, fever, rigors. +CVA tenderness. On apixaban.",
"note_type": "h-and-p",
"chart": {
"demographics": { "age": 58, "gender": "female" },
"problems": [ { "name": "Chronic kidney disease stage 3b", "icd10": "N18.32", "status": "active", "onset": "2022-03-01" } ],
"medications": [ { "name": "Apixaban 5 mg twice daily" } ],
"allergies": [ "Sulfa (rash)" ],
"procedures": [ { "name": "Colonoscopy", "date": "2025-04-02" } ],
"labs": { "Creatinine": [ { "value": 1.7, "unit": "mg/dL", "date": "2026-09-02", "interpretation": "high" } ] },
"imaging_reports": [ { "modality": "US", "name": "Renal ultrasound", "date": "2026-03-15", "impression": "No hydronephrosis." } ],
"notes": [ { "date": "2026-09-02", "title": "Nephrology clinic", "content": "CKD 3b stable..." } ],
"extracted_facts": null } }
Chart limits: 120 KB of JSON; send the last 1–2 years, newest first; up to 20 notes are read (first 500 characters each). note_type: h-and-p, progress-note, consult, clinic-note, procedure-note. demographics needs only age and gender; do not send names.
{ "response_id": "…", "encounterType": "acute",
"hpi": "58-year-old woman with CKD 3b on apixaban presents with three days of dysuria and left flank pain ...",
"pertinentHistory": [ "CKD stage 3b (N18.32), creatinine 1.7 on Sep 2, 2026 — contrast and nephrotoxic agents need caution", "..." ],
"alertFlags": [ "Apixaban for atrial fibrillation in a patient with CKD 3b and sepsis physiology — bleeding and accumulation risk; ..." ],
"chartWeightedDifferential": [ { "diagnosis": "Acute pyelonephritis", "likelihood": "high", "chartReasoning": "CVA tenderness with positive UA; renal US Mar 2026 showed no hydronephrosis" } ],
"workupRecommendations": { "labs": ["CBC with differential", "BMP — creatinine vs baseline 1.7", "Urine culture", "Blood cultures x2"], "imaging": ["Renal ultrasound to assess for obstruction"], "other": [] },
"plan": "1. Acute pyelonephritis\n • ...",
"emergency": null,
"pogosh_conditions": [ { "condition_id": "pyelonephritis", "...": "full profile with item_ids" } ],
"extracted_facts": { "activeProblemsSummary": "...", "medicationFlags": ["apixaban"], "labTrends": [], "...": "..." } }
Cache extracted_facts per patient and send it back in chart.extracted_facts on later calls for the same chart; that skips the extraction step and keeps every call well inside the 30 s limit. Re-extract when the chart changes.
When none of the differential maps to a detailed profile, triage_matches carries up to 3 triage-layer records (§3.10, model_enriched) found from the differential diagnoses and the clinician input; it is an empty array otherwise.
4.4 POST /cds/chart/followup — progress note
{ "chart": {…}, "prior_note": { "date", "title", "content" }, "interval_update": "optional clinician text" } → { "intervalSummary", "assessment", "plan", "alertFlags", "workupRecommendations" }. Interval data = chart notes newer than prior_note.date plus labs; every cited lab value carries its date, and vitals-dependent medication continuations carry a "verify at today's visit" note.
4.5 POST /cds/chart/disposition — discharge, signoff or transfer
{ "chart": {…}, "note_type": "discharge" | "signoff" | "transfer", "baseline_note": { "date", "content" }, "subsequent_notes": [ { "date", "content" } ], "disposition_plan": "optional" } → admissionDx, dischargeDx, hospitalCourse (reason for admission, problems with single-line narratives, key diagnostics anchored on the admission abnormal value, response to treatment), procedures, dischargeCondition, dischargeMeds (only medications that drove, changed in, or were started/held during the admission), followUpPlan, pendingResults, alertFlags.
4.6 What the copilot will not do
It never writes to your chart, never stores the chart or the notes, never invents negatives, and never recommends a study that is not in the Pogosh profile or the chart-driven workup rules. Everything it returns is a draft for a clinician to edit and sign.
5. Clinical calculators (every tier)
118 current clinical calculators (CURB-65, PSI, Wells, HEART, CHA2DS2-VASc, MELD 3.0, PREVENT, Glasgow Coma Scale, anion gap and more), plus 25 older versions kept for comparison. Each one is built from its primary publication, every published formula is checked with worked examples, and every reference is listed with a PubMed or DOI link where one exists (otherwise a PubMed search link).
Offered, never run automatically. Pogosh suggests calculators that fit the case; it never computes one on its own. The clinician chooses to open a calculator, enters the values, and presses Calculate. Show suggested_calculators as an offer, not a result.
These endpoints use no language model and store nothing: calculator inputs and results are never logged or stored. They are available on every tier, including demo, and count against your hourly rate limit like the corpus endpoints (not against the monthly AI budget).
5.1 GET /cds/calculators — the catalog
Current versions only; add ?include_superseded=1 for the older versions too.
{ "count": 118, "include_superseded": false,
"calculators": [ { "id": "curb65", "name": "CURB-65 pneumonia severity", "category": "Pulmonary", "status": "current",
"superseded_by": null, "description": "Pulmonary · based on Lim WS et al. (2003) · 5 inputs",
"dosing_utility": false, "related": { "conditions": ["pneumonia"], "emergencies": ["septic_shock"] } }, ... ] }
5.2 GET /cds/calculators/:id — the definition
Everything needed to draw the form: inputs[] with id, label, type (number, boolean, choice, date), unit, min/max (the plausible range; values outside it are rejected), options[] for choices, default, optional, required, and points where the item is scored. Also references[] (text, pmid, doi, url), notes (clinical caveats), status, superseded_by and current_version.
5.3 POST /cds/calculators/:id — run it
Request (booleans may be omitted when false)
{ "inputs": { "confusion": true, "rr": true, "age65": true } }
Response
{ "calculator": "curb65", "name": "CURB-65 pneumonia severity", "status": "current",
"inputs": { "confusion": true, "urea": false, "rr": true, "bp": false, "age65": true },
"outputs": [ { "id": "score", "label": "CURB-65", "value": 3, "unit": "/5" } ],
"interpretation": "High risk (about 22%) — hospital admission; assess for ICU at 4–5. Social factors, oxygen requirement and comorbidity override the score.",
"offered_not_automatic": true, "disclaimer": "Decision support for clinicians. Review before acting." }
Errors: an unknown id → 404. Missing or implausible values → 400 listing every problem at once, so your form can mark each field:
{ "error": "Invalid inputs", "calculator": "psi",
"fields": [ { "field": "age", "message": "Age must be a number" }, { "field": "sex", "message": "Sex is required" } ] }
A value is never clamped or guessed: out of range is an error. A superseded calculator still runs (for comparison), and its response adds superseded_by, current_version {id, name} and a notice naming the current version — show it.
5.4 Where suggestions appear
suggested_calculators: [{ "id", "name", "why" }] (current versions only, at most 5, dosing utilities never) is added to /cds/condition/:id, to each match of /cds/evaluate, to /cds/note/analyze (from the presenting and comorbid conditions and the emergency, not from history) and to /cds/chart/analyze. Suggestions are built from the matched condition and emergency ids only; your note text is never used for them.
"suggested_calculators": [
{ "id": "curb65", "name": "CURB-65 pneumonia severity", "why": "matched condition: pneumonia" },
{ "id": "psi", "name": "Pneumonia Severity Index (PSI / PORT)", "why": "matched condition: pneumonia" } ]
The same calculators are public at https://pogosh.com/calculators, one page per calculator with its references.
6. Clinical semantics you must respect
| Rule | Why |
|---|---|
| Recommendations are advisory. The treating clinician decides. Show them as suggestions, never auto-order. | Clinical responsibility stays with the licensed clinician. |
high_yield vs ancillary is the engine's priority, not an order of operations. |
Ancillary studies are conditional; the rationale says when. |
/cds/emergency is a safety net, not a triage system. A null means "no defining emergency found in this text", not "safe". |
It is tuned for specificity. |
| Urgency describes a new or changed presentation. A known, stable, unchanged chronic diagnosis is lower (see §7). | Avoids over-triaging the history. |
Never send direct identifiers to /cds/analyze or /cds/emergency beyond what the clinical question needs. |
Minimum necessary. |
| Pogosh is not FDA-cleared software. Display the source as "Pogosh CDS — advisory". | Labeling. |
7. Active vs. history — what drives what
Three different features need three different definitions of "active":
| Feature | What "active" means | Where it comes from |
|---|---|---|
| Routing, triage, emergency check | Only what the patient is here for now. A grand-mal seizure or a trauma in the history must not triage today's toe cellulitis to E0. | The most recent note / chief complaint, as read by the model, not the problem-list status. |
| Labs and imaging recommendations | Today's problem, adjusted for comorbidities that change the workup (CKD and contrast, anticoagulation and bleeding, pregnancy). | Most recent note + active problem list and medications. |
| Note writing | The whole chart: history, meds, allergies, prior results, plus today's visit. | Chart read (1–2 year lookback) + today's note. |
FHIR Condition.clinicalStatus is a useful hint but is often wrong in practice, so the engine treats it as one input and lets the model judge from the most recent note. Historical E0/E1 conditions are returned flagged (visible, default routine), and the clinician can promote a flag to active (design in PILOT-DESIGN.md; /cds/note/analyze already returns status and history_flags, §4.1).
8. Feedback and pilot measurement
Every response from /cds/evaluate, /cds/condition/:id, /cds/analyze, /cds/emergency and the copilot endpoints carries a response_id; every lab, imaging and routing line carries an item_id. Two small calls let your UI report what clinicians think and do, with no extra screens and no patient data:
POST /cds/rate — thumbs and comments
{ "response_id": "<from any response>", "item_id": "lab:630-4", "rating": "up" } // item-level thumb, sent on click
{ "response_id": "<id>", "rating": "down" } // whole-response thumb; a later "up" replaces it
{ "response_id": "<id>", "item_id": "img:RID10321", "comment": "Would not CT a pregnant patient first." } // sent on Send / Enter
rating is "up", "down" or null (clears). A rating and a comment can be sent together or separately; the last write for a (response_id, item_id) wins, so a flipped thumb simply overwrites. Comments are capped at 2,000 characters and identifier-scrubbed server-side; tell clinicians not to type patient identifiers.
POST /cds/usage — what the clinician did with a recommendation
{ "response_id": "<id>", "item_id": "lab:630-4", "action": "ordered" } // accepted | dismissed | ordered | referred
Send it when the clinician acts (adds the order, creates the referral, dismisses the line). Not a question to the clinician.
Both endpoints return { "ok": true, ... }, 404 for a response_id that does not belong to your key, and 400 for an invalid value. They work on every tier.
What we measure from this (visible to Pogosh only)
Per key, per month, kept for the whole pilot: calls per endpoint; conditions requested; emergency checks fired; for every recommendation, how often it was shown, accepted, ordered, referred, dismissed, thumbed up or down; every comment with its context. The owner dashboard sorts recommendations by "shown but never used" so dead weight is obvious, and exports everything as CSV.
What you can see yourself
GET /cds/me returns your key's tier, limits, expiry, and this month's and last month's usage (calls per endpoint, model-backed calls against your monthly budget, conditions requested, emergency checks fired, ratings and actions you sent). The partner portal at https://api.pogosh.com/portal shows the same information and the program documents (this guide, the data-handling and pilot-scope schedules, and the agreements), each printable to PDF from the browser. Your key is entered on the page and kept only in that browser tab.
Monthly AI budget
Each key has a monthly budget of model-backed calls (/cds/analyze, /cds/emergency, /cds/note/*, /cds/chart/*); the default is 5,000. Over budget → 429 {"error":"Monthly AI-call budget reached for this key.", "limit": N, "used": N} until the first of the next month (UTC). Corpus endpoints are not counted. Keys can also carry an expiry date.
9. Recommended integration (server-side)
Clinician opens an encounter
│
├─ 0. (simplest path) POST /cds/chart/analyze { chart, clinician_input } → HPI, alerts, differential, workup, plan, profiles, emergency
│ or POST /cds/note/analyze { note } when a note already exists; then POST /cds/note/write to draft it.
│ The numbered steps below are the building blocks if you prefer to compose them yourself.
│
├─ 1. POST /cds/emergency { note: <today's HPI/assessment text> } → banner if non-null (E0 red, E1 amber)
│
├─ 2. GET /cds/lookup?q=<chief complaint words> → condition_ids
│ (or map the encounter diagnosis ICD-10 → condition via /cds/conditions)
│
├─ 3. POST /cds/evaluate { symptoms: [...ids/aliases], existing_labs: [resulted lab names] }
│ → side panel: labs (with LOINC → your order catalog), imaging (RadLex), routing, escalation
│
├─ 4. (optional) POST /cds/analyze { condition_id, analysis_type: "labs", ordered_labs: [...] }
│ → "you ordered X; high-yield Y was not ordered because ..."
│
└─ 5. POST /cds/rate (thumbs, comments) and POST /cds/usage (accepted / ordered / dismissed) as the clinician acts
Map LOINC codes to your lab compendium and RadLex to your imaging catalog; both are returned as FHIR-style codings so a ServiceRequest can be built directly.
10. Support
Email support@pogosh.com. With any report, include the response_id from the response (or the timestamp if there is none). Service status: GET https://api.pogosh.com/cds/health. Your own usage and pilot documents: https://api.pogosh.com/portal.