Get started
API reference

Every route a key can call.

96 endpoints, generated from the code that serves them, so this page cannot list a door that is shut. One base URL, bearer auth, JSON in and out.

  • Base URL https://medappz.com/v1
  • 15 scopes
  • 17 webhook events

Authentication

Two kinds of key, both made by a clinic admin inside the app and shown once.

API keys · mza_…Admin → Integrations. Each key holds named scopes and can call only the routes those scopes open. It reaches its own clinic and nothing else, expires within two years, and is limited to 600 requests a minute.
curl https://medappz.com/v1/patients/search?q=asha \
  -H "Authorization: Bearer mza_<prefix>_<secret>"
Device keys · mzv_…Voice Inbox → Integrations. For a recorder or notetaker: it can open, feed and close voice sessions and read what became of them, and nothing else.
curl -X POST https://medappz.com/v1/voice/ingest/sessions \
  -H "X-MedAppz-Key: mzv_<prefix>_<secret>" \
  -H "Content-Type: application/json" -d @session.json

Scopes

What a key can be given. Billing and pharmacy are read-only feeds of amounts, ids, medicine names and quantities. No key can write a prescription, a note or a result, or read clinical notes or HR.

lead.readlead.managepatient.readappointment.readquote.readsurvey.readadmitline.readimaging.readimaging.managebenchlink.postbilling.readpatient.intakeappointment.intakepharmacy.feed.readfhir.read

Endpoints

Paths are under https://medappz.com/v1. Search by path, scope or what it does.

Leads and CRM · 19Push enquiries from your website, ad forms or call centre, and move them through the pipeline.
  • GET/v1/crm/leads/scoresScores: crm leads scoresScope: lead.read
  • GET/v1/crm/pipeline/reportReport: crm pipeline reportScope: lead.read· Query: from, source, to
  • GET/v1/leadsList: leadsScope: lead.read· Query: due, mine, source, status
  • POST/v1/leadsRecord an enquiry.Scope: lead.manage· JSON body
  • GET/v1/leads/:idDetail: leads (one record)Scope: lead.read
  • POST/v1/leads/:id/assignAssign: leads assign (one record)Scope: lead.manage· JSON body
  • POST/v1/leads/:id/bookedBooked: leads booked (one record)Scope: lead.manage· JSON body
  • POST/v1/leads/:id/contactedContacted: leads contacted (one record)Scope: lead.manage· JSON body
  • POST/v1/leads/:id/convertConvert: leads convert (one record)Scope: lead.manage· JSON body
  • POST/v1/leads/:id/lostLost: leads lost (one record)Scope: lead.manage· JSON body
  • POST/v1/leads/:id/nextNext: leads next (one record)Scope: lead.manage· JSON body
  • GET/v1/leads/attributionAttributionDesk: enquiries credited to sources or campaigns under a model.Scope: lead.read· Query: by, from, model, to
  • GET/v1/leads/package-funnelThe package funnel branch by branch (Fold sweep 3: AestheticUpsell).Scope: lead.read· Query: from, to
  • GET/v1/leads/sourcesWhere enquiries come from and what each source costs.Scope: lead.read· Query: from, to
  • GET/v1/leads/spendSpend: leads spendScope: lead.read
  • PUT/v1/leads/spendSet spend: leads spendScope: lead.manage· JSON body
  • GET/v1/quotesList: quotesScope: quote.read
  • GET/v1/surveysList: surveysScope: survey.read
  • GET/v1/surveys/summarySummary: surveys summaryScope: survey.read
Patients · 9Find a patient, read the record your key is allowed to read, check for duplicates, and register a new patient.
  • GET/v1/patients/:patientIdThe full record needs a care relationship, not just the permission. Holding `patient.read` lets you read *a* patient, never *this* one.Scope: patient.read
  • GET/v1/patients/:patientId/chartChart: patients chart (one record)Scope: patient.read
  • GET/v1/patients/by-mrn/:mrnBy mrn: patients by mrn (one record)Scope: patient.read
  • POST/v1/patients/duplicate-checkCalled as the clerk types, before anything is created.Scope: patient.read· JSON body
  • POST/v1/patients/intakeRegister a patient; a strong duplicate is refused until confirmedNotDuplicate is sent.Scope: patient.intake· JSON body· 120/min
  • POST/v1/patients/intake/duplicate-checkLook for an existing record before registering; phones come back masked.Scope: patient.intake· JSON body· 120/min
  • GET/v1/patients/searchSearch: patients searchScope: patient.read· Query: limit, q
  • GET/v1/portal-accessStatus: portal accessScope: patient.read· Query: patientId
  • GET/v1/registration-requestsList: registration requestsScope: patient.read· Query: status
Appointments and availability · 21Read diaries, free slots and bookings, and book, move or cancel an appointment, for a booking widget, a call centre or a calendar.
  • GET/v1/appointments/availabilityAvailability: appointments availabilityScope: appointment.read· Query: from, schedulableId, serviceType, to
  • GET/v1/appointments/dayAppointment day: appointments dayScope: appointment.read· Query: date, schedulableId
  • GET/v1/booking-offersList: booking offersScope: appointment.read· Query: exceptionId, status
  • GET/v1/booking-offers/exceptions/:exceptionIdAffected: booking offers exceptions (one record)Scope: appointment.read
  • GET/v1/booking-policy/depositsDeposits: booking policy depositsScope: appointment.read· Query: status
  • GET/v1/booking-policy/lateLate: booking policy lateScope: appointment.read
  • GET/v1/booking-seriesList: booking seriesScope: appointment.read· Query: patientId, status
  • GET/v1/booking-series/:idGet: booking series (one record)Scope: appointment.read
  • POST/v1/booking-series/planPlan: booking series planScope: appointment.read· JSON body
  • GET/v1/encounters/:id/follow-up-slotsFollow up slots: encounters follow up slots (one record)Scope: appointment.read· Query: days, schedulableId
  • GET/v1/patients/:patientId/appointmentsPatient appointments: patients appointments (one record)Scope: appointment.read
  • GET/v1/scheduling/bookingsBookings: scheduling bookingsScope: appointment.read· Query: limit, subjectId, subjectType
  • GET/v1/scheduling/bookings/:groupId/cardOne appointment as a confirmation reads it: doctor, type, prep note, when.Scope: appointment.read
  • GET/v1/scheduling/bookings/:groupId/icsThe same appointment as a calendar file for a phone.Scope: appointment.read
  • GET/v1/scheduling/diaryThe calendar's read: a day or a week of bookings with the patient's name, the type and the state on each, for every resource at once.Scope: appointment.read· Query: facilityId, from, schedulableId, to
  • POST/v1/scheduling/intake/bookingsBook a patient of this clinic into an open slot.Scope: appointment.intake· JSON body
  • POST/v1/scheduling/intake/bookings/:groupId/cancelCancel a patient's booking, with the reason the patient gave.Scope: appointment.intake· JSON body
  • POST/v1/scheduling/intake/bookings/:groupId/rescheduleMove a patient's booking to another open slot; the old one is released.Scope: appointment.intake· JSON body
  • GET/v1/scheduling/intake/resourcesThe doctors and services a patient can be booked with.Scope: appointment.intake· Query: facilityId
  • GET/v1/scheduling/intake/slotsOpen appointment slots in a window of up to 62 days, with who they are with.Scope: appointment.intake· Query: facilityId, from, schedulableId, serviceType, to
  • GET/v1/scheduling/schedulables/:id/dayOne resource's list for one day — the clinic's working view.Scope: appointment.read· Query: date
Billing · 4Read-only, for an accounting system: bills with their lines, payments, and what each patient still owes.
  • GET/v1/billing/feed/billsBills finalised in a period, with totals, paid and balance; paged by `after`.Scope: billing.read· Query: after, from, limit, patientId, status, to
  • GET/v1/billing/feed/bills/:idOne bill: totals, its lines (service, quantity, price, tax) and its payments.Scope: billing.read
  • GET/v1/billing/feed/outstandingWhat each patient still owes on finalised bills, optionally for bills from a period.Scope: billing.read· Query: after, from, limit, patientId, to
  • GET/v1/billing/feed/paymentsPayments received in a period, with method, amount, status and receipt number.Scope: billing.read· Query: after, from, limit, patientId, to
Pharmacy and stock · 3Read-only: stock levels, batches near expiry, and prescriptions waiting to be dispensed (medicine and quantity, no clinical detail).
  • GET/v1/pharmacy/feed/dispense-queuePrescriptions waiting to be dispensed: medicine and quantity per line, patient id and MRN.Scope: pharmacy.feed.read· Query: limit
  • GET/v1/pharmacy/feed/expiringBatches on the shelf that expire within `withinDays` (default 60), or already have.Scope: pharmacy.feed.read· Query: withinDays
  • GET/v1/pharmacy/feed/stockStock per item — on hand, reorder level, low, expiring soon; filter `low` or `expiring`.Scope: pharmacy.feed.read· Query: filter
Admissions funnel · 5Read-only: report the admission pipeline to a referral partner or a dashboard.
  • GET/v1/admitline/boardBoard: admitline boardScope: admitline.read· Query: doctor, mine, owner, patientId, payer, q, stage
  • GET/v1/admitline/cases/:idDetail: admitline cases (one record)Scope: admitline.read
  • GET/v1/admitline/doctorsThe "advised by" picker. Names only — no privileges, no rota.Scope: admitline.read
  • GET/v1/admitline/funnelFunnel: admitline funnelScope: admitline.read· Query: by, from, to
  • GET/v1/admitline/summaryCounts of admission cases by stage, for a dashboard.Scope: admitline.read
Imaging bridge · 13For an imaging gateway on the hospital network: the modality worklist in, studies back out.
  • GET/v1/imaging/archivesArchives: imaging archivesScope: imaging.read
  • POST/v1/imaging/archives/checkTest the connection to each imaging archive and record the result.Scope: imaging.read
  • POST/v1/imaging/pollAsk the archive what it has now.Scope: imaging.manage
  • GET/v1/imaging/studiesStudies: imaging studiesScope: imaging.read· Query: patientId, state, worklistEntryId
  • POST/v1/imaging/studiesThe bridge's way in, and the manual one.Scope: imaging.manage· JSON body
  • POST/v1/imaging/studies/:id/discardDiscard: imaging studies discard (one record)Scope: imaging.manage· JSON body
  • POST/v1/imaging/studies/:id/matchMatch: imaging studies match (one record)Scope: imaging.manage· JSON body
  • POST/v1/imaging/studies/:id/refreshRefresh: imaging studies refresh (one record)Scope: imaging.manage
  • POST/v1/imaging/studies/:id/unmatchUnmatch: imaging studies unmatch (one record)Scope: imaging.manage· JSON body
  • GET/v1/imaging/studies/unmatchedStudies the archive holds that are not yet matched to a patient.Scope: imaging.read
  • GET/v1/imaging/worklistWorklist: imaging worklistScope: imaging.read· Query: from, patientId, state, station, to
  • POST/v1/imaging/worklistSchedule: imaging worklistScope: imaging.manage· JSON body
  • POST/v1/imaging/worklist/:id/stateSet state: imaging worklist state (one record)Scope: imaging.manage· JSON body
Lab analysers · 2For the BenchLink edge agent that speaks ASTM and HL7 v2 to the analysers on the bench.
  • POST/v1/benchlink/device/resultsPost an analyser’s results for a sample; they wait for verification.Scope: benchlink.post· JSON body
  • GET/v1/benchlink/device/worklistThe host query: which tests are ordered on this sample barcode.Scope: benchlink.post· Query: analyzerId, barcode
Voice ingest · 5For recorders, phone apps and meeting notetakers. Uses a device key (mzv_…), not an API key.
  • POST/v1/voice/ingest/sessionsOpen a consultation. Refused without the patient's consent; idempotent on `externalId`.Device key· JSON body· 120/min
  • GET/v1/voice/ingest/sessions/:idStatus and every proposal, with what became of it.Device key· 600/min
  • POST/v1/voice/ingest/sessions/:id/closeClose it: extraction starts straight away.Device key· 120/min
  • GET/v1/voice/ingest/sessions/:id/eventsServer-sent events: `proposal.created`, `proposal.accepted`, `proposal.rejected`, `session.extracted`, `session.blocked`.Device key· Query: after· 30/min
  • POST/v1/voice/ingest/sessions/:id/segmentsAppend up to 200 segments. A segment id already stored is counted as a duplicate, not an error.Device key· JSON body· 600/min
Other · 15
  • GET/v1/calls/centre/callerCaller: calls centre callerScope: appointment.read + patient.read· Query: phone
  • GET/v1/calls/centre/slotsSlots: calls centre slotsScope: appointment.read· Query: days, near, q
  • GET/v1/calls/centre/todayToday: calls centre todayScope: appointment.read
  • POST/v1/fhir/$exportStart a bulk FHIR export of every patient: 202, with the status URL in Content-Location.Scope: fhir.read· Query: _outputFormat, _since, _type· 10/hour
  • GET/v1/fhir/$export/:jobIdPoll a bulk export: 202 while it works, then signed NDJSON links that expire.Scope: fhir.read
  • DELETE/v1/fhir/$export/:jobIdCancel a bulk export and delete its files.Scope: fhir.read
  • GET/v1/fhir/Patient/:patientIdOne patient as a FHIR Patient resource.Scope: fhir.read
  • GET/v1/fhir/Patient/:patientId/$everythingThe patient's record as a searchset Bundle. `_type` narrows it. For an API key, DocumentReference entries are metadata only: their download link needs `file.read`, which no key can hold.Scope: fhir.read· Query: _type· 120/min
  • GET/v1/patient-messagesDesk threads: patient messagesScope: patient.read
  • GET/v1/patient-messages/:threadIdDesk thread: patient messages (one record)Scope: patient.read
  • GET/v1/patient-zara/messagesMessages and red flags patients left with Rain: open first.Scope: patient.read
  • GET/v1/surveypulse/carersCarers: surveypulse carersScope: survey.read
  • GET/v1/surveypulse/carers/reviewsReviews: surveypulse carers reviewsScope: survey.read
  • GET/v1/surveypulse/carers/ruleRule: surveypulse carers ruleScope: survey.read
  • GET/v1/surveypulse/carers/to-rateTo rate: surveypulse carers to rateScope: survey.read

Webhook events

Signed with HMAC-SHA256 and retried with backoff. Envelopes carry ids and changed field names, never clinical content.

patient.patient.registeredpatient.patient.updatedpatient.patient.mergedscheduling.booking.createdscheduling.booking.cancelledscheduling.booking.rescheduledscheduling.booking.no_showclinical.encounter.finishedbilling.bill.finalisedbilling.payment.receivedbilling.bill.voidedbilling.payment.refundeddispensedesk.dispense.completedwardline.admission.admittedwardline.admission.dischargedleaddesk.lead.createdadmitline.case.staged

OpenAPI document and SDKs

Generated from the same controllers as the table above, by the same build that fails when either goes stale — so neither can describe a route the code does not actually open to a key.

OpenAPI 3.1 documentEvery endpoint on this page, plus request/response shapes, security schemes and the 17 webhook events, as one JSON file. Feed it to Postman, Redocly or your own client generator.Download openapi.json
TypeScript SDK · @medappz/sdkA zero-dependency fetch client: one typed method per endpoint, bearer auth for either key kind, Idempotency-Key support, and automatic retry on 429. No build step — copy the source into your project.Download the .zip
Python SDK · medappzThe same shape over requests: one method per endpoint, typed request bodies, the same auth, idempotency and 429-retry behaviour as the TypeScript client.Download the .zip
curl -O https://medappz.com/developers/openapi.json

Errors and retries

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "phone must be a valid mobile number.",
    "requestId": "req_01J…"
  }
}
Send an Idempotency-Key header on writes so a retry is a no-op, not a duplicate. A 429 means slow down; quote the requestId when you write to us. The developer guide walks through pushing a lead and verifying a webhook signature end to end.