South African flag
Skip to main content

Error Handling

NHIE services do not share a single error format. How a failure is reported depends on where it happened: at the gateway, in a FHIR service, or in an HL7 v2 or CSD service. This page explains how to recognise each kind of error, what to do about it, and what to capture when you need support.

Where errors come from

LayerServicesHow the error is reported
Connection (TLS)All mTLS endpointsThe TLS handshake fails. No HTTP response is returned.
GatewayAll servicesAn HTTP status with a short JSON message, or an HTML error page for unknown routes
FHIR servicesHPRS PDQm (/pdqm, /mtlspdqm), ARR (/arr/AuditEvent)An HTTP status with a FHIR OperationOutcome body
HL7 v2 servicesHPRS PDQ (/pdq)An HL7 acknowledgment code in MSA-1 and QAK-2, with details in the ERR segment
CSD servicesMHFL (/csd)CSD XML on success. Some query errors return a plain-text error trace instead.

Check the layer first. A TLS failure or a gateway 401 or 403 means the request never reached the service, so changing the payload will not help.

Status codes

StatusMeaning on NHIEWhat to do
400 Bad RequestMissing or malformed search parameter, unsupported identifier type code, or an mr identifier system without a facility codeFix the request. Do not retry unchanged.
401 UnauthorizedMissing or invalid apikey header (MHFL)Check the header name and value.
403 ForbiddenMissing or incomplete x-security header (HPRS), IP address not whitelisted, or credentials that do not cover the routeCheck the x-security header. If it is correct, contact HIS Support.
404 Not FoundNo patient matched the identifier, or the request reached the gateway without a service pathFor a patient lookup, treat it as a valid "no match" result. For the base URL, call a service endpoint.
422 Unprocessable EntityThe request is well formed but fails a business rule, such as an invalid South African ID number check digit or a patient id sent with an identifierCorrect the data. Do not retry unchanged.
429 Too Many RequestsThrottling applied to protect shared use of the test environmentWait, then retry with backoff.
5xxService or upstream failureRetry read operations with backoff. Escalate if the error persists.

HPRS errors

PDQm (FHIR)​

HPRS PDQm returns errors as a FHIR OperationOutcome. The human-readable reason is in issue[].details.text, and a machine-readable code is in issue[].details.coding[].code.

{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "invalid",
"details": {
"coding": [
{
"system": "https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1",
"code": "INVALID_PARAMETER",
"display": "Invalid parameter"
}
],
"text": "Invalid RSA identification number."
}
}
]
}
ScenarioResponse
Patient does not exist404 Not Found, operation outcome "patient not found"
No x-security header403 Forbidden, "Missing security header"
x-security header without a user ID (x-security: emr$)403 Forbidden, "Missing account id in security header"
$match request without a security segment or security header403 Forbidden, "missing security segment"
Empty or missing identifier parameter400 Bad Request, "Invalid parameter"
Unsupported identifier type code (for example PRV)400 Bad Request, "Invalid identifier type code"
mr identifier system without a facility code400 Bad Request, "Invalid identifier system required https://test-hie.health.gov.za/mr/<facilitycode>"
South African ID number fails validation422 Unprocessable Entity, "Invalid RSA identification number."
Request contains both a patient id and an identifier422 Unprocessable Entity, CONFLICTING_VALUES
Unsupported resource path (for example /pdqm/Location)An HTML error page: Cannot GET /pdqm/Location

South African ID numbers are validated before the search runs: 13 digits, starting with a valid YYMMDD date, and ending with a valid check digit. Validate them in your own system first to avoid avoidable 422 responses.

See PDQm for the full request and response examples.

PDQ (HL7 v2)​

HPRS PDQ always returns an HL7 message. Read the outcome from the acknowledgment codes, not from the HTTP status.

MSA-1QAK-2MeaningWhat to do
AAOKOne or more patients foundProcess the PID segments.
AANFQuery accepted, no patients foundTreat as a valid "no match" result.
AEAEApplication errorRead the ERR segment, correct the query, and resend.
AR—Application rejectRead the ERR segment. Check the message structure and header fields.
MSA|AE|TEST-CR-14-30
ERR||QPD^1^3^2|204|E||||unknown domain namespace id
QAK|8494764101303661668890645833527|AE

In this example, ERR-2 (QPD^1^3^2) points to the field that caused the error, and ERR-8 gives the reason: the assigning authority in the query is not a known identifier domain.

See PDQ (HL7 v2) for more examples.

MHFL errors

ScenarioResponse
Missing or invalid apikey header401 Unauthorized with {"message":"Invalid authentication credentials"}
Paged search where max is larger than the result setA plain-text error trace containing [FOER0000] The collection size is not greater or equal to the max value!

Because some MHFL errors are returned as plain text, check that the response body is CSD XML before you parse it. Never show the raw error trace to end users.

See the MHFL API Guide for the supported search modes.

ARR errors

The ARR is a FHIR endpoint, so failures are returned as an OperationOutcome with an HTTP error status. A successful submission returns the stored AuditEvent with its assigned id.

If the ARR cannot be reached, keep the audit record and send it later. The HPRS guide requires audit records to be buffered locally until connectivity is restored. Do not drop them, and do not block the clinical workflow while you wait.

Handling errors in code

Choose a parser based on the response Content-Type. An error body may be FHIR JSON, plain JSON, HTML, or plain text.

async function readNhieError(response) {
const contentType = response.headers.get('content-type') || '';
const text = await response.text();

if (contentType.includes('json')) {
try {
const body = JSON.parse(text);

if (body.resourceType === 'OperationOutcome') {
const issue = body.issue?.[0] ?? {};
return {
code: issue.details?.coding?.[0]?.code ?? issue.code,
message: issue.details?.text ?? issue.diagnostics ?? 'Request failed',
};
}

return { code: null, message: body.message ?? 'Request failed' };
} catch {
// Fall through and treat the body as text.
}
}

// HTML pages and plain-text traces are for logs only.
return { code: null, message: `Request failed with status ${response.status}` };
}

async function requestNhie(url, options, context) {
const response = await fetch(url, options);

if (response.ok) {
return response;
}

const { code, message } = await readNhieError(response);

console.error('NHIE request failed', {
service: context.service,
method: options.method ?? 'GET',
path: new URL(url).pathname,
status: response.status,
code,
message,
timestamp: new Date().toISOString(),
});

const error = new Error(message);
error.status = response.status;
error.code = code;
throw error;
}

For HPRS PDQ (HL7 v2), check the acknowledgment codes instead of the HTTP status:

function readPdqAck(hl7Message) {
const segments = hl7Message.split(/\r\n|\r|\n/);
const field = (name, index) =>
segments.find((segment) => segment.startsWith(`${name}|`))?.split('|')[index];

return {
ackCode: field('MSA', 1), // AA, AE, or AR
queryStatus: field('QAK', 2), // OK, NF, or AE
errorText: field('ERR', 8) ?? null,
};
}

Retries

Retry only when the failure is temporary and the request is safe to repeat.

FailureRetry?
429 Too Many RequestsYes, with exponential backoff and jitter
5xx on a query (PDQ, PDQm, CSD)Yes, with backoff, up to a small limit
Network timeout or connection reset on a queryYes, with backoff
ARR unavailableBuffer the audit record locally and resend when the ARR is reachable
TLS handshake failureNo. Check your certificate, key, and CA bundle.
400, 401, 403, 404, 422No. Fix the cause first.
HL7 AE or ARNo. Fix the query first.
const RETRIABLE_STATUSES = new Set([429, 500, 502, 503, 504]);

async function withBackoff(requestFn, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
return await requestFn();
} catch (error) {
// Network errors have no status and are treated as retriable.
const isRetriable = error.status === undefined || RETRIABLE_STATUSES.has(error.status);

if (!isRetriable || attempt === maxAttempts) {
throw error;
}

const delayMs = 500 * 2 ** (attempt - 1) + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}

Cross-service workflows

A clinical workflow usually touches more than one service. Check each dependency in order, so a failure points to one cause.

  1. Connection and credentials. Confirm the mTLS handshake succeeds and the apikey or x-security header is accepted.
  2. Patient identity (HPRS). Search HPRS before any further processing. A 404 or NF result means no match, not a failure.
  3. Facility (MHFL). Confirm the facility code exists and the facility status is Active.
  4. Audit (ARR). Send the ATNA audit event for the transaction. If the ARR is unavailable, buffer it locally.

If HPRS itself is unavailable (connection failures or repeated 5xx responses), your system may create the patient locally. When HPRS is available again, update the local record with HPRS demographics using a PDQ query. See the minimum requirements for third-party applications.

User-facing messages

Keep messages short, specific, and free of internal detail. Never show stack traces, raw HL7, or identifiers that are not already on screen.

ConditionMessage to show
Patient not found in HPRSNo patient matched this identifier. Check the number or register the patient.
Invalid South African ID numberThis ID number is not valid. Check the digits and try again.
Missing or rejected security headerPatient lookup is not available for your account. Contact your system administrator.
Invalid MHFL API keyFacility lookup is not configured correctly. Contact your system administrator.
Rate limit reachedThe service is busy. Wait a moment and try again.
HPRS or MHFL unavailableThe national registry is not responding. You can continue, and the record will be updated later.

Logging

  • Log the timestamp, service, HTTP method, path, status, and error code for every failed call.
  • For HPRS PDQ, log the message control ID (MSH-10) and query tag (QPD-2).
  • For the ARR, log the id returned for each stored AuditEvent.
  • Never log the apikey value, private keys, or certificate files.
  • Patient identifiers and demographics are personal information under POPIA. Mask them in application logs. Your ATNA audit events are the record of who accessed which patient.

Support checklist

When you contact HIS Support, include:

  • Environment: test or production
  • Service and endpoint, for example HPRS PDQm GET /pdqm/Patient
  • HTTP method and the time of the request, with time zone
  • HTTP status and the full error body (OperationOutcome, HL7 MSA, ERR and QAK segments, or the error text)
  • The request with secrets removed and patient identifiers replaced by test values
  • Your source IP address, if you see a 403 or a TLS failure
  • Whether the problem happens every time or only sometimes