Run SottoMD on your own server
This is the self-host path: everything that hears a visit runs on a box your practice controls. The audio, the transcript, the note and the EHR drafts never leave your network. Until a BAA exists for our hosted options, this is the only way to use SottoMD with real patients.
You set up two things:
- The engine: decosa-api's clinical stack, meaning speech recognition, speaker labels and the language model, on a GPU box.
- The app: this repository, plus HTTPS in front of it and, optionally, the EHR bridge.
The app is light, so it can share the GPU box or run on a small machine next to it.
What you need
- A Linux box with an NVIDIA GPU for the engine: 96 GB (RTX PRO 6000 Blackwell) is measured, and 80 GB (H100/H200) and 48 GB (L40S) are starting points. You also need about 80 GB of disk, Docker, and the NVIDIA container toolkit.
- A name on your network for the app, such as
sotto.localor a hostname your IT already uses. Phones need HTTPS to use their microphone, so the app is served over TLS. - Nothing else: no cloud account and no outside service.
1. The engine
Follow decosa-api's own guide, Assemble the Decosa visit copilot on this machine. It's the clinical tool's self-host prompt, and the Decosa team gives you the source.
- Keep the engine on
DECOSA_LLM_ROUTE=direct. Your model calls are then receipted by your own box's key, and nothing goes to a gateway. - Keep its API on the docker network or on
127.0.0.1. The app reaches it over the shared docker networkdecosa_default.
Then make an API key for the app. It never leaves your box:
ADMIN=$(openssl rand -base64 24) # set DECOSA_ADMIN_SECRET=$ADMIN in the engine's .env and restart its api
curl -s -X POST http://127.0.0.1:8445/v1/keys -H "Authorization: Bearer $ADMIN" -H 'Content-Type: application/json' \
-d '{"label":"sotto app","verticals":["clinical","ehr-drafts"],"daily_llm_tokens":5000000,"daily_audio_seconds":28800}' > app-key.json
2. The app
git clone <the SottoMD repository> sotto && cd sotto/deploy/selfhost
./gen-secrets.sh # writes VISIT_APP_KEK into .env and a bridge token into secrets/ (prints no secrets)
$EDITOR .env # APP_ORIGIN and SITE_HOST: the name clinicians will open
docker compose up -d --build # the app and Caddy (HTTPS)
- Back up
.env.VISIT_APP_KEKunlocks every practice data key, so without it the stored visit content can't be read. Keep it off the box, the way you keep other keys. - Data volume. The app keeps its database in the
app-datavolume: practices, users, schedule, visit content (encrypted per practice), the audit log and the token vault.
3. First sign-in
The first owner is created from the command line. There's no sign-up page:
docker compose exec app node_modules/.bin/tsx scripts/invite.ts --email [email protected] --name "Dr. You" --practice "Your Practice" --state CA
- Open
https://sotto.local, enter your email, and read the six-digit code:- With
MAIL_TRANSPORT=outbox(the default), the code is indocker compose exec app ls /data/outbox. - Or set
MAIL_TRANSPORT=smtpandSMTP_URLto an SMTP server your practice already has.
- With
- In Settings → Where it runs, choose Your server.
- Set the engine address to
http://api:8445(the engine's service on the shared network). - Paste the key from
app-key.json, press Check now, and expect "Connected".
- Set the engine address to
- The lock badge in the top bar now reads Your server.
4. HTTPS on your devices
- Caddy's
tls internalmakes a local certificate authority. Install its root certificate once on each tablet, laptop and phone:- Export it with
docker compose exec caddy cat /data/caddy/pki/authorities/local/root.crt > sotto-root.crt. - Then trust it in each device's settings.
- Export it with
- If your IT already issues certificates, replace
tls internalinCaddyfilewith yours.
5. Optional: EHR drafts into OpenEMR
The bridge runs decosa-api's ehr-drafts engine next to your OpenEMR. It opens the chart, checks the patient's name and date of birth, types each item as a draft, and stops before sign.
docker compose --profile openemr up -d
- In Settings → EHR, choose OpenEMR, set the address to
http://bridge:8562, and paste the token fromsecrets/bridge_token. - Give the bridge its own OpenEMR user (an "agent user"), so OpenEMR's audit log names it.
- The bridge never signs, transmits, faxes, e-prescribes or deletes. Those requests are held in its browser's network layer, whatever a button says.
6. Check it end to end
Do this with a made-up visit first:
- Today: add a visit for a made-up patient, or use a demo practice.
- Consent: the patient's consent is on file, or you tap They agreed.
- Record: Use this device's microphone, or Use my phone as the mic and scan the QR code. Act out a short visit.
- Review: end the visit, and the note, codes and forms are ready in about a minute. Every sentence links to what was said.
- Receipts: under Audit & receipts, Verify 5 at random. Each receipt is signed by your engine's own key.
What leaves the box
- The app: nothing. It makes no outside calls, and has no telemetry (there's nothing to opt into yet).
- The engine: its clinical code lookups (NLM ICD-10-CM tables, RxNav, openFDA drug labels) are public web lookups that carry drug and diagnosis words from the visit.
- decosa-api has a switch for them (added for the enclave build, page 56 §0.3e). Check that your engine version has it, and turn it off if your policy requires it [U: confirm on your version].
- With them off, codes are still suggested but not checked against the NLM table.