Fullscript Integration — Clinic Portal¶
Overview¶
Practitioners connect their own Fullscript account (per-practitioner OAuth). Treatment plans created or updated in Fullscript are pulled into the patient's master protocol in Onset Clinic. Manual/non-Fullscript supplements are preserved. The unified master protocol is delivered to the patient iOS app with configurable timing.
Default SOP (pull-first): Build supplement plans in Fullscript → link or import patients on the Patients page → pull the latest active treatment plan → send master protocol to the patient app.
Two separate systems (do not conflate)¶
| System | Who uses it | Purpose |
|---|---|---|
Fullscript (us-snd.fullscript.io sandbox) |
Practitioner | Dispensary, catalog, treatment plans — native Fullscript UI |
Fullscript developer portal (fullscript.dev) |
Integrator (you) | OAuth app, webhooks, Create test account |
| Onset Clinic (staging / production) | Practitioner | Patient charts, master protocols, Patients → Connect / pull |
Practitioners log into Fullscript separately from Onset. Onset Connect links an existing Fullscript login to the Onset practitioner profile via OAuth.
Sandbox setup order¶
- Developer portal — Create OAuth app, webhooks; use Create test account to mint a sandbox practitioner login.
- Fullscript sandbox — Sign in at https://us-snd.fullscript.io with that test practitioner. Build treatment plans here.
- Onset Clinic — Sign into staging clinic portal → Patients → Connect Fullscript (or Settings → Integrations → Connect). Authorize with the same Fullscript login.
After Connect succeeds, use the Patients page to import Fullscript patients, link charts, and pull protocols. Open in Fullscript opens the native Fullscript UI in a new tab.
Clinic workflow (Patients hub)¶
| Step | Where | Action |
|---|---|---|
| 1 | Patients | Connect Fullscript (banner when not connected) |
| 2 | Fullscript patients table | Import FS-only patients, Link + pull email matches, Pull protocol for linked charts |
| 3 | Patient Directory | Fullscript column shows link status; Pull from Fullscript per row |
| 4 | Patient profile | Link chart, Pull latest protocol, or Create or link Fullscript chart (push patient identity only) |
| 5 | Patient profile | Send master protocol to patient app (works for invited patients — queues until app install) |
Bulk Sync all matched by email matches Fullscript patients to Onset charts by email and pulls each latest active plan.
Auto-sync: webhooks + hourly cron re-pull when Fullscript plans change (if auto-update enabled on the patient).
Push vs pull (phase 1)¶
| Direction | Supported | Notes |
|---|---|---|
| Pull patient + protocol (FS → Onset) | Yes | Default SOP; product names resolved via catalog variant lookup |
| Push patient identity (Onset → FS) | Yes | Creates/links Fullscript chart without pulling a plan |
| Push protocol (Onset → FS) | Phase 2 | Requires Fullscript variant IDs; deferred |
Availability flags¶
| Variable | Default | Purpose |
|---|---|---|
FULLSCRIPT_INTEGRATION_ENABLED |
true (set false on production) |
Kill-switch for all Fullscript routes |
FULLSCRIPT_CUSTOMER_AVAILABLE |
false |
OAuth, sync, and patient UI |
FULLSCRIPT_BILLING_ENABLED |
false |
Stripe Solo add-on line items at checkout |
FULLSCRIPT_EMBED_UI_ENABLED |
unset (false) |
Opt-in in-portal iframe embed (not used in default workflow) |
Production (until SOC 2 + Sub-BAA): FULLSCRIPT_INTEGRATION_ENABLED=false and FULLSCRIPT_CUSTOMER_AVAILABLE=false.
Staging sandbox POC: FULLSCRIPT_INTEGRATION_ENABLED=true, FULLSCRIPT_CUSTOMER_AVAILABLE=true, FULLSCRIPT_BILLING_ENABLED=false.
Environment variables¶
| Variable | Purpose |
|---|---|
FULLSCRIPT_CLIENT_ID |
OAuth app client id from Fullscript API Dashboard |
FULLSCRIPT_CLIENT_SECRET |
OAuth app secret |
FULLSCRIPT_API_BASE |
Sandbox: https://api-us-snd.fullscript.io |
FULLSCRIPT_OAUTH_AUTHORIZE_URL |
Default: https://us-snd.fullscript.com/oauth/authorize |
FULLSCRIPT_OAUTH_TOKEN_URL |
Default: {FULLSCRIPT_API_BASE}/api/oauth/token |
FULLSCRIPT_OAUTH_SCOPES |
Space-separated scopes (see dashboard) |
FULLSCRIPT_WEBHOOK_CHALLENGE_TOKEN |
Secret challenge token from Fullscript Webhooks tab |
FULLSCRIPT_WEBHOOK_SECRET |
Fullscript-signature HMAC secret key from Webhooks tab |
FULLSCRIPT_TOKEN_ENCRYPTION_KEY |
Base64url 32-byte AES key (falls back to PATIENT_PII_ENCRYPTION_KEY) |
FULLSCRIPT_EMBED_PUBLIC_KEY |
Optional; only if FULLSCRIPT_EMBED_UI_ENABLED=true |
FULLSCRIPT_EMBED_ENV |
Embed environment: us-snd (sandbox), us (production) |
Register OAuth redirect URI: {NEXT_PUBLIC_APP_URL}/api/integrations/fullscript/callback
Register webhook URL: {NEXT_PUBLIC_APP_URL}/api/webhooks/fullscript
Patient isolation invariant (HIPAA-critical)¶
A Fullscript treatment plan may only be merged into the Onset chart when all of the following are true:
- The chart belongs to the same practitioner as the connected Fullscript OAuth account.
- The chart's verified Onset email exactly matches the Fullscript patient email.
- The
fullscript_patient_linksrow is scoped to that practitioner (practitioner_id+fullscript_patient_idunique). - Manual pulls pass an explicit
practitionerPatientId— stale Fullscript metadata never overrides a link row.
Sync is rejected (logged to fullscript_sync_log) when email mismatch, link mismatch, or practitioner mismatch is detected. Auto-sync (webhooks + cron) uses the same guards.
Manual SOP¶
- Never link a Fullscript patient to an Onset chart unless emails match.
- Use Import for Fullscript-only patients instead of linking the wrong chart.
- If roster shows Incorrect link, use Remove link before pulling again.
- After any suspected crossover incident, run the link audit script before re-syncing.
Incident response¶
- Stop manual pulls until audit completes.
- Run
node --env-file=.env.local scripts/audit-fullscript-links.mjs(report only). - Remediate mismatches:
node --env-file=.env.local scripts/audit-fullscript-links.mjs --apply - Re-pull protocols manually per patient from the patient profile.
- Review
fullscript_sync_logforemail_mismatch,link_mismatch, orpractitioner_mismatchentries.
API routes (clinic)¶
| Route | Method | Purpose |
|---|---|---|
/api/integrations/fullscript/connect |
GET | Start OAuth |
/api/integrations/fullscript/roster |
GET | Fullscript patient roster with link status + latest active plan |
/api/integrations/fullscript/import-patient |
POST | Create/link Onset chart + pull latest active plan |
/api/integrations/fullscript/match-patients |
POST | Bulk email match + pull |
/api/clinic/patients/[id]/fullscript/link |
POST | Manual link + optional pull |
/api/clinic/patients/[id]/fullscript/sync-latest |
POST | Pull latest active plan for linked chart |
/api/clinic/patients/[id]/fullscript/push-patient |
POST | Create/link Fullscript chart (no plan pull) |
/api/clinic/patients/[id]/fullscript/ensure-patient |
POST | Match by email + link + pull |
/api/integrations/fullscript/dynamic-link |
POST | Open Fullscript in new tab |
Cron jobs¶
| Route | Schedule | Purpose |
|---|---|---|
POST /api/cron/protocol-deliveries |
Hourly (0 * * * *) |
Promote scheduled assignments → sent + notify |
POST /api/cron/fullscript-sync |
Hourly (15 * * * *) |
Poll Fullscript Events API for missed webhooks |
Sandbox E2E acceptance checklist¶
- Apply migration
20260624120000_fullscript_integration.sqlto Supabase (if not already applied). - Create a sandbox practitioner via Fullscript developer portal; sign in at
https://us-snd.fullscript.io. - Onset staging: Patients → Connect Fullscript — authorize with that Fullscript login.
- Patients page shows Fullscript roster; import or link a patient and pull protocol.
- Patient profile: no iframe embed; Pull latest protocol from Fullscript works.
- Pull protocol → confirm amber Protocol synced banner and assignment status (
scheduledorsent). - Send to patient app works for invited patients without the Onset app (queues assignment).
- Webhook or hourly cron → master protocol updates when plan changes in Fullscript.
- Hourly cron promotes
scheduledassignments when delay elapses. - Patient app E2E (Onset Staging TestFlight):
- Portal: send master protocol with Apply immediately (
status = sent). Scheduled/delayed assignments are invisible to the app until cron runs. - Copy connection code from patient profile (16 hex — not the clinic org code).
- iOS: More → My Practitioner (or onboarding practitioner code) → Sign in with Apple → accept assignment sheet.
- Verify supplement names on Protocol/Today; portal patient status →
active. - Readiness check:
node --env-file=.env.local scripts/verify-staging-patient-e2e.mjs [patient_id]
- Portal: send master protocol with Apply immediately (
Production gates¶
- SOC 2 Type 2 + Sub-BAA with Fullscript
- Partner approval via
api@fullscript.com - Production OAuth app + webhook secret
- Set
FULLSCRIPT_CUSTOMER_AVAILABLE=true, thenFULLSCRIPT_BILLING_ENABLED=truewhen ready to sell Solo add-on
Partner evaluation brief (PDF attachment) and outreach email template:
FULLSCRIPT_PARTNER_EVAL_BRIEF.md/FULLSCRIPT_PARTNER_EVAL_BRIEF.pdf— one-page scope, data flows, security, staging verificationFULLSCRIPT_PARTNER_EVAL_EMAIL.md— copy into email body; do not attach
Delivery modes¶
| Mode | Patient visibility | Routine activation |
|---|---|---|
delayed_push |
Hidden until delay elapses | On accept after visible |
immediate_preview |
Immediate | Manual "Add to my routine" |
immediate_apply |
Immediate | On accept |
Open in Fullscript¶
Dynamic link opens Fullscript in a new tab via POST /api/integrations/fullscript/dynamic-link. This is the supported way to manage supplement catalogs and plans in the native Fullscript UI.