NHS Booking and Referral Standard

Guide v1.0.0 | Core v1.0.0

BaRS Core

BaRS consists of BaRS Core that provides a core set of functionality and BaRS Applications that provide distinct functionality for each use case.

You will find here a set of documentation, specifications and services that describe and support all the fundamental components of the standard that are always the same for all use cases or care journeys. Examples include the underlying capabilities and patterns and transport layer elements such as security, authorisation and access control.


End to end workflow

This section covers the core elements of workflow outlined within the Booking and Referral Standard. There are two caveats when reading this:

  • Workflow is dependent on its application or use case, for example in 999 - CAS validation, there is no step to perform a booking. See the relevant use case page in the BaRS Applications section.

  • The order of workflow is interchangeable. It is possible to:

    • make a referral before a booking
    • refer without booking
    • book without referring

Service discovery

For a sender making a booking or referral, the first step is to establish a service (including the identifier) to send to. This can be achieved by using national or local service directories or preconfigured services, where a sender only ever sends to a handful of endpoints. The service identifier is all the sender requires to engage with a service which supports BaRS. Contact us if you need further information about service discovery.

Authenticate with BaRS

The sender must have already connected with our platform in order to generate an access token to make a request of the BaRS API. The sender generates and signs a JWT and sends this request to the BaRS token endpoint which provides an access token to be used to interact with the BaRS API. A receiver will follow the same process to interact with BaRS API when providing feedback to an original sender, for example. The sender and receiver switch roles in the workflow for referral in both the current first-of-type use cases. This is only relevant to some applications and will be detailed in the specific application guide.

BaRS FHIR API

The BaRS FHIR API supports all functionality with receiver APIs for both booking and referrals. Once an access token has been obtained, the sender can start making requests of the API.

Note: With every API request the sender must include the HTTP header parameter 'NHSD-Target Identifier'.

You can find details for each endpoint in the API Specification.

HTTP header

The BaRS API specifies several additional headers, many of which are items described in a standard Base64 encoded object (JSON). Their purpose and usage are described below. Examples can be found in the API Specification.

Routing

Header Requirement Description Value
NHSD-Target-Identifier Required by the sender Allows BaRS to route the message to the appropriate endpoint Base64 encoded JSON object

Authentication and Authorisation

Header Requirement Description Value
Authorisation Required by the sender and not forwarded to the receiver Will contain a token obtained from the relevant OAuth endpoint for the BaRS API being called. This token facilitates authentication with the API. String representing a token
NHSD-End-User- Organisation Optional for the sender and forwarded to the receiver For authorisation purposes this header contains information about the organisation making the request. Can be used by the receiver to impose access control limitations and should be retained for auditing purposes. Base64 encoded object based on a FHIR Organization resource
NHSD-Requesting-Person Optional for the sender and forwarded to the receiver For authorisation purposes this header contains information about the individual initiating the request itself. This can be used by the receiver to impose access control limitations and should be retained for auditing purposes. Base64 encoded object based on a FHIR Person resource
NHSD-Requesting-Practitioner Optional for the sender and forwarded to the receiver For authorisation purposes this header contains information about the healthcare professional making the request. Can be used by the receiver to impose access control limitations and should be retained for auditing purposes. Base64 encoded object based on a FHIR Practitioner resource
NHSD-Requesting-Software Optional for the sender and forwarded to the receiver For authorisation purposes this header contains information about the application making the request. This can be used by the receiver to impose access control limitations and should be retained for auditing purposes. Base64 encoded object based on a FHIR Device resource

Transactional integrity

Header Requirement Description Value
X-Request-ID Required for sending of any request and forwarded to the receiver Will facilitate transactional integrity and the ability to audit messages UUID
X-Correlation-Id Required for sending of any request and forwarded to the receiver Will facilitate transactional integrity and the ability to audit messages UUID

We expect receivers to use and store the header values in the following ways:

  • to ensure the message maintains transactional integrity and hasn't been received previously
  • to check who has sent the request and apply access control if desired
  • to ensure all interactions are auditable

If at any point the request fails, the receiver must send an appropriate error response (see Error handling) back to the sender to inform them why the request cannot be fulfilled. It is important to return as accurate information in the response as possible so the sender knows what is happening and can act accordingly. Transparency in the workflow is essential to enable senders and receivers to work together to provide the best patient experience possible.

HTTP Response Headers

Responses to requests in the BaRS standard need not include anymore than Headers required by the HTTP protocol and the Transaction Integrity Headers listed above. Senders should not expect or require anymore than this in a synchronous HTTP response.

For secure usage of HTTP headers, guidance can be found in the OWASP Secure headers project regarding the security headers and prevention of information disclosure which can be leveraged.

Processing requests

For GET requests (reads) the receiver honours what is being asked for by generating and synchronously sending a response, for example the server Capability Statement on a GET /metadata request. Any failure during processing must initiate an error response back to the Sender.

The $process-message endpoint on the implemented API supports a mix of RESTful and Messaging exchanges, as required by BaRS. The main benefit of introducing messaging is to allow a sender to package up the information a receiver needs, to process a particular request, but letting them handle it as necessitated by their system, rather than a sender having to make numerous requests to REST endpoints to perform the same action.

When a request is received against the $process-message endpoint, the MessageHeader resource must be examined to determine what process the sender is expecting the receiver to perform. There are three key elements to determine this -

  • Event - primary function being asked to perform, for example: booking-request or servicerequest-request
  • Reason - operation, for example:. New or Update
  • Focus - the key resource in the bundle, to be read first to make sense of other related resources

The receiver interprets the request, engages the processing and synchronously feeds back a response.

Responses

In the BaRS workflows responses are an important step and likely to become more so as use cases offer up increasingly complex requirements, for example: negotiated referrals.

Reversing roles

When responding the sender and receiver roles are reversed. Here you are essentially building a sender, and still need to implement a $process-message endpoint. Similarly, a receiver needs the ability to send the response to the BaRS API and will need to register with our platform.

Asynchronous workflow

The FHIR $process-message function supports 'async' parameter but BaRS does not employ this, all $process-message requests are synchronous.

However, BaRS does support request-response loops which are asynchronous. An example of this is the Safeguarding DNA response workflow; the sender (NHS 111 Telephony service) sends a referral with a safeguarding concern flag to an Emergency Department, the patient subsequently doesn't attend (DNAs). The Emergency Department receiver is now responsible for sending a response back to the original sender (the NHS 111 Telephony service) to inform them of the DNA.


Core functionality requirements

BaRS should provide different functionality depending on:

This list of functionality will expand in later versions of BaRS.

There are requirements in each of the central areas of functionality which every BaRS application must adopt:

All

  • Provide Capability Statement
  • Read and interpret Capability State
  • Provide Message Definition(s) for a specific service
  • Read and interpret Message Definition(s) for a specific service

Booking Sender

  • Request Slots for a specific service
  • Make Booking request
  • Cancel Booking request
  • Make Rebook request (two open Bookings during this routine)

Booking Receiver

  • Provide Slots for a specific service
  • Create Booking
  • Cancel Booking
  • Accept Rebook request (two open Bookings during this routine)

Referral Sender

  • Make Referral request
  • Cancel Referral request
  • Re-request Service Request (revoke current open Referral prior to sending new. Only one active/open Service Request during this routine)

Referral Receiver

  • Create Referral
  • Cancel Referral
  • Accept re-request Service Request (revoke current open Referral prior to sending new. Only one active/open Service Request during this routine)

BaRS FHIR usage

BaRS uses FHIR to achieve interoperability between healthcare IT systems. This section explains how BaRS makes use of some key FHIR concepts which need to be understood by developers implementing the standard.

Frameworks

A mix of data exchange frameworks are employed by BaRS to support different workflows.

REST

FHIR is often associated with REST (REpresentational State Transfer) as a primary method of exchange. BaRS uses REST for many of the benefits it offers -

  • allows independent modular development of client and server APIs (they don't need to know about each other before exchanges)
  • easy to integrate because of common agreed standards
  • scalability in developing and performance

In BaRS, simple CRUD operations on single resources is used for discrete atomic functions, such as retrieving a known booking or referral entity.

FHIR Operations framework

Performing the required steps in workflow using simple CRUD operations on single resources can be complex and relies on prior knowledge or dictates explicit rules around workflow in any given system, for example: a patient must exist before a service request can be made. In order to support complex workflows that utilise composites of resources, simple CRUD operations on sinlge resources would breach some core principles of ReST (e.g. inabililty to maintain stateless communication for example). Therefore BaRS utilises the FHIR operations framework which:

  • offers a degree of autonomy to the receiver of the communication in how they process for their system
  • is designed to support events, triggering activity between systems, supporting the response workflow requirements identified by many of the BaRS use cases, for example: 999 to CAS Validation Request/Response.

$process-message

This specific FHIR operation enables the exchange of complex composites whilst maintaining the principles of REST. The $process-message endpoint will be called at various stages in any of the workflows outlined to fulfil requests such as:

  • 'create a booking'
  • 'process a referral for validation'
  • 'validation outcome response'

The endpoint receives only POST requests of bundle type 'message', with the required MessageHeader resource dictating how to process and what is deemed the 'focus' (key Resource) of the request. The sender packages up this content, POSTs to the receiver and lets them decide how to consume and process the bundle.

You must implement a $process-message endpoint to be compliant with BaRS because it is used for initial requests (booking, service request etc.) but also for responses (validation outcome response etc.).

Bundle

The use of the FHIR bundle in BaRS primarily supports the use of $process-message. It is also used as follows:

  • When $process-message is initiated, a sender packages up numerous FHIR resources (MessageHeader, Patient, ServiceRequest etc.), the bundle will include everything needed by a receiver to process the request being made.
  • The $process-message endpoint is capable of receiving different types of request. The sender indicates the the action required by specifying in the MessageHeader resource, for example: booking or servicerequest, the operation, whether it be 'new' or 'update' and central FHIR resource from which to make sense of the bundle, the 'focus'.
  • A bundle type of 'searchset' is also used in BaRS when a receiver responds to a senders request for available slots, in the booking workflow. A receiver responds by bundling FHIR resources (HealthcareService, Schedule, Slot(s) etc.) related to particular HealthcareService which a sender will unpack and process to display service availability to a user.

Journey ID

The Journey ID uses the episodeOfCare resource.

Each Encounter between systems can reference the same originating episodeOfCare, allowing grouping of all the Encounters to one flow.

The FHIR episodeOfCare resource would be created at the beginning of the patient journey when the Encounter is created and leads onto a ServiceRequest or other appropriate flow.

This snippet of coding shows the episodeOfCare within the Encounter referencing a GUID that will be used throughout the patients journey.

<episodeOfCare>
    <!--      Resource reference to an EpisodeOfCare Journey ID      -->
    <reference value="d877b820-e72b-44d1-a627-195f54bfc606" />
</episodeOfCare>

LastUpdatedDate

Every resource will include a lastUpdated date in the meta tag. This is to be used for tracking and updating.

<meta>
    <versionId value="1.0.0-alpha" />
    <lastUpdated value="2021-11-26T15:00:00+00:00" />
    <profile value="https://fhir.nhs.uk/StructureDefinition/BARSBundleMessage" />
</meta>

How to handle times

  • All times MUST be in FHIR Instant format (YYYY-MM-DDThh:mm:ss.sssssss+zz:zz)
    • e.g. YearMonth-DayTHours:Minutes:Seconds.miliseconds+OffsetFromUTC
    • e.g. 201502-07T13:28:17.2398742+02:00
    • except where specifically defined otherwise
  • All times SHOULD be converted to UTC from local times (if not stored locally in UTC) before being included in any messages, this means the offset should be zero
  • When receiving a time in a message a system MUST expect and handle a non UTC time (e.g. with a non-zero offset)
    • Therefore if a time is received that is not UTC, the receiver should convert the time back to UTC using the offset given (or to their local time, if storing times in a local time)

Security and authorisation

Sender

The BaRS standard uses an application-restricted security model and security pattern as opposed to a user-restricted security model. This means the application is authenticated as opposed to the end-user using it. The high level steps for a sending application are defined below:

  1. The end user launches the calling application
  2. Time passes, until the user needs to interact with BaRS
  3. The calling application generates and signs a JWT, using its own private key
  4. The calling application calls our OAuth 2.0 token endpoint with the signed JWT. In particular, this uses the OAuth 2.0 client credentials flow
  5. We check the signature against the application's public key and, if valid, return an access token to the calling application
  6. The calling application calls the BaRS API, including the access token
  7. The BaRS API allows the interaction should the access token be valid

OAuth Endpoints

Environment Endpoint Usage/Availability
Sandbox https://sandbox.api.service.nhs.uk/oauth2 Authentication is not required in the Sandbox environment.
Development https://dev.api.service.nhs.uk/oauth2 Limited to specific APIs
Integration test https://int.api.service.nhs.uk/oauth2 All APIs
Production https://api.service.nhs.uk/oauth2 All APIs

Receiver

BaRS will utilise TLS-MA (Mutual Authentication) to communicate with receiving endpoints. Receiving endpoints will require a certificate under the NHS Root CA to facilitate TLS-MA.

As the certificates are using the NHS Root CA, fully qualified domain name (FQDN) must be an nhs.uk address, this is the case for both INT and Prod

You need to apply for your domain, ensuring that you complete 'Section 5: For website or application records visible on public internet'.

In production there is a naming convention that must be adhered to. In INT the naming convention should be adhered where possible. In the context of BaRS an example will be:

  • BaRS-< ODSCode >.< OrganisationName >.nhs.uk , BaRS is always the application name.

Once you have you have your domain registered you can then begin the process to obtain your certificate by generating a certificate request.

Certificate requests will need to be signed for your endpoint. Note that the FQDN is equal to the certificate name (CN) by convention.

At this point you should have a .key and a .csr files. The next step will be to send the .csr file to be signed by the NHS and get the client certificate.

  • If your client certificate will be implemented in any PTL environment then you should send the .csr file to itoc.supportdesk@nhs.net and they will reply with the certificate (.crt file)

  • If your client certificate will be implemented in the PROD environment then the .csr file needs to be send to the DIR team at dir@nhs.net and they will take care of issuing the certificate after validating your request with the Live Services Pipeline at liveservices.gate@nhs.net

Authorisation

BaRS will use several HTTP headers to facilitate authorisation. Although optional, receivers can expect these to be present for each request (excluding /metadata and /MessageDefinition), where applicable, to enforce access control. These will be in addition to the X-Request-ID and the X-Correlation-ID headers. If the information in these headers is available in the sending system, they'll be included in the request.

Each of these HTTP headers are objects and will therefore need to be added in the form of a standard Base64 encoded string.

Examples of each HTTP header item can be found within the API Specification.

HTTP Header Purpose
NHSD-End-User-Organisation This will represent the organisation making the request. It Shall include the ODS code and the human readable name of the organisation making the request. The object is based on a FHIR organisation resource.
NHSD-Request-Person This entry will represent the Person sending the request.
NHSD-Requesting-Practitioner This entry will represent the Practitioner associated to the request. It shall include their sds role id and a SNOMED code related to their role where applicable. The object is based on a FHIR Organisation resource.
NHSD-Requesting-Software This entry will represent the Software or application making the request. It shall include an identifier for the instance, the product name and its version. The object is based on a FHIR Device resource.

Error handling

There are multiple points where an error may occur to prevent booking and referral operations from completing successfully. This section provides error handling guidance for BaRS and its associated API. For More Detail on error handling, there is specific information on failure scenarios available in the Failure Scenarios section in addition to information included on this page.

Overview

Error codes (HTTP Response codes) can be caused by any of the three parties involved in a transaction using the Booking and Referral API. Error codes will be accompanied by a OperationOutcome FHIR resource where possible. Error codes and their relative operation outcome codes are meant for developer use only - they should not be presented to end-users. Instead, sending applications should interpret the two codes and provide user-friendly resolution steps on failure. Sender and receiver systems should record the detailed error codes in local logs in order to support service incident investigation. There are scenarios where an operation outcome may not be applicable or available. A HTTP Response code should always be returned.

Operation Outcome Codes are prefixed with an indicator of the source of the error help identify where a problem may be.

The following table provides a high-level overview of each stage in the booking and referral process from the perspective of a sending application.

Interaction Error Handling Overview Operation Outcome Prefix
Sender querying a Service Discovery tool As an existing solution, the DOS APIs return a number of significant errors specific to the actual API failing. These are all self-contained in that process. These are documented in a link below and will not affect the ongoing activity unless the data returned in the query is erroneous and then cause the next stages of the process to either fail or not be possible. N/A
Sender interactions with the BaRS API Interactions with the BaRS API may fail due to, but not limited to, the following reasons: The service being unavailable to the sender, the Sender being unauthenticated, the Sender reaching or exceeding a rate limit, or badly formed requests being sent. SEND
BaRS Service internal interactions Depending on the operation requested, internal interactions may fail within the proxy due to non-existent resources, service failures or data related issues within the Senders payload. PROXY
BaRS interaction with the Receiver In the event BaRS successfully proxies a request through to a Receiver, the Receiver may still respond with an error due to authentication, authorization or data related issues within the Senders payload, assuming the endpoint is available. REC

HTTP response codes and operation outcomesBaRS interactions (sending)

In the event of an error; the BaRS API always responds with an HTTP response code (issue.code) and an OperationOutcome code (issue.details.code) within an OperationOutcome FHIR Resource - conditioned on it being reached successfully. Assuming the sender's access token (obtained from the Oauth 2 endpoint) is accepted and the request is valid, the BaRS API processes the request. Depending on the operation, BaRS proxies the request to the receiver. Depending on where in this flow the error occurs, the OperationOutcome codes is prefixed with one of the following three items, indicating where the cause of the error resides:

  • SEND - The sending application is the cause of the error
    • These items are only be generated by the API to indicate there was an issue with the senders request
  • PROXY - The BaRS Service is the source of the error
    • These items generally generated to indicate there was an error internal to BaRS
    • The information within the response will help identify the cause
  • REC - The receiver is the source of the error
    • These are generated by the receiver, however if there is a scenario where the receiver cannot respond, these are generated by the proxy to clarify the source of the problem
    • The information within the response helps identify the cause

These OperationOutcome codes are part of a value set to be used within a OperationOutcome FHIR Resource (issue.details.code) which forms the body of the response. Below is an example of an error response:

OperationOutcome Example

{
  "resourceType": "OperationOutcome",

  "id": "4e2e13af-3bc7-4de3-8cc5-ea4f14d45ef8",

  "meta": {

    "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "invalid",

      "details": {

        "coding": [

          {

            "system": "https://fhir.nhs.uk/CodeSystem/http-error-codes",

            "code": "PROXY_BAD_REQUEST",

            "display": "400 - PROXY_BAD_REQUEST"

          }

        ]

      },

      "diagnostics": "BaRS encountered a conflict: <further diagnostics information, error message/error text>"

    }

  ]

}

Diagnostics text

The purpose of the two code values are to give high level information as to the nature and source of an error response. The diagnostics text is to contain clear and concise information regarding the exact cause of the problem encountered, where possible. This should include information on the problem encountered, internal debug details such as error message and texts are encouraged, exposing stack traces is not. The diagnostics field does not include any patient identifiable information at any time.

Example errors

The table below contains some further examples of error codes, their associated Operation Outcomes codes and and potential diagnostics texts.

HTTP Code Code Value Description Example diagnostics text
400 SEND_BAD_REQUEST The API was unable to process the request. "The API was unable to process the request: <further diagnostics information, error message/error text>"
REC_BAD_REQUEST The Receiver has responded stating the message was malformed. "<Receiver Identifier (ODS)> was unable to process the request: <further diagnostics information, error message/error text>"
PROXY_BAD_REQUEST Though Unlikely, BaRS, having accepted the message; has received a 400 response from an internal component. "BaRS was unable to process the request: <further diagnostics information, error message/error text>"
404 PROXY_NOT_FOUND The resource was not found within BaRS. "The resource was not found within BaRS: <further diagnostics information, error message/error text>"
REC_NOT_FOUND The resource was not found at the Receiver. "<Receiver Identifier (ODS)> encountered a conflict: <further diagnostics information, error message/error text>"
409 REC_CONFLICT Example: Sender is trying to Book an appointment in a slot that doesn't allow booking. "<Receiver Identifier (ODS)> encountered a conflict: <further diagnostics information, error message/error text>"
501 SEND_NOT_IMPLEMENTED The Request was not recognized. "The Request was not recognized by the API: <further diagnostics information, error message/error text>"
REC_NOT_IMPLEMENTED The Receiver did not recognize the request. "The Request was not recognised by the Receiver - <Receiver identifier (ODS)>: <further diagnostics information, error message/error text>"
PROXY_NOT_IMPLEMENTED BaRS did not recognize the request. "This Request was not recognized by the proxy: <further diagnostics information, error message/error text>"

Sender responsibilities

  • Log errors returned for incident investigation by IT support staff
  • Ensure sufficient information for appropriate local incident management is captured
  • Communicate with receivers should an unexpected error be received consistently (REC)
  • Communicate with NHS Digital should a persistent error state be encountered from BaRS (PROXY)
  • Inform end-user with a suitable message appropriate to the business flow, for example: critical error with advice to call local IT helpdesk, or business process options to warn users to choose another service

BaRS interactions (receiving)

Receiver responsibilities

  • Receiving APIs should adhere to the HTTP Response code and Operational Outcome code paradigm described above in the event that an error response needs to be returned
  • The sender should be able to ascertain what went wrong and why in the event of an error
  • Receiving endpoints should use the REC prefixed Operation Outcome codes and provide clear and concise diagnostics texts
  • There should be no need to omit or use a different prefix when responding to requests
  • There will be times where it is not possible to generate a response in the above format
  • In the event this occurs, a relevant HTTP response code should be the minimum
  • Log errors locally for incident investigation by IT support staff
  • If the request is problematic, this should be logged specifically as a sender system issue
  • Details of the sender system should be logged to support investigation
  • As a minimum, the Operation Outcome must include:
    • The HTTP Response code
    • The Operation Outcome code
    • Clear and concise diagnostics information
{

  "resourceType": "OperationOutcome",

  "id": "4e2e13af-3bc7-4de3-8cc5-ea4f14d45ef8",

  "meta": {

  "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "not-found",

      "details": {

        "coding": [

          {

            "system": "http://hl7.org/fhir/ValueSet/operation-outcome",

            "code": "REC_NOT_FOUND",

            "display": "404 - REC_NOT_FOUND"

          }

        ]

      },

      "diagnostics": "The resource was not found at the <Receiver Identifier (ODS)>: <further diagnostics information, error message/error text>"

    }

  ]

}

Failure Scenarios

For More Detail on error handling, there is specific information on failure scenarios available in the Failure Scenarios section.


Transactional integrity

Transactional integrity is employed to ensure data integrity is maintained between two parties. It helps ensure that the success or failure of a message is known and can be confirmed.

There are two existing header items for requests currently available to allow BaRS to meet transactional integrity requirements. They are listed below with their intended uses. The BaRS API specification contains example values for these entries.

Header Requirement Description Value
X-Correlation-ID Required A globally unique identifier for the request that can be used to track related transactions across multiple systems. string representing a GUID
X-Request-ID Required A globally unique identifier for the request, used to de-duplicate repeated requests and to trace the request for support purposes if needed. string representing a GUID

Transactional integrity is based on guidance in the FHIR standard. The header items are used as outlined below:

  • the sender will generate both IDs at the beginning of a request for a message.
  • the receiver will respond to this initial message, echoing back both IDs. The receiving server does not need to generate a new ID.
  • any feedback requests from receiver to the initial sender shall use a new X-Request-ID but retain the X-Correlation-ID. This will be as a new message of the same conversation.
  • any subsequent updates from the sender to the receiver shall use a X-Request-ID but will retain the original X-Correlation-ID. This will be as a new message of the same conversation.
  • any onward referrals of a message will use a new X-Request-ID but retain the X-Correlation-ID
  • a receiver of any message will not accept two messages with the same request and X-Correlation-IDs

Initial Request

The X-Correlation-ID is a conversation ID for this Encounter or Case. In our initial request both IDs are supplied by the original sender.

BaRS FHIR API end-to-end process

Sending an update

For an update or subsequent message, a new X-Request-ID is generated, therefore allowing the receiver to accept the new message. The X-Correlation-ID remains unchanged to indicate it is part of the same conversation.

BaRS FHIR API end-to-end process

Feedback (response) requests

In the event of feedback (a response), for example, a Safeguarding/DNA message; the receiver (becoming the sender effectively) sends a request to the initial sender with a new X-Request-ID and the same X-Correlation-ID as the original request.

BaRS FHIR API end-to-end process

Retry scenario

Should a request fail for any reason and the sender not receive a response (in any of the above scenarios) the sender is to retry the request, retaining the same X-Request-ID and X-Correlation-ID as the initial attempt. This allows the receiver to identify whether it has already processed this message or not.

BaRS FHIR API end-to-end process

This satisfies the need to be able to handle transaction integrity, and for the most part auditing/logging. There are two more scenarios that need to be clarified:

  • onwards referrals
  • sending a booking and referral

The proposal is to retain the X-Correlation-ID for two separate and distinct messages concerning the same encounter or case.

Onward Referrals

The X-Correlation-ID is retained in all interactions. This is beneficial for logging and auditing as using the X-Correlation-ID you can see all interactions for this encounter.

BaRS FHIR API end-to-end process

Definition of a retry

In this context a retry is an attempt to send the same message, without any changes following a failure. This means:

  • the Message body is the same
  • the X-Request-ID and X-Correlation-ID are unchanged
  • no other header items have changed. (with the potential exemption of the Access Token)

Receiver responsibilities

  • return the X-Request-ID and X-Correlation-ID in responses at ALL times, where possible
  • reject any message with no X-Request-ID and X-Correlation-ID, without exception with REC_BAD_REQUEST (400)
  • in the event that a duplicate message that has already been correctly processed is received, return a response with REC_CONFLICT (409) and an operationOutcome.issue.code of "duplicate"
  • this combination of codes can only be used in a duplicate message scenario

Sender responsibilities

The frequency of retries and the duration of a retry period depends on the scenario and should not disrupt workflow. Exponential backoff is considered best practice however it is at the discretion of the sender to define how many times a retry is attempted.

  • retry in the event X-Request-ID and X-Correlation-ID is not in the response
  • retry in the event no OperationOutcome is in the body of the response
  • retry when an OperationOutcome from a receiver contains one of the following values and response codes:
    • REC_TIMEOUT (408)
    • REC_TOO_MANY_REQUESTS (429)
    • REC_UNAVAILABLE (503) -retry when an OperationOutcome from BaRS itself contains one of the following:
    • PROXY_TIMEOUT / TIMEOUT (504 indicating 408 internally)
    • PROXY_TOO_MANY_REQUESTS / TOO_MANY_REQUESTS (500 indicating a 408 internally)
    • PROXY_UNAVAILABLE / UNAVAILABLE (503)
  • retry when an OperationOutcome from the BaRS API contains one of the following:
    • SEND_TOO_MANY_REQUESTS (429)
    • SEND_FORBIDDEN (403), on the assumption the access token issue is resolved
  • it is then at the senders discretion as to whether they update other properties of the message accordingly
  • do not retry a request again if a response with the following attributes is received, this indicates the message was successfully sent
    • REC_CONFLICT (409)
    • an operationOutcome.issue.code of "duplicate"

Any intermediary network device responding 'on behalf or in lieu' of the API or the receiver is not likely to respond with an OperationalOutcome or the required X-Request-ID and X-Correlation-ID. Any response not having either one of these properties can be safely deemed a communications failure, a temporary interruption to connectivity or could potentially indicate a service outage. Any of these scenarios could, but not always, warrant an retry. This would be at the discretion of the suppliers however these failed interactions should be logged with as much detail as possible. Errors outside of the HTTP standard should also be logged locally with as much detail as possible, for example; Transport-Layer error messages.

Failure Scenarios

When a message is received, the X-Request-ID and X-Correlation-ID header values are stored appropriately. In this example, this occurs ahead of the message being processed but after any access control is applied by means of the other available headers.

If a message fails due to a message with the same header ids having already been processed, the response must be a 409, REC_CONFLICT with an OperationOutcome.issue.code of 'duplicate' as seen below.

BaRS FHIR API end-to-end process

401 Outcome

     {

  "resourceType": "OperationOutcome",

  "id": "531e073a-3295-4e67-ae90-e00bd96a9cdd",

  "meta": {

    "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "security",

      "details": {

        "coding": [

          {

            "system": "https://fhir.nhs.uk/Codesystem/http-error-codes",

            "code": "REC_UNAUTHORIZED"

            "display": "401 - REC_UNAUTHORIZED"        

          }

        ]

      },

      "diagnostics": "Details of the Exact problem"

    }

  ]

}   

409 Outcome

        {

  "resourceType": "OperationOutcome",

  "id": "4e2e13af-3bc7-4de3-8cc5-ea4f14d45ef8",

  "meta": {

    "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "duplicate",

      "details": {

        "coding": [

          {

            "system": "https://fhir.nhs.uk/Codesystem/http-error-codes",

            "code": "REC_CONFLICT"

            "display": "409 - REC_CONFLICT"        

          }

        ]

      },

      "diagnostics": "This message has already been received and processed"

    }

  ]

}    

In the event of a timeout, a retry attempt is made after a suitable amount of time to ensure the message was received. The same X-Request-ID and X-Correlation-ID must be used. Should a 409 REC_CONFLICT response be received with a OperationOutcome.issue.code of "duplicate", then this can be used as confirmation that the message was received.

BaRS FHIR API end-to-end process

408 Outcome

{

  "resourceType": "OperationOutcome",

  "id": "531e073a-3295-4e67-ae90-e00bd96a9cdd",

  "meta": {

    "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "timeout",

      "details": {

        "coding": [

          {

            "system": "https://fhir.nhs.uk/Codesystem/http-error-codes",

            "code": "REC_TIMEOUT"

            "display": "408 - REC_TIMEOUT"        

          }

        ]

      },

      "diagnostics": "The connection for this request has timed out."

    }

  ]

}

409 Outcome

{

  "resourceType": "OperationOutcome",

  "id": "4e2e13af-3bc7-4de3-8cc5-ea4f14d45ef8",

  "meta": {

    "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "duplicate",

      "details": {

        "coding": [

          {

            "system": "https://fhir.nhs.uk/Codesystem/http-error-codes",

            "code": "REC_CONFLICT"

            "display": "409 - REC_CONFLICT"        

          }

        ]

      },

      "diagnostics": "This message has already been received and processed"

    }

  ]

}

If the processing of a message is not completed prior to the initial retry, the receiver must respond with a 425 REC_TOO_EARLY response, to indicate the initial message is still processing. The receipt is then unconfirmed and the sender can retry after a suitable amount of time until they receive a desired response.

BaRS FHIR API end-to-end process

408 Outcome

{

  "resourceType": "OperationOutcome",

  "id": "531e073a-3295-4e67-ae90-e00bd96a9cdd",

  "meta": {

    "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "timeout",

      "details": {

        "coding": [

          {

            "system": "https://fhir.nhs.uk/Codesystem/http-error-codes",

            "code": "REC_TIMEOUT"

            "display": "408 - REC_TIMEOUT"        

          }

        ]

      },

      "diagnostics": "The connection for this request has timed out."

    }

  ]

}

425 Outcome

{

  "resourceType": "OperationOutcome",

  "id": "98ae13af-3bc7-8cc5-4ac7-ea4f14d47dc9",

  "meta": {

    "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "duplicate",

      "details": {

        "coding": [

          {

            "system": "https://fhir.nhs.uk/Codesystem/http-error-codes",

            "code": "REC_TOO_EARLY"

            "display": "425 - REC_TOO_EARLY"        

          }

        ]

      },

      "diagnostics": "The Server has not finished processing the previous attempted request."

    }

  ]

}

409 Outcome

{

  "resourceType": "OperationOutcome",

  "id": "4e2e13af-3bc7-4de3-8cc5-ea4f14d45ef8",

  "meta": {

    "profile": [

      "https://fhir.hl7.org.uk/StructureDefinition/UKCore-OperationOutcome"

    ]

  },

  "issue": [

    {

      "severity": "error",

      "code": "duplicate",

      "details": {

        "coding": [

          {

            "system": "https://fhir.nhs.uk/Codesystem/http-error-codes",

            "code": "REC_CONFLICT"

            "display": "409 - REC_CONFLICT"        

          }

        ]

      },

      "diagnostics": "This message has already been received and processed"

    }

  ]

}

The scenarios above describe the response where a message is successful. If a message is not successfully processed following a timeout, the receiver should respond with the response that that failure would have generated. In the diagram below the final response inherits its response from the message that has failed after the timeout.

The relevant 4xx or 5xx response is what the sender receives as a response, ensuring the correct reason for a failure is communicated.

BaRS FHIR API end-to-end process


Non functional requirements

The following non functional requirements apply to all APIs designed to receive requests from BaRS. This includes sender systems receiving asynchronous responses and feedback, as well as receiving systems. All items listed below will be adhered to.

Name Description
Conformance The solution conforms with the BaRS standards where applicable, within any application
Safety The solution complies with NHS Digital Clinical Risk Management Standards, in particular 0129 (IT Supplier) and 0160 (Trust/Provider)
Security The solution resists unauthorised, accidental or unintended usage and provides access only to legitimate requests
Scalable and capacity The solution is designed to accommodate increased volumes, workloads and users
Availability and reliability The solution meets a service level agreement that includes a 99.9% uptime guarantee and a high Mean Time Between Failures (MTBF) The solution has adequate disaster recovery and fault tolerance enabling continued operation in the event of a failure
Auditability All of the solutions interactions are fully auditable for both sending and receiving
Data retention The Solution audits all API access attempts and actions, retaining all pertinent data therein
Deployment Solution providers release a new major version of their Booking and Referral APIs alongside a previous major version, until such time as consumers have migrated to the new major version. Alternatively, there is support for backward compatibility of requests to support consumers. Solution providers release a new minor or patch version, replacing the previous minor or patch version The Booking and Referral endpoints are independently deployable against unique Fully Qualified Domain Names (FDQN). This ensures support for limitations around sending and receiving messages between path-to-live and production environments. To increase availability, during upgrades and maintenance, the Booking and Referral APIs are load balanced across multiple servers In multi tenanted deployments, the activity of individual tenants are readily distinguishable

Standard Pattern for BaRS Operations

The majority of BaRS operations utilise a single standard approach. Since most BaRS operations involve composites of FHIR resources supporting a particular workflow they all utilise a single type of endpoint designed for processing and consuming of composite resources. This is the $process-message endpoint from the FHIR operations framework. The resource being transmitted in the body of the http request is a FHIR "Bundle" resource. This request payload needs to support two purposes, both the transmission of information as well as an indicator to direct the recieving system to how this particular bundle of resources is to be processed and what workflow should be triggered as a result of its consumption.

These core functions are:

  • making a referral
  • cancelling/amending a referral
  • making a booking
  • cancelling/amending a booking
  • providing a response/feedback communication

At the highest level this pattern follows the following key steps:

  • Sender GET the message definition for the payload/workflow being attempted
  • Sender composes the bundle (as defined by the message definition) ready for POST-ing.
  • Sender does a POST request to the receivers' /$process-message endpoint.
  • Receiver inspects the request header and the bundle MessageHeader resource for the core workflow variables indicating how to process and consume the bundle.
  • Receiver send http response message back to the sender.

Below is a pseudo code example, showing the above process in detail, illustrating how a message could be interpreted using core workflow variables for a message sent to POST /$process-message.

Each Application will have a tailored example of the below pseudo code.

> Click here to show example
Receive_Request
{
	initialise_variable "messageType" 
	initialise_variable "MessageReason" 
	initialise_variable "RequestType"
	
	//HTTP_Headers
	{
		if (HttpHeaders is null || HttpHeaders not Guid )
			OperationOutcome.issue.code = "invalid"
			throw exception with "REC_BAD_REQUEST"
			then return with HTTP.ResponseCode 400
		else if (HttpHeaders.RequestId == RequestId.AlreadyReceived)
			OperationOutcome.issue.code = "duplicate"
			throw exception with "REC_CONFLICT"
			then return with HTTP.ResponseCode 409
	}
	//Bundle
	{
		if(Bundle.meta.versionID is null)
			OperationOutcome.issue.code = "invariant"
			throw exception with "REC_BAD_REQUEST"
			then return with HTTP.ResponseCode 422
		else if!(Bundle.meta.versionID in versionID.supported)
			OperationOutcome.issue.code = "not-supported"
			throw exception with "REC_UNPROCESSABLE_ENTITY"
			then return with HTTP.ResponseCode 422
	}
	//Contents;
	{
		switch(MessageHeader.eventCoding)
		{
			case "servicerequest-request":
				if (MessageHeader.reason.code == "new" && ServiceRequest.status == "active")
					{
						switch(ServiceRequest.Category)
						{
							case "Validation":
								if (CarePlan.status != "active")
									{							
										RequestType = "unknown";
										OperationOutcome.issue.code = "invariant";//A content validation rule failed
										throw exception with "REC_BAD_REQUEST";
										then return  HTTP.ResponseCode 400;
									}
								else if(Encounter.Status.In("triaged","in-progress")
									{RequestType = "Im Receiving a new Validation Request";}
								else
									{
										RequestType = "unknown";
										OperationOutcome.issue.code = "invariant";//A content validation rule failed
										throw exception with "REC_BAD_REQUEST";
										then return  HTTP.ResponseCode 400;
									}
								break;
							case "Referral":
								if (Careplan.status != "completed")
								{
									RequestType = "unknown"
									OperationOutcome.issue.code = "invariant"//A content validation rule failed
									throw exception with "REC_BAD_REQUEST"
									then return  HTTP.ResponseCode 400
								}
								else if(Encounter.Status.In("triaged","finished"))
									RequestType = "Im Receiving a new Referral"
								else
									RequestType = "unknown"
									OperationOutcome.issue.code = "invariant"//A content validation rule failed
									throw exception with "REC_BAD_REQUEST"
									then return  HTTP.ResponseCode 400
								break;
							default:
								RequestType = "unknown"
								OperationOutcome.issue.code = "invariant"//A content validation rule failed
								throw exception with "REC_BAD_REQUEST"
								then return  HTTP.ResponseCode 400;
						}
					}
				else if (MessageHeader.reason.code == "update")
					{
						switch(ServiceRequest.category)
						{
							case "Validation":
								if(ServiceRequest.status.In("entered-in-error","revoked"))
									{RequestType = "im receiving a cancelled validation request";}
								else if(ServiceRequest.status.In("active","on-hold"))
									{RequestType = "im receiving an update to a validation request";} 
								else
								{
									RequestType = "unknown"
									OperationOutcome.issue.code = "invariant"//A content validation rule failed
									throw exception with "REC_BAD_REQUEST"
									then return  HTTP.ResponseCode 400								
								}
								break;
							case "Referral":
								if(ServiceRequest.status.In("entered-in-error","revoked"))
								{RequestType = "im receiving a cancelled referral"}
								else
								{
									RequestType = "unknown"
									OperationOutcome.issue.code = "invariant"//A content validation rule failed
									throw exception with "REC_BAD_REQUEST"
									then return  HTTP.ResponseCode 400								
								}
								break;
							default:
								RequestType = "unknown"
								OperationOutcome.issue.code = "invariant"//A content validation rule failed
								throw exception with "REC_BAD_REQUEST"
								then return  HTTP.ResponseCode 400;
						}

					}
				else
				{
					RequestType = "unknown"
					OperationOutcome.issue.code = "invariant"//A content validation rule failed
					throw exception with "REC_BAD_REQUEST"
					then return  HTTP.ResponseCode 400}
				break;
			case "servicerequest-response":
				if (MessageHeader.Response is null )
				{
						RequestType = "Invalid servicerequest-response"
						OperationOutcome.issue.code = "invariant"//A content validation rule failed
						throw exception with "REC_BAD_REQUEST"
						then return  HTTP.ResponseCode 400;
				}
				else if ( !Message.Response.identifier.existsLocally())
				{
						RequestType = "none or invalid response ID"
						OperationOutcome.issue.code = "not-found"//A content validation rule failed
						throw exception with "REC_NOT_FOUND"
						then return  HTTP.ResponseCode 404;
				}
				switch (ServiceRequest.Category)
					{
						case "Referral":
							if (ServiceRequest.status == "revoked" && MessageHeader.reason.code == "new")
							{ RequestType = "im receiving a Safeguarding DNA response (noshow)" } 
							else
							{
								RequestType = "unknown"
								OperationOutcome.issue.code = "invariant"//A content validation rule failed
								throw exception with "REC_BAD_REQUEST"
								then return  HTTP.ResponseCode 400;
							}
							break;
						case "Validation":
							if(!AnyEncounter.Originates.Local && Encount.Count()<=3)
							{
								if (MessageHeader.Reason.code == "new" && ServiceRequest.status == "active" && MessageHeader.FocusEncounter.status=="in-progress")
								{Request Type = "im receiving a Validation Response interim update" }
								else if (MessageHeader.Reason.code.In ("new","update") && ServiceRequest.status == "completed" && MessageHeader.FocusEncounter.status.In("triaged","complete")
								{Request Type = "im receiving a final Validation Response" }
								else
								{
									RequestType = "unknown"
									OperationOutcome.issue.code = "invariant"//A content validation rule failed
									throw exception with "REC_BAD_REQUEST"
									then return  HTTP.ResponseCode 400;
								}
							}
							else if(MessageHeader.FocusEncounter.status = "triaged" && ServiceRequest.status == "revoked" && MessageHeader.Reason.code.In("new","update"))
							{ RequestType = "im receiving  a Rejected validation response" } // a new encounter here is an edge case.
							else
							{
								RequestType = "unknown"
								OperationOutcome.issue.code = "invariant"//A content validation rule failed
								throw exception with "REC_BAD_REQUEST"
								then return  HTTP.ResponseCode 400;
							}
						default:
							RequestType = "unknown"
							OperationOutcome.issue.code = "invariant"//A content validation rule failed
							throw exception with "REC_BAD_REQUEST"
							then return  HTTP.ResponseCode 400;
					}
			case "booking-request":
				if (MessageHeader.Reason.code== "new" && Appointment.Status == "booked")
					if(slot.IsFree())
					{RequestType = "Im Receiving a new booking.";}
					else
					{
						OperationOutcome.issue.code = "conflict"
						throw exception with "REC_CONFLICT"
						then return with HTTP.ResponseCode 409
					}
				else if (MessageHeader.Reason.code == "update")
					MessageHeaderIsUpdate = true;
					switch (Appointment.Status)
					{
						case "cancelled":
							RequestType = "Im Receiving a booking cancellation."
							break						
						case "entered-in-error":
							RequestType = "Im Receiving a booking cancellation."
							break
						case "booked":
							RequestType = "Im Receiving an update to a booking."
							break
						default:
							OperationOutcome.issue.code = "invariant"//A content validation rule failed
							throw exception with "REC_BAD_REQUEST"
							then return with HTTP.ResponseCode 400;
							break;
					}
				else
				{
					OperationOutcome.issue.code = "invariant"//A content validation rule failed
					throw exception with "REC_BAD_REQUEST"
					then return with HTTP.ResponseCode 400;
				}
				break;
			case "booking-response":
					OperationOutcome.issue.code = "invariant"//A content validation rule failed
					throw exception with 'REC_BAD_REQUEST'
					then return with HTTP.ResponseCode 400
					break;
			default:
				OperationOutcome.issue.code = "invariant"//A content validation rule failed
				throw exception with 'REC_BAD_REQUEST'
				then return with HTTP.ResponseCode 400
				break;
		}
		
	}
	//Submit
	{
		
		if (Message == "update")
		{
			if (currentLocalData.LastUpdated > originaRequest.ReceivedDate)
			{
				OperationOutcome.issue.code = "conflict"
				throw exception with 'REC_CONFLICT'
				then return with HTTP.ResponseCode 409
				break;
			}		
			foreach (Entry in Bundle)
			{
				if (currentLocalData.Item.exists)
				{
					if (currentLocalData.LastUpdated > originaRequest.Received)
					{
						OperationOutcome.issue.code = "conflict"
						throw exception with 'REC_CONFLICT'
						then return with HTTP.ResponseCode 409
						break;
					}
					if(Entry.LastUpdated > currentLocalData.Item.meta.LastUpdated && Entry.fullUrl = currentLocalData.Item.fullUrl)
						currentLocalData.Item = Entry.Item
						Entry.SubmitWith(currentLocalData.Item.meta.LastUpdated == Entry.LastUpdated )
					else
						ignore
				}
				else
					Entry.SubmitWith(currentLocalData.Item.meta.LastUpdated == Entry.LastUpdated )					
			}
			Submit(currentLocalData.Bundle.meta.LastUpdated = Bundle.Meta.LastUpdated)
			return HTTP.ResponseCode 200 'OK'
		}
		else
			{
				foreach(Entry in Bundle)
				{
					Entry.SubmitWith(currentLocalData.Item.meta.LastUpdated == Entry.LastUpdated )
					Submit(currentLocalData.Bundle.meta.LastUpdated = Bundle.Meta.LastUpdated)
					return HTTP.ResponseCode 200 'OK'
				}
			}
	}	
}	

back to top