How to Build a Medicus Integration for Your Digital Health Product

Written by Technical Team Last updated 11.09.2026 19 minute read

Home>Insights>How to Build a Medicus Integration for Your Digital Health Product

Integrating a digital health product with Medicus is best approached as a clinical workflow integration rather than simply an API development task. The technical work involves authentication, patient matching, clinical data retrieval, record write-back, appointments, workflow automation and webhooks, but a successful implementation starts one level above the endpoints themselves: with a precise definition of what your product needs to do inside a GP practice.

Before writing integration code, document the journey from the moment a user or automated process enters your product to the point at which information is read from, or written back to, Medicus. Identify which person is acting, which patient is involved, which clinical information is required, which action needs to occur in Medicus, and what should happen when the action cannot be completed.

This matters because Medicus permissions are granular. Access is granted to individual endpoints and authentication modes rather than giving an application unrestricted access to the entire Transactional API. Your implementation therefore needs to be designed around the smallest set of API capabilities required for the product.

A digital triage platform, for example, might need to identify a patient, obtain selected information from their GP record and file the outcome of a consultation. An appointment product may instead need appointment metadata, sites, available slots, booking and cancellation. A clinical AI application might need to retrieve a patient’s care record, process relevant information, and create an encounter containing structured observations, coded clinical entries and supporting notes.

Defining this workflow first gives you the blueprint for the entire integration.

1. Map Your Product Workflow to the Medicus API

Start by translating each step of your application’s workflow into an explicit interaction with Medicus. Avoid beginning with a broad requirement such as “integrate with the patient record”. Break the journey down into individual operations.

A useful integration map might contain:

  • how the practice or Medicus tenant is identified;
  • how the patient is found or matched;
  • whether the application needs patient demographics;
  • which elements of the care record need to be retrieved;
  • whether documents need to be listed or downloaded;
  • which information needs to be written back;
  • whether the write-back should create a note, observation, document, referral or complete encounter;
  • whether appointment availability, booking or cancellation is required;
  • whether the workflow needs to run as the application itself or on behalf of an authenticated Medicus user;
  • which events should trigger activity in your application;
  • and how failures, unavailable data and restricted patient records should be handled.

This mapping exercise should result in an endpoint matrix that becomes part of your technical specification.

Patient identification should be designed particularly carefully. Medicus provides both patient search and patient matching capabilities, and these should not be treated as interchangeable.

Free-text search is suitable where a human user needs to locate a patient interactively. Your interface might allow a user to search using an NHS number, name, date of birth or other demographic details and then choose the correct record.

Automated workflows should favour deterministic matching. If your platform already knows an NHS number, or has a combination of demographic details, use the matching workflow and explicitly handle cases where no unique patient can be established.

Never design the integration around the assumption that demographic information will always produce one record. Patient matching belongs in the domain layer of the integration, with explicit states such as matched, not matched, ambiguous and restricted. Downstream clinical actions should only run after the correct state has been established.

Once you have a Medicus patient identifier, determine exactly how much clinical information your product needs.

For structured GP record access, the Medicus care-record interface returns a FHIR STU3 Bundle and can include areas such as medication, allergies, consultations, problems, immunisations, investigations, referrals and other clinical information. Your application should not simply ingest everything because the endpoint makes it available. Parse and retain only the information required for the approved workflow.

Build a FHIR translation layer between Medicus and your internal data model rather than allowing the rest of your product to depend directly on the structure of the external Bundle. This gives you a clear boundary: Medicus-specific structures are translated into internal clinical objects, and your application operates on those objects.

The same principle applies when writing back.

If your workflow produces a simple narrative result, a clinical note may be sufficient. If it produces a measurable clinical value, use an observation where appropriate. If the output is a file or report, design around document filing. If the workflow represents a complete clinical consultation, consider creating an encounter rather than issuing several disconnected write operations.

The Medicus encounter capability is especially useful for products generating structured clinical outputs because an encounter can contain topics and headings, with observations, notes and documents nested beneath them. That allows your integration to write a coherent clinical consultation in an atomic operation instead of constructing the record through a sequence of independent calls.

Where coded clinical information is written into Medicus, create a terminology layer for SNOMED CT concepts. Do not scatter concept identifiers throughout application code. Store codes and their descriptions in a controlled configuration or terminology service, validate them as part of the product lifecycle, and make the clinical meaning visible to developers and clinical safety reviewers.

The result of this stage should be a precise contract between your product and Medicus: what you read, what you write, when you do it and which endpoint performs every action.

Planning a Medicus API integration? Start by defining the exact patient, clinical record, appointment or workflow actions your digital health product needs to perform. A well-scoped Medicus integration reduces unnecessary API permissions, simplifies NHS assurance, improves patient safety and makes it easier to build a secure, scalable GP clinical system integration.

2. Build Authentication, Tenant Management and Authorisation as Core Infrastructure

Do not bolt Medicus authentication onto individual API calls. Build it as a dedicated component of your integration layer.

Each Medicus Transactional API request carries a short-lived JWT signed by your application using RS256. Your private signing key remains under your control, while Medicus retrieves the corresponding public key from the JWKS endpoint you registered during onboarding.

The signing service should therefore have four responsibilities: securely access your private key, generate the appropriate claims, sign the token, and set the correct key identifier in the JWT header.

Tokens are intentionally short-lived. The expiry can be no more than 60 seconds after the issued-at time, so do not implement a conventional long-lived access-token cache. Generate a fresh token for each request or a tightly controlled short burst of requests.

A simple Node.js signing function can generate the short-lived RS256 JWT required for an application-restricted Medicus API request:

import jwt from 'jsonwebtoken';
import { readFileSync } from 'node:fs';

const applicationId = 'your-application-identifier';
const privateKey = readFileSync('private-key.pem');
const now = Math.floor(Date.now() / 1000);

const medicusJwt = jwt.sign(
    {
        iss: applicationId,
        sub: applicationId,
        iat: now,
        exp: now + 60
    },
    privateKey,
    {
        algorithm: 'RS256',
        header: {
            kid: 'your-key-id'
        }
    }
);

Keep signing out of browser applications and mobile clients. The private key should sit in secure server-side infrastructure, ideally backed by a managed secret store, key-management system or equivalent protected runtime. Client applications should call your backend, and your backend should communicate with Medicus.

You also need a publicly accessible JWKS containing your public signing keys. Design this for key rotation from the beginning. Assign every signing key a distinct kid, publish a replacement key alongside the existing key, begin signing with the new key only after it is available to Medicus, and leave the previous public key available during the transition period.

Medicus caches consumer JWKS data, so an immediate replacement of one public key with another can cause otherwise valid requests to fail while cached key material is still in use. Overlapping keys removes that avoidable failure mode.

Next, make the entire integration tenant-aware.

A Medicus practice is represented by a tenant, and the tenant identifier forms part of the Transactional API hostname. Your integration should therefore treat tenant context as a first-class attribute on every operation.

Do not allow arbitrary hostnames to be generated from untrusted user input. Maintain a controlled mapping between customers or practices in your platform and their validated Medicus tenant identifiers. Your API client can then construct the appropriate Staging or Production endpoint from trusted configuration.

Authentication also needs to accommodate two distinct modes.

Application-restricted calls are performed by the application itself. In this mode, the JWT subject is the application. Where the application is acting on behalf of a practitioner, Medicus supports actor information that can identify the practitioner and organisation involved. Build this attribution from trustworthy identity data rather than allowing arbitrary actor details to be submitted by a client.

User-restricted access works differently. The request represents a particular Medicus staff user. Before your application can make calls on that user’s behalf, an active authorisation must exist.

The standard flow redirects the user to Medicus, where they authenticate and approve access. Medicus then redirects back to your application with the username that should subsequently be used when generating user-restricted JWTs and an expiry time for the authorisation. The standing authorisation lasts for 12 hours.

This is not a traditional OAuth authorisation-code flow. Do not design a code-exchange or refresh-token mechanism that Medicus does not provide. Instead, securely associate the returned Medicus username and authorisation expiry with the relevant application session. When the authorisation expires, send the user through the Medicus authorisation process again.

Build your API client so authentication mode is declared by the operation rather than inferred dynamically. For example, your application’s internal definition for retrieving a care record might explicitly state that it requires user-restricted access, whereas another endpoint may be configured as application-restricted.

This approach prevents developers from accidentally calling an endpoint under the wrong identity model.

Remember that authentication, endpoint permission and tenant enablement are separate gates. A correctly signed JWT does not mean the application can perform every operation. Medicus grants individual endpoint-and-mode combinations, and the application must also be enabled by the relevant practice.

Represent these distinctions clearly in your error handling so support teams can tell the difference between an invalid token, an expired user authorisation, a missing endpoint permission and an application that has not been enabled by a practice.

3. Build a Clinical Integration Layer Rather Than Calling Medicus Directly From Product Features

Once authentication works, create a dedicated Medicus adapter or integration service rather than allowing different parts of your application to call the external API independently.

The adapter should own URL construction, JWT generation, HTTP calls, request validation, response parsing, FHIR translation, error normalisation, retry behaviour, logging and observability.

Product services should communicate in your own domain terminology.

Instead of application code saying “call Retrieve Care Record and inspect this FHIR path”, it should be able to ask the integration layer for the clinical information required by the workflow. Instead of constructing the Create Encounter request throughout your product, define an internal consultation object and translate it into the Medicus request at the boundary.

This makes testing substantially easier and prevents external API details from leaking across your codebase.

For record retrieval, normalise FHIR data carefully. Treat missing optional elements as normal rather than exceptional. Preserve clinically significant coding, dates, provenance and status values, while avoiding transformations that could change the clinical meaning.

For example, once your application has the Medicus patient identifier and an appropriately authorised JWT, a server-side integration can retrieve the patient’s structured GP care record like this:

const tenantId = 'your-tenant-id';
const patientId = '4d59c916-9726-11ec-af3f-0a58a9feac02';

const response = await fetch(
    `https://${tenantId}.api.england.medicus.health/transactional-api/retrieve-care-record`,
    {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${medicusJwt}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            patientId
        })
    }
);

if (!response.ok) {
    throw new Error(`Medicus request failed: ${response.status}`);
}

const careRecord = await response.json();

console.log(careRecord.resourceType); // Bundle

Documents should follow a similarly explicit pipeline. If your workflow lists patient documents before retrieving selected content, separate metadata retrieval from binary retrieval. Decode base64 document data only where the product actually needs the document, enforce permitted file sizes and formats within your own system, and avoid persisting copies unless that persistence is part of the approved use case.

For write-back, make operations idempotent at your own application level wherever the external workflow could be retried. A timeout can leave you uncertain whether a write succeeded. Your integration should record an internal operation identifier, request state and result so an engineer can determine what happened without simply issuing the write again.

Do not blindly retry clinical write operations. A generic retry policy that is appropriate for a GET request may duplicate notes, observations or documents when used for a POST whose original result is uncertain.

Use different policies for reads and writes.

Reads can usually use bounded retries for transient infrastructure failures. Writes should generally require a more cautious reconciliation strategy, based on the semantics of the specific action. If your application cannot establish whether a clinical write succeeded, surface that state rather than pretending the operation definitely failed.

Appointment integration should follow the same domain-first approach. Retrieve the relevant appointment metadata and practice sites, obtain available slots for the required period and filters, then use the returned slot identifiers and times when making a booking. Do not maintain your own independent representation of future availability and assume it remains bookable.

Treat booking as a transaction against current Medicus state. If the slot is no longer available, return the user to availability selection rather than attempting to force the previous choice.

When searching availability, divide longer product journeys into supported time windows rather than sending unconstrained searches. Cache relatively stable metadata separately from fast-changing slot availability.

For cancellations, capture the cancellation reason in your product before submitting the operation. For patient self-check-in workflows, use the eligible appointments returned for that patient rather than attempting to infer today’s check-in candidates entirely from your own appointment data.

The same design pattern extends to referrals, investigations and other workflow endpoints. Model the clinical workflow as a state machine. Retrieve the authoritative state, determine which transition is permitted, submit that transition and persist the result.

This is safer than treating the API as a collection of unrelated CRUD methods.

4. Engineer Webhooks, Errors and Data Handling for Production Conditions

A production Medicus integration needs to operate correctly when information is missing, an authorisation has expired, a patient cannot be matched, a practice disables your application, an API call times out or a webhook never arrives.

Design those conditions before deployment.

Medicus authentication and authorisation failures are rejected before an endpoint runs and commonly return HTTP 403 responses. Your integration should map these into meaningful internal categories rather than exposing raw external descriptions to users.

Endpoint-level failures have their own documented error codes. Branch on machine-readable error codes wherever possible rather than matching free-text descriptions.

Create a normalised integration error model such as:

  • authentication failure;
  • authorisation expired;
  • tenant not enabled;
  • endpoint not permitted;
  • patient not found;
  • patient match ambiguous;
  • patient restricted;
  • invalid clinical request;
  • external state conflict;
  • transient upstream failure;
  • and uncertain write outcome.

Your user experience can then respond appropriately. An expired user authorisation can trigger re-authorisation. An ambiguous patient match can request additional information. A restricted patient record can stop processing securely. A transient read error can offer a retry.

Webhook handling requires a deliberately different architecture from synchronous API calls.

Medicus can send signed webhook events for supported changes and user actions. The webhook body is a JWT rather than ordinary JSON, and your receiver should verify the signature against Medicus’s public keys before processing the event.

Using a JWT library such as jose, your webhook endpoint can resolve the tenant’s Medicus JWKS, verify the RS256 signature and validate the issuer before processing the event:

import { createRemoteJWKSet, jwtVerify } from 'jose';

async function verifyMedicusWebhook(rawJwt, tenantId) {
    const jwksUrl = new URL(
        `https://${tenantId}.api.england.medicus.health/transactional-api/jwks`
    );

    const medicusKeys = createRemoteJWKSet(jwksUrl);

    const { payload } = await jwtVerify(rawJwt, medicusKeys, {
        algorithms: ['RS256'],
        issuer: 'medicus'
    });

    return {
        event: payload.event,
        eventId: payload.jti,
        data: payload.data
    };
}

Cache Medicus JWKS data and refresh it when an unfamiliar kid is encountered. Validate the token’s issuer and expiry, then use its unique identifier for deduplication.

Most importantly, acknowledge webhooks quickly.

Do not perform lengthy clinical processing in the HTTP request that receives the webhook. Verify it, record or enqueue the event, return a successful response, and process the workflow asynchronously inside your own architecture.

Treat the webhook as a signal that something happened, not as your sole authoritative record of that event. Medicus webhook delivery is best-effort and failed deliveries are not automatically retried. If your product requires guaranteed state consistency, use the event to trigger retrieval of current data and provide an appropriate reconciliation mechanism for situations in which no webhook arrives.

This distinction is particularly important for products that react to a clinician opening a record or beginning a consultation. Your business logic should be capable of recovering its state from Medicus or your own persisted workflow rather than relying on a perfect sequence of events.

Apply the same discipline to API usage. Avoid unnecessary polling, repeated patient-record retrieval and duplicate requests. Cache only where your approved use case allows it and where the data can safely tolerate the chosen lifetime.

Never assume that because an API technically returns information, your application is automatically permitted to retain it. Decide for every data element whether it is required transiently, displayed, cached or persistently stored. Implement those decisions as technical controls rather than leaving them to developer judgement during feature development.

Logging should give you enough information to investigate integrations without copying unnecessary patient data into application logs. Record the tenant, endpoint, operation identifier, response status, latency, authentication mode and relevant non-sensitive correlation identifiers. Redact JWTs, private keys, document content, clinical free text and unnecessary patient identifiers.

Build dashboards around call volume, failure rates, latency, authorisation failures, webhook verification failures and write outcomes. This allows integration problems to be separated quickly from problems elsewhere in your product.

5. Test, Assure and Release the Medicus Integration in Controlled Stages

Build against Medicus Staging before introducing production practices. Keep environment configuration completely separate so a Staging tenant can never be accidentally directed towards the Production API and production credentials cannot leak into development workflows.

Begin with the simplest authenticated request. Configure your registered application identifier, JWKS and private signing key, generate a correctly formed short-lived JWT and call the Medicus ping endpoint. Confirm both application identity and any requesting context returned by the API before moving into clinical endpoints.

From there, test each integration workflow from beginning to end.

Do not limit testing to successful demonstrations. Build an unhappy-path suite deliberately. Expire tokens. Use an unknown key identifier. Remove an expected permission in a controlled environment where possible. Attempt a workflow after user authorisation has expired. Test no patient match and multiple patient matches. Test patients without the information required by the chosen endpoint. Send invalid coded entries. Attempt to book a slot that has become unavailable. Simulate timeouts immediately before and after a write.

For every scenario, verify three things: what your backend records, what the user sees and whether repeating the action can create an unsafe or duplicate clinical result.

Automate as much of this suite as possible. Contract tests should validate your assumptions about required and optional fields. Integration tests should exercise the actual Staging service. End-to-end tests should cover the real product journey through authentication, matching, retrieval and write-back.

Also build compatibility tolerance into response parsers. Do not make your application fail simply because an API response gains an additional field or an enumeration receives a value your current user interface does not recognise. Parse what you need, preserve safe defaults, log unexpected values and explicitly fail only where continuing could alter the clinical meaning.

Prepare assurance evidence at the same time as you build the integration rather than creating it retrospectively.

For a direct Transactional API integration, the Medicus onboarding process includes an integration request, partner agreement, Staging configuration, development and testing, assurance evidence, and demonstration of both successful and unsuccessful interaction paths before a controlled First of Type deployment.

Treat the First of Type as a production engineering stage, not simply a commercial launch milestone.

Instrument the pilot heavily. Confirm tenant configuration before go-live. Verify that the practice has enabled the application. Test authentication in the production environment with a non-destructive operation before allowing the complete workflow. Confirm support contacts and escalation paths. Monitor every integration operation closely during the first live usage.

Where your product writes into the clinical record, confirm that the resulting entries appear to practice users exactly as intended. Check coding, headings, dates, practitioner attribution, document presentation and clinical context. The API accepting a request only proves that the request was technically valid; your pilot should also prove that the resulting GP record is clinically usable.

After the first practice is stable, make practice onboarding configuration-driven. A new practice should require a validated tenant identifier, confirmation that the application has been enabled and any practice-specific workflow configuration your product legitimately needs. It should not require engineers to fork integration code.

Finally, maintain Medicus as an external dependency with its own lifecycle.

Track API changes, review the Medicus developer documentation regularly, keep your signing keys rotatable, preserve automated integration tests and maintain an inventory of the endpoint permissions your product actually uses.

When a new capability is introduced, return to the original workflow-first method. Define the clinical operation, identify the required Medicus endpoints, choose the correct authentication context, determine what information can be processed or retained, build the domain mapping, test failure conditions and take the change through the appropriate assurance process.

That approach produces an integration that can grow with the product without turning the Medicus interface into tightly coupled infrastructure spread throughout the codebase.

The most effective Medicus integrations are therefore built from the workflow inwards. Start with the exact clinical action your product needs to perform. Reduce it to the minimum required API interactions. Put authentication, tenant isolation and data translation behind a dedicated integration boundary. Treat clinical writes as auditable operations rather than ordinary HTTP requests. Use webhooks as triggers rather than guaranteed state. Design every failure path before production. Then take the complete workflow through Staging, assurance and a controlled First of Type deployment before scaling it across practices.

Following that sequence turns the Medicus API from a collection of endpoints into a maintainable integration layer that can support a digital health product through development, assurance, live clinical use and subsequent expansion.

Need help with Medicus integration?

Is your team looking for help with Medicus integration? Click the button below.

Get in touch