Software development best practices

12-Step Guide to Patient Access APIs with Azure Health Data Services

Time for reading: 16 min

Less than two years remain for healthcare payors to fully comply with CMS-0057-F (CMS Interoperability and Prior Authorization Final Rule). Among other things, this impressive acronym requires developing or improving Patient Access APIs. This guide will cover everything you need to know to comply.

Patient access APIs are pretty simple for teams with cloud-native expertise. The tricky part is mapping the data from all the legacy systems to appropriate FHIR resources, creating the logic for the FHIR transformation, and synchronizing the data properly across the systems.

This guide will cover these challenges in greater detail, so that you can avoid costly reworks.

Table of contents:

#1 Understand what the Patient Access API is all about

As covered in our CMS-0057-F guide, healthcare payors must provide patients with secure access to health data, including:

  • Claims, adjudicated claims, and encounters.
  • Lab results, medications, shots, and other clinical data.
  • Provider directory information.
  • Formularies and drug coverage information.
  • Prior authorization statuses and details.

Patient Access API is the standard way to provide this data.

Who needs a Patient Access API?

  • Medicare Advantage (MA) organizations.
  • State Medicaid fee-for-service (FFS) programs.
  • Medicaid managed care plans.
  • State CHIP FFS programs and CHIP managed care entities.
  • Qualified Health Plan (QHP) issuers on the Federally-Facilitated Exchanges (FFEs).

What are the deadlines?

  • January 1, 2026. The affected organizations must report Patient Access API usage metrics to CMS.
  • January 1, 2027. Provide patients with prior authorization information using the API within 1 business day of the decision.

Now, how do you implement a FHIR-compliant Patient Access API?

CMS-0057-f compliance timeline checklist

#2 Choose an optimal approach to compliance

There are several options for implementing a Patient Access API.

The most popular approach is using a plug-and-play solution like Azure Health Data Services. It is a fully-managed, secure, and scalable FHIR service that:

  • Acts as the Patient Access API and FHIR data store.
  • Supports FHIR R4.0.1+ and SMART on FHIR out of the box.
  • Provides built-in OAuth 2.0 / OpenID Connect integration.
  • Enforces access control and auditability.
  • Integrates with Azure-native tools for monitoring, compliance, and data ingestion.

The API itself supports custom extensions, token validation, and audit capture, making it suitable as the access layer for third-party apps. It is a fast and cost-effective way to meet CMS requirements for healthcare startups. However, it sacrifices some of the flexibility that larger organizations might want.

Another option is to use Azure as a stateless frontend, while the data stays in your internal systems. The API requests go through an internal microservice layer that transforms and fetches the data. This approach is more complex but offers rich customization, tight data governance, and minimal data duplication.

The last alternative is a custom pipeline that extracts data from internal systems, transforms it into FHIR resources, and loads it into a separate data store. This makes it easier for large enterprises to control data quality and compliance.

aa FHIR-compliant Patient Access API with MindK

This guide will follow the first approach, using Azure Health Data Services as a cloud-native FHIR Patient Access API and data store. With that out of the way, let’s define our scope.

#3 Identify the data for correct FHIR mapping

It’s crucial to establish what patient information your API will expose:

  • Clinical data (conditions, medications, procedures, observations).
  • Claims, coverage, and explanation of benefits (EOB).
  • Provider directories and formulary data.

This requires mapping data in existing systems to appropriate FHIR resources (like ExplanationOfBenefit, Procedure, or Condition).

People often underestimate this step’s complexity. Legacy data might vary in structures, terminology, and interpretations across clinical, billing, and administrative datasets. The team might have to iterate several times to reveal conflicting definitions or incomplete data in legacy systems.

To make things easier, we identify the data sources that need mapping and exposure:

  • Electronic Health Records (EHR).
  • Claims processing systems.
  • Eligibility and coverage databases.
  • Provider directories.

It’s best to start with data explicitly required by CMS-0057-F. Limiting this initial scope reduces the initial complexity. Common mappings for Patient Access API include:

Data categoryFHIR resource (recommended)
Patient demographicsPatient
Clinical conditionsCondition
Observations & lab resultsObservation
Medication orders/prescriptionsMedicationRequest, MedicationStatement
ProceduresProcedure
Claims and billing details*ExplanationOfBenefit
Insurance and coverage infoCoverage
Providers/cliniciansPractitioner, PractitionerRole
Provider organizationsOrganization, Location

*It’s important to use the ExplanationOfBenefit resource rather than Claim to represent processed claims. The latter is primarily intended for claim submissions, not finalized payment or adjudication records.

MindK recommends a structured workflow to map the data:

  1. Export realistic examples from each data source.
  2. Map each source data field explicitly to a specific FHIR resource and field.
  3. Validate mappings with subject matter experts to avoid costly mapping errors later.
  4. Document mappings source-to-target relations, and transformation rules.

Here’s how such a mapping documentation might look like on a real project. 

Source SystemSource TableSource FieldFHIR ResourceFHIR FieldNotes
EHR SystemPAT_INFOpatient_idPatientidentifier.valueUnique patient identifier
Claims DBCLAIMSclaim_amountExplanationOfBenefittotal.amount.valueFinal adjudicated amount
Pharmacy DBRXmedication_codeMedicationRequestmedicationCodeableConceptRxNorm codes preferred

This stage includes a few challenges and common pitfalls. Different systems use varying codes (e.g., local medication codes instead of RxNorm). Clinical data is often ambiguous or inconsistent.
A Business Analyst needs to confirm the clinical meaning and the correct corresponding FHIR resources. For instance, deciding between Observation and Condition often requires clinical judgment.

The data might also be incomplete, lacking key fields required by FHIR standards. This means that we need to define clear default behaviors or handling rules. For example, use DataAbsentReason extensions in FHIR to explicitly document why particular data elements are unavailable.

The step is complete once we have all the required CMS-0057-F data points explicitly mapped, consistent in terminology, and validated by clinical professionals. Next, we’ll have to create the logic for the FHIR transformation.

#4 Normalize the data to prevent any distortions

After clearly mapping your data, the next critical step is normalizing and transforming your existing healthcare data into fully compliant FHIR resources. IT ensures data from different sources:

  • Is converted into FHIR-standard vocabularies (SNOMED CT, LOINC, RxNorm, ICD-10).
  • Has no missing, ambiguous, or conflicting data.
  • Retains its clinical meaning post-transformation.
  • Now, how do we achieve this?.

Step 1: standardize your terminology

We need to replace or map proprietary terminologies into CMS-mandated standards, for example:

Source DataTarget Standard (FHIR)Example
Clinical DiagnosesSNOMED CT / ICD-10Proprietary codes → SNOMED CT codes
Lab ResultsLOINCLocal test codes → LOINC codes
MedicationsRxNormLocal drug identifiers → RxNorm codes

We need to create such a table early on, instead of embedding the mappings into the application logic directly. Terminologies evolve over time, which means frequent updates to the mappings.

Step 2: implement the logic for normalization and transformation

The next step is to use Azure Functions to build scalable normalization workflows. An example below shows the transformation of a clinical observation into a FHIR Observation resource:

FunctionName("NormalizeObservation")]
public static async Task<IActionResult> NormalizeObservation(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
ILogger log)
{
var inputJson = await new StreamReader(req.Body).ReadToEndAsync();
var sourceObservation = JsonConvert.DeserializeObject<SourceObservation>(inputJson);

// Normalize and map local lab code to LOINC
string loincCode = TerminologyMapper.GetLoincCode(sourceObservation.LocalTestCode);

if (string.IsNullOrEmpty(loincCode))
{
return new BadRequestObjectResult(CreateOperationOutcome(
"unknown",
$"Unable to map local test code '{sourceObservation.LocalTestCode}' to LOINC."));
}

var fhirObservation = new JObject
{
["resourceType"] = "Observation",
["status"] = "final",
["code"] = new JObject
{
["coding"] = new JArray
{
new JObject
{
["system"] = "http://loinc.org",
["code"] = loincCode,
["display"] = sourceObservation.TestName
}
}
},
["subject"] = new JObject
{
["reference"] = $"Patient/{sourceObservation.PatientId}"
},
["effectiveDateTime"] = sourceObservation.TestDate.ToString("yyyy-MM-ddTHH:mm:ssZ"),
["valueQuantity"] = new JObject
{
["value"] = sourceObservation.ResultValue,
["unit"] = sourceObservation.ResultUnit,
["system"] = "http://unitsofmeasure.org"
}
};

return new OkObjectResult(fhirObservation);
}

// Helper method for FHIR OperationOutcome error responses
private static JObject CreateOperationOutcome(string issueCode, string details)
{
return new JObject
{
["resourceType"] = "OperationOutcome",
["issue"] = new JArray
{
new JObject
{
["severity"] = "error",
["code"] = issueCode,
["details"] = new JObject
{
["text"] = details
}
}
}
};
}

Always include clear error handling (e.g., OperationOutcome) when normalization fails due to missing terminology mappings.

Step 3: handle incomplete or ambiguous data

In real-world healthcare datasets, there will be partial and unclear records. Misinterpretations can affect patient safety and compliance, so we need to confirm the intended meaning with clinicians as early as possible.

For missing values, we can document the data using FHIR’s built-in DataAbsentReason extension:

{
"resourceType": "Observation",
"valueQuantity": {
"extension": [{
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
"valueCode": "unknown"
}]
}
}

Step 4: validate transformed data against FHIR profiles

Before committing data to Azure Health Data Services, we need to validate it explicitly against the US Core Implementation Guide profiles. Tools like the official HL7 FHIR Validator or Firely FHIR Validator are of great help here.

#5 Synchronize the data to avert instability

One of the downsides of the cloud-native approach is that pushing source data into the FHIR service creates a parallel data store. Without synchronization, there’s no way to ensure that patient data remains accurate, timely, and consistent across systems.

Synch is a straightforward process on paper. However, real-world projects may present significant challenges:

  • Concurrent updates of patient data in multiple sources.
  • Event ordering to ensure data events arrive in a logical, time-consistent order.
  • Idempotency to prevent data loss or corruption during duplicate or replayed synchronization events.
  • Resilience and fault-tolerance for reliable data flow despite failures.

One way to tackle these challenges is to use an event-driven messaging infrastructure. We’ll use Azure Service Bus for guaranteed message delivery, durability, ordering, and built-in support for idempotent processing.

Step 1: provision Azure Service Bus resources

  • Create a Service Bus namespace dedicated to healthcare synchronization.
  • Create queues or topics/subscriptions for each primary data type (patients, claims, observations).

Step 2: publish messages from source systems

Each time normalized data is ready, publish it reliably to Azure Service Bus:

public async Task PublishPatientUpdateAsync(PatientUpdate patientUpdate)
{
var client = new ServiceBusClient("<connection_string>");
var sender = client.CreateSender("patient-updates");

var messagePayload = JsonConvert.SerializeObject(patientUpdate);

var message = new ServiceBusMessage(messagePayload)
{
MessageId = patientUpdate.EventId, // critical for idempotency
SessionId = patientUpdate.PatientId, // ensures event ordering per patient
ContentType = "application/json"
};

await sender.SendMessageAsync(message);
}

We recommend setting the MessageId explicitly to ensure idempotency and use SessionId for strict event ordering for the same patient’s data.

Step 3: consume and store messages in Azure FHIR Service

We’ll use Azure Functions to consume messages and write them reliably into Azure Health Data Services:

[FunctionName("SyncPatientUpdates")]
public static async Task Run(
[ServiceBusTrigger("patient-updates", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message,
ILogger log)
{
var payload = message.Body.ToString();
var patientUpdate = JsonConvert.DeserializeObject<PatientUpdate>(payload);

using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "<FHIR_API_ACCESS_TOKEN>");

var fhirPatient = TransformToFhirPatient(patientUpdate);
var fhirPatientJson = JsonConvert.SerializeObject(fhirPatient);

var response = await httpClient.PutAsync(
$"https://<your-fhir-service>.azurehealthcareapis.com/Patient/{patientUpdate.PatientId}",
new StringContent(fhirPatientJson, Encoding.UTF8, "application/fhir+json"));

if (!response.IsSuccessStatusCode)
{
log.LogError($"Failed to sync patient: {patientUpdate.PatientId}. Response: {response.StatusCode}");
throw new Exception("FHIR sync failed"); // trigger retry
}

log.LogInformation($"Successfully synced patient: {patientUpdate.PatientId}");
}

private static JObject TransformToFhirPatient(PatientUpdate update)
{
return new JObject
{
["resourceType"] = "Patient",
["id"] = update.PatientId,
["name"] = new JArray(new JObject
{
["use"] = "official",
["family"] = update.LastName,
["given"] = new JArray { update.FirstName }
}),
["birthDate"] = update.DateOfBirth.ToString("yyyy-MM-dd")
};
}

Step 4: ensure idempotency

Synchronization must handle duplicate messages gracefully:

  • Define a unique event ID (MessageId) for each message (e.g., source system timestamp + record ID).
  • Check event IDs or versions before processing updates (if applicable).

Use FHIR conditional updates to prevent unwanted duplicates:

PUT [base]/Patient?identifier=system|identifier-value

Step 5: handle data concurrency and ordering

Enforce message ordering per patient using Azure Service Bus sessions (SessionId) . We can also manage data versioning explicitly in FHIR (meta.versionId) if concurrent updates frequently occur.

#6 Set up authorization, authentication, and consents

Securing your Patient Access API means clearly managing authentication (identity), authorization (permissions), and patient consent.

Our Azure stack will do most of the heavy lifting. Azure Entra ID provides secure patient identity management with built-in OAuth 2.0 and OpenID Connect. It’s HIPAA compliant by default and aligns with SMART on FHIR standards.

CMS mandates explicit management and clear documentation of patient consent, which is easy with the FHIR Consent resource.

 

#7 Make your error handling patient-centric

Patients should never feel frustrated or confused about what to do. Error messages need to be clear, actionable, privacy-sensitive, and accessible to users with disabilities. Here’s what it means in practice:

  • Clarify the steps a patient can take to resolve the issue.
  • Embed links or buttons for common actions.
  • Provide intuitive, self-service troubleshooting for minor issues without.
  • Include simple escalation paths for patients who can’t solve issues themselves.
  • Reassure patients that no personal health data was exposed.
  • Use audio descriptions, easily readable fonts, and high-contrast UI elements.
  • Ensure compatibility with screen readers or accessibility tools.
  • Issue SMS/email updates for persistent or system-wide issues.
Bad Good 
not-foundWe couldn’t find your records. Please check your insurance info or try again later.
forbiddenYou’re not authorized to access this data. Try logging in again or contact support.
invalidSomething went wrong. Please verify your information or contact our help team.

FHIR provides the OperationOutcome resource for structured error responses. However, those messages are usually of a technical nature. You may use two key fields to include both technical and patient-friendly messaging:

  • details.text for technical explanation for developers or logging
  • diagnostics for human-readable explanation

Those are mandatory requirements. We can then add some optional enhancements like returning error messages in the patient’s preferred language via the Accept-Language header. You might also want custom application-level codes in an extension for traceability, as well as log each error (excluding PHI) for review and QA.

#8 Test and validate the Patient Access API

It’s essential to ensure your Patient Access API is CMS-0057-F compliant, FHIR-conformant, and reliable in real-world usage. Thorough testing and validation will help you catch regulatory, interoperability, and data integrity issues before they cause real harm.

Test typeDescriptionRecommended tools
FHIR Conformance testingValidate structure and compliance of FHIR resources (profiles, extensions, terminology bindings)HL7 FHIR Validator, Inferno
API Contract testingValidate all FHIR endpoints return expected data under different query patternsPostman + OpenAPI specs
SMART on FHIR Flow testingEnsure OAuth 2.0/OpenID Connect flows, scopes, and access tokens behave as expectedSMART App Launch Tester
Security testingConfirm token validation, unauthorized access denial, and consent-based access restrictionsCodeQL, GitHub security, JMeter
Performance testingValidate response times under concurrent requests, API quotas, and retry behaviorsJMeter or Azure Load Testing

#9 Expose the FHIR API endpoints securely

Once your data is mapped, normalized, transformed, and synchronized into Azure Health Data Services, you must expose it to third-party applications via a secure FHIR API.

We’ll use Azure API Management (APIM) as a secure gateway that:

  • Controls access to your Azure FHIR API
  • Enforces rate limiting, quotas, and scopes
  • Provides developer onboarding and interactive docs.
  • Logs access for monitoring and audit.

Our first step is to import Azure FHIR API into API Management. Use the OpenAPI specification for the Azure FHIR service (/metadata endpoint). Then, configure APIM to forward requests securely to the underlying Azure Health Data Services instance.

The next step is to secure the exposed API by enforcing OAuth 2.0 with SMART on FHIR scopes like patient/Observation.read, patient/Condition.read, and launch/patient, offline_access.

We’ll also need to integrate APIM with your Azure AD tenant and use Application Insights or custom policies for audit logging. Apply policies in APIM to:

  • Limit request rate (e.g., 1000 requests/hour per client).
  • Detect and reject malformed FHIR queries
  • Reject overly broad or unsafe search parameters.
<rate-limit calls="1000" renewal-period="3600" />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" require-scheme="Bearer">
<openid-config url="https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration" />
<required-claims>
<claim name="scp">
<value>patient/*.read</value>
</claim>
</required-claims>
</validate-jwt>

How does throttling work

#10 Implement monitoring and observability

Monitoring and observability are essential to meeting CMS-0057-F, HIPAA, and SMART on FHIR requirements. It’s a pretty complex topic that requires its own article to do justice. In short, we need to:

  • Detect and alert on failures (availability, authentication, data sync issues).
  • Track access to patient data (audit logging for compliance).
  • Identify abusive usage or misconfigured third-party apps.
  • Provide telemetry to support future improvements.
AreaMetrics & events to monitor
AvailabilityAPI uptime, request success/failure rates, latency, retries.
SecurityUnauthorized requests, invalid tokens, consent violations, throttling.
FHIR-specific accessRead/write activity per resource type (e.g., Patient, EOB, Observation).
Audit trailWho accessed what data, when, and under what consent.
Rate limitingRequests per user/app; 429 (Too Many Requests) responses.
Transformation errorsFailed data sync, normalization mismatches, bad FHIR payloads.

You can use Azure Monitor + APIM for real-time metrics and alerting, and log FHIR AuditEvent resources to meet compliance requirements. It’s also important to have proactive alerts on anomalies as opposed to reactive-only alerts. Remember, a well-monitored Patient Access API is not just technically sound; it’s legally defensible and trusted by partners.

#11 Publish interactive docs for developers

CMS-0057-F doesn’t just mandate that the API be available; it must also be usable, securely accessible, and well-explained. Therefore, we need to provide external developers with all the information needed to:

  • Authenticate using SMART on FHIR flows.
  • Discover available FHIR resources and query parameters.
  • Understand usage limits, scopes, and patient access behavior.
  • Test and troubleshoot their integration easily.

Azure API Management (APIM) Developer Portal is our preferred way to provide these resources via OpenAPI or FHIR metadata.

#12 Maintain the API usability and compliance

You now have a secure, scalable, developer-friendly, and CMS-compliant Patient Access API. Ensuring it stays this way in the future requires ongoing attention.

The first part of these efforts is to track the evolving CMS and FHIR standards. Subscribing to CMS Interoperability Updates, HL7 Announcements, and the FHIR Dev Forum helps keep up with these changes.

The second part is improving the user experience. For this reason, both the developer portal and patient-facing apps must include clear feedback mechanisms. When handling such feedback, prioritize complaints about error messages, latency, and inconsistent data availability.

When publishing new versions of the API, include the appropriate URL request headers (/v1/fhir/, /v2/fhir/) and use semantic versioning for breaking vs. non-breaking changes.
Finally, periodic regression testing is a must-have, especially when updating terminology mappings, consent, or authorization logic.

Conclusion

Exposing a Patient Access API is one of the key requirements in CMS-0057-F. Most of the steps in this guide are pretty straightforward. However, data mapping, normalization, and synch are often quite tricky on real-world projects. Moreover, failures in these areas go unnoticed unless you have solid monitoring and observability.

That’s why implementing a Patient Access API requires solid business analysis, planning, and budgeting. Above all, it requires technical expertise. At MindK, we have vast experience with FHIR, healthcare integrations, and interoperability. Don’t hesitate to contact us if you have any questions or need help with CMS-0057-F compliance.

choose Mindk as your tech partner

 

Subscribe to MindK Blog

Get our greatest hits delivered to your inbox once a month.
MindK uses the information you provide to us to contact you about our relevant content andservices. You may unsubscribe at any time. For more information, check out our privacy policy.

Read next

Agile best practices

10 Agile Best Practices for Remote Teams: Recalibrate Your Processes and Improve Efficiency

Read more
Low-code SaaS development

Low-Code SaaS Development: How to Accelerate the Speed of Development and Improve Productivity

Read more
The surprising benefits of IT Managed Services

The surprising benefits of IT Managed Services

Read more