All articles
Engineering

How to Build an AI Note Taker in 5 Minutes (with ClinikAPI)

A deep, hands-on tutorial: build an AI medical scribe that turns a visit into a structured SOAP note and stores it as FHIR — using an LLM for the draft and ClinikAPI for the data. Real code included.

ClinikAPI TeamApril 19, 202610 min read
How to Build an AI Note Taker in 5 Minutes (with ClinikAPI)

Documentation is the single biggest time sink in clinical work, and an AI note taker (an AI medical scribe) is the highest-leverage feature you can build to fix it. The good news: it's only three moving parts — pull clean context, draft with an LLM, store as FHIR — and with ClinikAPI handling the data, you can stand up a working version in about five minutes. This is a deep, hands-on build with real code: the proxy route, the AI draft endpoint, and the review-and-save flow.

The platform we build on is ClinikAPI — a FHIR-native API that gives your AI clean context and stores the result as a real record. Why we recommend it up front:

  • Free to start: Get your API keys in seconds — no credit card needed.
  • Clean context: Pull exactly the FHIR records your model needs.
  • Real storage: clinik.notes.create stores the signed note as FHIR.
  • A NoteEditor component: Drop-in review UI for the clinician.
  • Compliant: HIPAA-compliant, SOC 2-audited, with a signed BAA.

Quick Answer

To build an AI note taker, pull the patient's FHIR context, draft a SOAP note with an LLM, and store the signed note as FHIR. With ClinikAPI you fetch context with clinik.patients.read(id, { include }), send it plus the visit transcript to an LLM that signs a BAA, and ask for a structured SOAP note. The clinician reviews and signs the draft in the NoteEditor component, and you save it with clinik.notes.create({ patientId, title, content, type: 'progress-note' }), which returns a FHIR DocumentReference connected to the patient and encounter. The model does the writing, the clinician owns the accuracy, and ClinikAPI keeps the note searchable and part of the chart. Build on HIPAA-compliant infrastructure and keep a human in the loop.

Get your free API keys

ClinikAPI gives your AI scribe clean FHIR context and a place to store signed notes — HIPAA-compliant, with a free sandbox. Start in seconds.
Start Building Free

The architecture (three steps)

An AI note taker is a short pipeline:

Visit transcript  +  FHIR context (ClinikAPI)
        ↓
   LLM draft (SOAP note)        ← a model provider that signs a BAA
        ↓
Clinician reviews & signs (NoteEditor)
        ↓
   Store as FHIR (clinik.notes.create → DocumentReference)

The LLM writes; the clinician approves; ClinikAPI stores. Let's build each step.

Step 1: Install and set up the SDK

npm install @clinikapi/sdk @clinikapi/react
// lib/clinik.ts — server-only
import { Clinik } from '@clinikapi/sdk'

export const clinik = new Clinik(process.env.CLINIKAPI_SECRET_KEY!, {
  baseUrl: 'https://api.clinikapi.com', // default
  timeout: 30_000,
  retries: 2,
})

Your key looks like clk_live_… (or clk_test_… in the sandbox) and must stay server-side.

Step 2: Build the AI draft endpoint

This is the heart of the note taker. On your server, you (a) pull the patient's structured FHIR context, (b) send it plus the transcript to an LLM, and (c) return a draft. Here's a complete endpoint:

// app/api/draft-note/route.ts — server-only
import Anthropic from '@anthropic-ai/sdk'
import { clinik } from '@/lib/clinik'

const anthropic = new Anthropic() // a provider that signs a BAA for PHI

export async function POST(req: Request) {
  const { patientId, transcript } = await req.json()
  // Authenticate + authorize the clinician for this patient first.

  // (a) Pull clean, structured FHIR context — minimum necessary
  const { data: patient } = await clinik.patients.read(patientId, {
    include: ['conditions', 'medications', 'observations'],
  })

  const context = {
    conditions: patient.conditions?.map((c) => c.code?.text),
    medications: patient.medications?.map((m) => m.code?.text),
    recentResults: patient.observations?.slice(0, 10),
  }

  // (b) Ask the model for a structured SOAP note
  const message = await anthropic.messages.create({
    model: 'claude-opus-4-8',
    max_tokens: 1500,
    system:
      'You are a clinical scribe. Write a concise SOAP note (Subjective, ' +
      'Objective, Assessment, Plan) from the visit. Use only the provided ' +
      'context and transcript. Do not invent findings.',
    messages: [
      { role: 'user', content: `CONTEXT:\n${JSON.stringify(context)}\n\nTRANSCRIPT:\n${transcript}` },
    ],
  })

  const draft = message.content[0].type === 'text' ? message.content[0].text : ''
  return Response.json({ draft }) // never log the PHI
}
Caution

Three non-negotiables when sending visit data to an LLM: use a provider that signs a BAA, send the minimum necessary data, and never log the patient content. The draft is not a record until a clinician signs it. (See feeding FHIR data to LLMs safely.)

Notice why we pull FHIR context first: handing the model the patient's real conditions and medications — as clean structured data — produces a far more accurate note than the transcript alone.

Step 3: Let the clinician review and sign

The draft is exactly that — a draft. Drop the NoteEditor component in so the clinician reviews and edits before signing. It talks to your backend through the proxy pattern:

// components/ScribeReview.tsx
'use client'
import { NoteEditor } from '@clinikapi/react'

export function ScribeReview({ patientId }: { patientId: string }) {
  return (
    <NoteEditor
      proxyUrl="/api/clinik"
      patientId={patientId}
      theme="light"
    />
  )
}

(Just exploring? Set proxyUrl="demo" to run it with mock data and no backend.)

Step 4: Store the signed note as FHIR

When the clinician signs, save it with clinik.notes.create. Add the notes.create action to your proxy route:

// app/api/clinik/route.ts — server-only
import { clinik } from '@/lib/clinik'

export async function POST(req: Request) {
  const { action, data } = await req.json()
  // Authenticate + authorize for data.patientId first.
  switch (action) {
    case 'notes.create':
      return Response.json(await clinik.notes.create(data))
    default:
      return Response.json({ error: 'Unknown action' }, { status: 400 })
  }
}

The saved note is a FHIR DocumentReference linked to the patient and visit. The required fields are patientId, title, and content; type accepts values like progress-note or consultation-note:

const { data: note, meta } = await clinik.notes.create({
  patientId: 'patient-123',
  title: 'Progress Note — 2026-07-01',
  content: signedSoapNote,            // the clinician-approved text
  type: 'progress-note',
  encounterId: 'encounter-456',       // ties the note to the visit
  authorId: 'practitioner-789',
})
// → { data: DocumentReference, meta: { requestId, status, ... } }

Now the note is searchable, versioned, and part of the chart — not a loose text blob. (See storing patient data the right way.)

Bonus: draft automatically after a visit

To make it feel magical, draft the note before the clinician sits down. Subscribe to the Encounter resource and fire your draft endpoint when a visit is created:

await clinik.events.subscribe({
  resource: 'Encounter',
  on: 'created',
  webhook: 'https://yourapp.com/api/draft-note',
})

The draft is waiting in the NoteEditor when the clinician opens the chart.

Product Insight: Why ClinikAPI Is the Backbone

An AI note taker needs two things the LLM can't provide: clean context to write from and a real place to store the result. ClinikAPI is both.

  • Clean FHIR context: clinik.patients.read(id, { include }) gives the model accurate grounding.
  • Real note storage: clinik.notes.create saves a FHIR DocumentReference, connected and searchable.
  • The NoteEditor component: drop-in review UI, no editor to build.
  • Real-time events: draft automatically after a visit.
  • Compliance: HIPAA-compliant, SOC 2-audited, with a signed BAA.

Pair it with any BAA-signing model provider and you have a production-grade scribe. See the UI Library and Clinical Documentation API.

Frequently Asked Questions

1. How do I build an AI medical scribe?

Pull the patient's FHIR context, draft a SOAP note with a BAA-signing LLM, let the clinician review and sign in NoteEditor, and store it with clinik.notes.create.

2. Where does the note get stored?

As a FHIR DocumentReference via clinik.notes.create, linked to the patient and encounter — searchable and part of the chart.

3. Is it safe to send visit data to an LLM?

Yes, with a provider that signs a BAA, minimum-necessary data, a secure server-side call, no logging of PHI, and a clinician reviewing every note.

4. Why give the LLM FHIR context?

Structured context (real conditions, meds, results) grounds the draft in the actual chart, producing a far more accurate note than the transcript alone.

5. Can I draft automatically after a visit?

Yes — subscribe to Encounter events and fire your draft endpoint when a visit is created, so the note is ready for review.

Conclusion

An AI note taker isn't a research project — it's a three-step pipeline: clean FHIR context in, an LLM draft, a clinician-signed note stored as FHIR. ClinikAPI supplies the context (patients.read), the storage (notes.create), and the review UI (NoteEditor), so the only piece you add is a single model call. Keep a human in the loop, run it on HIPAA-compliant infrastructure, and you can give clinicians their hours back — in about five minutes of build time.

Key takeaways:

  • An AI note taker is three steps: context → LLM draft → signed FHIR note.
  • Pull structured context with clinik.patients.read(id, { include }) for accuracy.
  • Use a BAA-signing LLM, minimum-necessary data, and never log PHI.
  • Store the signed note with clinik.notes.create as a FHIR DocumentReference.
  • Keep a clinician in the loop, and draft automatically with Encounter events.

Ready to build? Get your free API keys or explore the UI Library.

Related Articles

Share

Keep reading