Scribes and note-taking clinic extensions integration guide

This guide is for developers of tools that a doctor launches from inside Medevio and that write their result back into the patient request — typically automated scribes and note-taking apps that record a consultation and return the finished text as the clinic medical record (zdravotní záznam).

Your app runs entirely on your own infrastructure. Medevio adds a button that opens it with the id of the request the doctor is working on, and exposes the public REST API you write the result back through. No custom endpoint and no middleware is involved on either side.

The integration has two halves that are deliberately independent of each other:

  1. The launcher — a button in the Medevio doctor app that opens your app in a new tab, carrying the patient request id. Installing the extension controls only whether that button is visible; it grants no access to any data.

  2. The write-back — your server calling the Medevio REST API with an API key the clinic issued to you. This is what actually reads and writes clinic data, and it works with or without the button.

Basics

  • Base URL: https://api.medevio.cz/external/v1/clinics/{clinicSlug}

  • Clinic identification: the clinic slug — the stable identifier of the clinic in Medevio — is part of the URL path, same as in the rest of the Medevio API.

  • Content type: application/json requests and responses, except the direct upload to storage (step 5), which sends the raw file.

  • Response envelope: every success response wraps the payload in data; errors return { "error": "...", "code": "..." }.

  • Extensions are catalogued by Medevio. There is no API and no self-service form for registering one — you send us the metadata described below and we publish it. See Prerequisites.

  • Machine-readable reference: all endpoints below are documented in the Medevio API reference under the Patient Requests and Users tags.

Authentication

Every call to the API requires an API key (Personal Access Token) sent with the request:

Authorization: Bearer <token>

The key is issued per clinic, by the clinic itself — not by Medevio to you centrally. The doctor generates it in Medevio (clinic picture → Propojky (Integrations)Správa API klíčů (API key management)) and pastes it into your app's settings. This flow needs both the Read scope (request detail) and the Write scope (medical record, attachments).

A key is bound to the clinics it was issued for: a key from one clinic cannot read or write another clinic's data. A missing or invalid token returns 401; a token without the required scope or without access to the clinic in the URL returns 403.

To check that a freshly pasted key works before you store it, call GET /external/v1/user/me — it works with any scope and returns nothing but the identity the key was issued for. Do this at the moment the doctor saves the key, and show them a clear success or failure state.

Treat the key as a server-side secret:

  • Keep it on your backend. Never ship it to a browser or mobile client, and never accept a key through the launch URL or any query parameter.

  • Store one key per clinic, encrypted at rest. Never share a key between clinics.

  • Scopes are coarse. Write covers the whole external API for the clinics the key is bound to, not just this flow, and every write is attributed to the account the key belongs to.

  • Treat 401 as revoked. Prompt the doctor for a new key instead of retrying in a loop.

Prerequisites

Get your extension listed

The extension catalog is curated. Send podpora@medevio.cz the metadata block below together with your icon and cover image; Medevio create the extension catalog listing.

Field

Type

Required

Description

slug

string

yes

Stable identifier, lowercase kebab-case (e.g. example-scribe). Never changes once published.

name

string

yes

Display name in the marketplace and the button tooltip.

description

Markdown

yes

Marketplace and detail-page copy. Paragraphs, bold, italics, bullet lists. Keep the first line short — catalog tiles truncate after ~3 lines.

shortDescription

string

yes

Marketplace listing - Shown on the marketplace tiles.

Keep it under ~75 characters; longer text is truncated on the tile.

iconUrl

URL

yes

Image file: Square icon image. Used on the tile inside app manager (256×256 px).

buttonIconURL

URL

yes

Image file: Square icon. Used inside the launcher button (60×60 px).

imageUrl

URL

yes

Image file: Cover image in 16:9, shown on the detail page (e.g. 1200×630 px) shown in extension store.

publisher.name

string

yes

Your company name, shown as the publisher.

publisher.websiteUrl

URL

yes

Your homepage.

publisher.termsUrl

URL

no

Your terms & conditions; linked from the install dialog.

infobox.title, infobox.text

string

no

Highlighted box on the detail page. Use it for the one-time setup step.

infobox.button.label, infobox.button.url

string, URL

no

Optional call-to-action inside the infobox. Send both or neither.

redirectUrlTemplate

URL template

yes

The launch URL — see step 1.

version

string

no

Your app version, shown on the detail page. Maintained manually — tell us when it changes.

Send the image files, not links to your own CDN — Medevio hosts extension images itself so the catalog does not depend on third-party availability. Button Icon: square PNG with a transparent background, around 60×60 px (preferably: monochrome or high-contrast). All media size ideally under 300 kB.

Example seed for extension listing (uploaded by Medevio)

# --- Extension listing: example-scribe ---

slug:          example-scribe                 # stable identifier, never changes
name:          "Example Scribe for Medevio"
description: |
  AI assistant that turns a conversation with the patient into structured
  medical documentation.

  **How it works:**
  - Activate the app before the consultation starts.
  - Talk to your patient the way you normally would.
  - Get a finished record — just review it, save it and send it to your Medevio.

iconUrl:       "https://static.medevio.cz/extensions/example-scribe/icon.png"          # catalog tile
imageUrl:      "https://static.medevio.cz/extensions/example-scribe/cover.jpg"         # detail page, 16:9
buttonIconUrl: "https://static.medevio.cz/extensions/example-scribe/button-icon.png"   # launcher button, 20×20

version:       "1.0"
scopes:        [PATIENT_REQUEST_DETAIL]
owners:        [CLINIC]

# Launch URL — {{patientRequestId}} and {{clinicSlug}} are the only placeholders
redirectUrlTemplate: "https://app.example.com/?new-visit=true&recording=first&medevio-patient-request-id={{patientRequestId}}"

publisher:
  name:       "Example Health"
  websiteUrl: "https://www.example.com"
  termsUrl:   "https://www.example.com/terms"      # optional

# Optional highlighted box on the detail page — use it for the one-time setup step
infobox:
  title: "Set up the connection"
  text:  "Before first use, paste your clinic API key into the Example app."

Let the clinic connect your app

Nothing happens automatically when a doctor installs your extension — the API key described in Authentication is a separate, manual step on your side of the fence. Design for it:

  • State the setup step in infobox so the doctor sees it on the detail page before the first click.

  • Medevio cannot tell whether the doctor is signed in to your app or whether the key is configured, and does not try to. Every "not connected yet" state has to be explained on your page.

    The flow at a glance

    Who

    What happens

    Detailed in

    Doctor

    Opens a patient request and clicks your extension button

    Medevio

    Opens your app in a new tab; the launch URL carries {{patientRequestId}}

    Steps 1–2

    Your app

    Records, transcribes and generates the note; the doctor reviews it

    Your backend

    GET …/patientRequests/{id} — optional: read the current record first

    Step 3

    Your backend

    PUT …/patientRequests/{id}/clinic-medical-record — write the note

    Step 4

    Your app

    Recomended: Closes its own tab; the doctor is back in Medevio with the record filled in

    Step 5

Everything from the GET onwards runs on your backend with the clinic's API key. Nothing in this flow requires the doctor to stay on your page once the write-back has succeeded.

1. Define your launch URL

redirectUrlTemplate is a URL template you supply with your metadata. Medevio fills in the placeholders at click time and opens the result in a new tab.

Two placeholders are available:

Placeholder

Value

{{patientRequestId}}

UUID of the patient request the doctor has open

{{clinicSlug}}

Slug of the clinic the doctor is working in

// Template you send us
https://app.example.com/?medevio-patient-request-id={{patientRequestId}}

// URL the doctor actually opens

https://app.example.com/?medevio-patient-request-id=6f1c2a9e-…-8b3d
  • Values are URL- encoded on substitution.

  • There are no other placeholders. No patient name, birth number or any other personal data goes into the URL, by design.

  • Resolution fails closed. If the template contains a placeholder Medevio cannot fill — a typo, an invented name, a retired one — the URL resolves to nothing and the button is hidden entirely rather than opening a half-built link. Use only the two placeholders above.

  • Put your own parameters in the template so the doctor lands in a working state instead of a dashboard: a visit created, recording already running.

2. Handle the launch

The doctor clicks your button and Medevio opens your URL with target="_blank" rel="opener" — a new tab that keeps a reference to the Medevio tab, which is what lets your page close itself in step 5.

Active installed extensions appear as a button group in the header of the request detail: up to three icon buttons, plus a Connect menu listing all of them.

What arrives is a patient request id and nothing else. Design around that:

  • The URL is not an authentication mechanism. It carries no credential and proves nothing about who opened it. Authenticate the doctor on your own side, exactly as you would if they had typed your address by hand.

  • Treat the id as an opaque pointer. It tells you which request to write to. It is not a permission — the write is authorized by the clinic's API key, and knowing an id is not evidence that the person in front of you may see that request.

  • Be idempotent per patient request id. The same id can arrive several times: a double click, a reload, the doctor coming back an hour later. Resume the existing visit instead of creating a duplicate.

  • Do not assume the tab stays open. The doctor may close it, navigate away, or lose the browser. The write-back has to be driven by your backend, not by a page staying alive.

3. Read the patient request

Optional, but the safest first move when the doctor may already have written something into the record.

GET https://api.medevio.cz/external/v1/clinics/my-clinic/patientRequests/0a9d3c57-51c2-4a6e-8f19-6b2c7d9e4455
// Response (200), abbreviated
{
  "data": {
    "id": "0a9d3c77-51b2-4a6e-8f10-6b2c7d9e4455",
    "title": "Consultation",
    "doneAt": null,
    "url": "/my-clinic/pozadavky?pozadavek=0a9d3c77-…",
    "clinicMedicalRecord": "<p>BP 130/80</p>",
    "clinicMedicalRecordVisibleToPatient": false,
    "medicalRecords": []
  }
  • clinicMedicalRecord is the text you are about to replace. Read it before you write if there is any chance the doctor typed into it — see step 4.

  • medicalRecords[] lists documents already attached to the request, each with a presigned url (inline) and downloadUrl (forces a download).

  • A request belonging to another clinic returns 404, not 403, so existence is never leaked outside the clinic your key is authorized for.

  • This is the most expensive call in the flow (it loads the patient, tags, reservations and every attachment) — do not call it per write out of habit if you have no reason to read.

4. Write the clinic medical record

// PUT …/patientRequests/0a9d3c77-51b2-4a6e-8f10-6b2c7d9e4455/clinic-medical-record
{
  "text": "<p><strong>Subjective:</strong> Cough for five days, no fever.</p><ul><li>Chest clear</li></ul>",
  "visibleToPatient": false
}

// Response (200)
{
  "data": {
    "text": "<p><strong>Subjective:</strong> Cough for five days, no fever.</p><ul><li>Chest clear</li></ul>",
    "visibleToPatient": false
  }
}
  • Each call replaces the whole record. The previous text is kept as a version in history, but from the doctor's point of view it is gone from the editor. If the doctor may already have written something, read it in step 3 and append — do not overwrite blind. The same decision applies when your app runs twice on one request: decide deliberately whether the second run appends to or replaces your own earlier output.

  • The record is HTML. Send simple, well-formed markup: <p>, <br>, <strong>, <em>, <ul>/<ol>/<li>, headings. No <script>, no <style>, no external references. Plain text is accepted but renders as a single block.

  • text: null clears the record.

  • visibleToPatient defaults to false. Sending true publishes the note to the patient in the Medevio app — only do that when the doctor explicitly asked for it. There is no API call to unpublish it afterwards; correcting a mistake requires the clinic to act in the app.

  • Do not issue parallel writes to the same request. Wait for one call to finish before sending the next.

5. Hand the doctor back

  • Recomended: Close your tab once the write-back has succeeded. window.close() works because Medevio opened the link with rel="opener". The doctor lands back on the patient request with the record filled in.

  • Tell the doctor what you wrote before you close. They do not see the API call happen — make the outcome explicit on your page.

  • Do not resolve the request. PUT …/patientRequests/{id}/resolve exists, but closing a request is the doctor's decision, not a side effect of writing a note.

  • Tagging the request is optional. If the clinic wants a marker in its inbox, list the clinic's request tags with GET …/tags?type=patient_request and send their ids to PATCH …/patientRequests/{id} as addTagIds.

Error handling & limits

Errors use standard status codes with { "error": "...", "code": "..." }:

  • 400 — validation failure (VALIDATION_FAILED), e.g. a contentType outside the allowed list, a malformed UUID, or a request detail holding more than 1000 attachments.

  • 401 — missing, unknown, expired or deactivated token. Ask the doctor for a new API key; do not retry.

  • 403 — the token lacks the required scope or has no access to the clinic in the URL (FORBIDDEN).

  • 404 — unknown patient request, or one belonging to another clinic (PATIENT_REQUEST_NOT_FOUND). Stop; do not retry with the same id.

  • 410 — the patient was merged into another record (PATIENT_MERGED). Permanent: the response carries mergedInto with the surviving patient id — store that id and use it from then on.

  • 5xx — internal error. GET detail and the medical record write are safe to retry (the write is a replace, not an append).

Testing checklist

Register your own test clinic at https://my.medevio.cz/registrace-lekare — sign up or log in with an existing Medevio account, fill in the basics and accept the terms. The clinic exists immediately and stays invisible to patients until it is published manually. Send us its slug and we install your extension there.

Then run the whole flow end to end:

  1. Your button appears in the Connect group on the request detail, with the icon rendering cleanly at 60×260 px.

  2. The launch URL resolves and your app receives the correct patientRequestId.

  3. A missing or invalid API key produces a clear message in your app — Medevio shows nothing.

  4. Opening the same request twice does not duplicate the visit or the record.

  5. A record the doctor wrote by hand is not silently overwritten.

  6. visibleToPatient is true only when the doctor asked for it.

  7. Your tab closes itself after a successful write-back and the record is visible on the request.

  8. Your error paths are exercised at least once: a 401 from a revoked key (prompts for a new one, no retry loop), a request id from another clinic (404), and an unsupported contentType at attachment registration (400).

Medevio API