42 endpoints · Last updated April 2026
When a clinic owner signs up, the system creates their account, sets up their workspace, and starts a 30-day free trial. Behind the scenes, this involves creating a secure login (Firebase Auth), a clinic record in the database, and a billing profile with Stripe.
Clinic owners can also invite their clinicians by email. Each invited clinician gets a password-reset link to set up their own login.
Clinic owners can add clinicians to their practice. The number of clinicians allowed depends on the billing tier: Solo (1), Studio (2–4), Clinic (5+). Extra seats can be purchased if you need more capacity within your tier.
Each clinician is linked to their PMS practitioner ID so their appointment data flows through automatically.
StrydeOS uses Stripe for all billing. Clinics choose which modules they want (Intelligence, Pulse, Ava, or Full Stack bundle), pick a tier based on clinic size, and select monthly or annual billing (20% discount for annual).
When a clinic subscribes, Stripe tells StrydeOS which modules to activate. If a payment fails, the clinic is marked "past due" but access isn't revoked immediately — giving time to update payment details via the billing portal.
Ava (voice receptionist) has a one-time £250 setup fee in addition to the recurring subscription.
StrydeOS connects to your existing Practice Management System (PMS) to pull in appointment data, clinician rosters, and patient records. Supported systems: WriteUpp, Cliniko, Halaxy, Zanda (Power Diary). PPS and TM3 support coming soon.
You enter your PMS API key in Settings, test the connection, and StrydeOS starts syncing. Data refreshes automatically — WriteUpp pushes updates in real-time via webhooks; other systems sync on a schedule.
For clinics without API access, you can upload CSV exports of your appointment or patient data. StrydeOS auto-detects common formats or lets you create a custom column mapping.
Home Exercise Programme (HEP) data flows in from your connected HEP provider (Physitrack, Rehab My Patient, or Wibbi) — tracking programme assignments, adherence, and completion rates per patient.
Ava is StrydeOS's AI voice receptionist, powered by ElevenLabs Conversational AI and Twilio telephony. When a patient calls, Ava answers, understands their request, and can book appointments directly into your PMS.
The booking flow: Patient calls → Ava converses → confirms booking details → n8n automation creates the appointment in your PMS → a copy is saved in StrydeOS for tracking. If the PMS write fails (e.g., the PMS is briefly offline), StrydeOS saves the booking locally and retries automatically.
Call data — duration, outcome (booked, cancelled, escalated, voicemail), transcripts, and sentiment — flows into the Receptionist dashboard so you can see how Ava is performing.
Pulse sends automated SMS and email messages to patients at the right moment. Six sequences run automatically:
When patients reply to a review prompt with an NPS score (0–10), StrydeOS automatically parses it and logs it to the Reputation dashboard. All messages are tracked: open rates, click rates, and whether the patient rebooked.
Every day at 6:00 AM UTC, StrydeOS runs its data pipeline. This pulls the latest data from your PMS, syncs patient and appointment records, computes the seven KPI metrics (follow-up rate, HEP compliance, programme assignment, utilisation, DNA rate, revenue per session, NPS), and triggers any due communications.
WriteUpp clinics also get near real-time updates — when an appointment is created or changed in WriteUpp, a webhook fires and StrydeOS re-computes within seconds.
For new clinics, a one-time 90-day backfill pulls historical data so you have context from day one.
StrydeOS supports GDPR and data privacy requirements out of the box:
These endpoints are used by the StrydeOS team (superadmins) to monitor system health, diagnose integration issues, and manage clinics.
The integration health dashboard shows sync success rates across all clinics — if a PMS connection is degrading, the team can see it before the clinic owner notices.
| Method | Header | Used By |
|---|---|---|
| Firebase ID Token | Authorization: Bearer <idToken> | Dashboard users (24 endpoints) |
| Cron Secret | Authorization: Bearer <CRON_SECRET> | Vercel Cron (pipeline/run, metrics/compute, bookings/retry-pms) |
| Webhook Secret | x-webhook-secret: <secret> | WriteUpp, n8n, Ava bookings |
| Stripe Signature | stripe-signature: <sig> | Stripe billing webhooks |
| ElevenLabs Signature | x-elevenlabs-signature: <sig> | ElevenLabs voice webhooks |
Roles: owner admin clinician superadmin — enforced via requireRole() in auth-guard.ts
clinicName: string ← required email: string ← required password: string ← required (min 8) profession: string ← optional clinicSize: string ← optional country: "uk"|"us"|"au" ← optional
{ uid, clinicId, email }
400 — Missing required fields 409 — Email already registered 500 — Internal server error
users doc, clinics doc (with compliance config derived from country), Stripe customeronboardingV2.stage: "signup_complete"clinicName: string ← required ownerEmail: string ← required ownerFirstName: string ← optional ownerLastName: string ← optional pmsType: string ← optional country: "uk"|"us"|"au" ← opt plan: string ← optional notionLeadId: string ← optional
x-admin-secret: <STRYDE_ADMIN_SECRET> Authorization: Bearer <token> (opt)
{ uid, clinicId, email,
passwordResetLink }
No body — clinicId from token
{ promoted: boolean,
alreadyLive?: boolean,
reason?: "no_admin_users"|"pending_logins" }
email: string ← required clinicianId: string ← optional
{ sent: true }
— or if Resend not configured —
{ sent: false, link, note }
name: string ← required role: string ← default "Physiotherapist" pmsExternalId: string ← optional (PMS practitioner ID) physitrackId: string ← optional
{ id: string,
clinician: { id, name, role, pmsExternalId,
physitrackId, active, avatar, createdAt } }
403 — Seat limit reached
{ error, currentCount, limit, canPurchaseSeat }
modules: ["intelligence"|"pulse"|"ava"|"fullstack"][] ← required tier: "solo"|"studio"|"clinic" ← default "studio" interval: "month"|"year" ← default "month" includeAvaSetup: boolean ← adds £250 one-time
{ url: string } ← Stripe Checkout URL
No body — clinicId from token{ url: string } ← Stripe Portal URLquantity: number ← 1–10, default 1
{ success: true, extraSeats: number }past_due (does NOT revoke access)provider: "writeupp"|"cliniko"|"halaxy"|"zanda" ← default "writeupp" apiKey: string ← required baseUrl: string ← optional (custom API URL)
{ ok: true }provider: string apiKey: string ← required
200: { ok: true, resolvedBase? } 400: { ok: false, error }
No body{ ok: true }{ results: [{ clinicId, ok, count?, error? }] }file: File (CSV) ← required fileType: "appointments"|"patients" ← required schemaId: string ← optional (auto-detect if omitted)
{ ok, count, imported, skipped, errors[] }file: File (CSV) ← required fileType: "appointments"|"patients" recipient: string ← extracts clinicId from email clinicId: string ← explicit override
x-inbound-secret: <CSV_INBOUND_SECRET>
name: string ← custom provider name fileType: "appointments"|"patients" ← required fieldMap: Record<csvHeader, canonicalField> ← required dateFormat: "uk"|"us"|"iso" ← default "uk" statusMap: Record<source, target> ← appointments only
date, time, endDate, endTime,
patientId, patientFirst, patientLast,
patientEmail, patientPhone, patientDob,
practitioner, practitionerId,
type, status, notes, price, duration
limit: number ← 1–100, default 30
{ ok, imports: [{ id, fileName, fileType,
importedAt, count, status, errors }] }provider: "physitrack" | "rehab_my_patient" | "wibbi" ← required apiKey: string ← required
{ ok: true }200: { ok: true } 400: { ok: false, error }
{ ok: true }No body — config pulled from clinic doc{ agent_id, message }book_appointment — create booking via PMScheck_availability — query open slotsupdate_booking — modify existing appointmenttransfer_to_reception — warm transfer to clinic reception desklocality: string ← optional, e.g. "London" for local number
{ phone, agent_id, trunk_sid, message }
{ error: "Already provisioned", phone }
transfer_to_reception tool. Warm-transfers the live call to the clinic's reception number via Twilio.Called by ElevenLabs when Ava's
transfer_to_reception tool fires
(complaint, escalation, human help needed)
{ result: "Call is being transferred..." }
// or graceful fallback message if transfer fails
clinicId: string ← required patientFirstName: string ← required patientLastName: string ← required patientPhone: string ← required (E.164) clinicianExternalId: string ← required dateTime: string ← required (ISO 8601) patientEmail: string ← optional durationMinutes: number ← default 45 appointmentType: string ← optional notes: string ← optional callId: string ← ElevenLabs call ID idempotencyKey: string ← dedup key
{ ok, appointmentId, pmsExternalId,
patientExternalId }
{ ok, partial: true,
pmsExternalId: null,
warning: "PMS write failed..." }
{ ok, retried, failed, skipped, results[] }Summary contains "book/appt" → booked Summary contains "cancel" → follow_up_required Summary contains "voicemail" → voicemail Summary contains "escalat" → escalated Summary contains "success" → resolved
clinicId: string ← required patientId: string ← required patientName: string ← required sequenceType: string ← required channel: "sms"|"email" ← required to: string ← required body: string ← required subject: string ← required for email templateVars: Record ← optional
hep_reminder rebooking_prompt pre_auth_collection review_prompt reactivation_90d reactivation_180d
clinicId, patientId, sequenceType, channel, logId, executionId, outcome: "booked"|"unsubscribed"|"no_action" openedAt?, clickedAt?
type: "inbound_reply" clinicId, fromPhone (E.164), replyText, receivedAt? NPS parsing: "8", "8/10", "eight" → 8 ≥9 = promoter, 7-8 = passive, <7 = detractor
x-webhook-secret: <WRITEUPP_WEBHOOK_SECRET> x-strydeos-clinic-id: ← optional direct clinic ID
clinicId?: string ← optional (all clinics if omitted for superadmin)
{ ok, result } or { results: [...] }clinicId?: string
clinicId?: string ← optional (all clinics if omitted) weeksBack?: number ← 1–12, default 6
{ clinicId, written } or { results: [...] }type: "access"|"correction"|"deletion" ← required requestedBy: string ← required patientId?: string description: string ← required
{ requests: [{
id, type, status, requestedBy,
patientId, description,
responseDeadline, ← 30 days from creation
completedAt, createdAt, updatedAt
}] }
{ success, data: {
exportedAt, patientId, clinicId,
patient: { ... },
appointments: [ ... ], ← up to 1000
comms_log: [ ... ], ← up to 1000
outcome_scores: [ ... ]← up to 1000
} }
{ success, message, gracePeriodEnd }clinicId: string ← required
{ success, baaSignedAt }days: number ← 1–90, default 30
{ clinics: [{
clinicId, clinicName, pmsProvider,
integrations: {
[provider]: {
totalSyncs, successfulSyncs,
successRate, avgDurationMs,
lastSuccessAt, lastFailureAt,
lastErrors, status: "healthy"|"degraded"|"down"
}
}
}] }clinicId: string ← required
{ ok, summary: {
dateFrom, dateTo, totalReturned,
keyShape: [{ keys, sample }],
confirmedFields, parseError?
} }None — public endpoint CORS: Access-Control-Allow-Origin: *
{
overall: "operational"|"degraded"|"down",
checkedAt: ISO 8601,
services: {
[key]: {
name, status: "operational"|"degraded"|"down",
latency: number ← ms, -1 if unreachable,
checkedAt, statusSource: "statuspage"|"ping",
uptimeHistory: number[] ← 30 entries (1=up, 0.5=degraded, 0=down)
}
}
}
Services checked (17):
Statuspage API: Vercel, Stripe, Twilio, Sentry, ElevenLabs
Google Cloud: Firebase / Firestore
HTTP ping: StrydeOS, Resend, n8n, WriteUpp,
Cliniko, Halaxy, Zanda, Physitrack, Heidi, Google PlacesAuto-refreshes every 60s · Pulls live from strydeos.com/status