Skip to main content
Pharmacy Benefits is one of haau3’s two Benefits services, the two halves of CMS’s Real-Time Benefits use case. Pharmacy Benefits answers what a medication would cost, in real time, from the sources it connects to; Plan Benefits answers what a Medicare Advantage or Marketplace plan covers and what a member pays, from CMS’s plan files. Ask what a medication would cost: its price at pharmacies near a ZIP code, with no patient details, or what it would cost a member under their pharmacy coverage. The exchange is the CARIN Consumer Real-Time Pharmacy Benefit Check (STU2 ballot, 2.0.0-ballot): you POST a FHIR message Bundle and get the answer in the same exchange.
No source is connected yet. Until the first price source and insurer are connected, every check is answered with the reason it could not be priced, never with a price made up in its place. The answers below show exactly what comes back today. A price, once a source gives one, is an estimate; the pharmacy confirms the price.
A member’s request carries patient data. Send the rtpbc-request message only under an executed agreement with haau3, as the terms require. The request without patient details carries none. Nothing you send is stored, cached or logged. See Data handling.

Service base

Everything hangs off that base, exactly as the FHIR REST specification describes. /v1/ is the version of the haau3 platform API contract, not a FHIR version. The FHIR release is r4, and every response says so:
You never have to ask for a release. A plain Accept: application/fhir+json, or no Accept header at all, works fine. If you do ask for one we do not serve, or for a format other than JSON (in Accept or _format), you get a 406 rather than a silent substitution. Every error on this base is a FHIR OperationOutcome, including the 401 you get without a key and a 429, so a FHIR client only ever has one error shape to parse. Errors, limits and request ids lists the statuses every service shares.
The uppercase form /v1/fhir/R4/... redirects to the lowercase base with a 308, which preserves the method and body so a POST is not quietly turned into a GET.
The Terminology service, the Provider directory, Questionnaires, Plan Benefits and MedList use this same base and the same CapabilityStatement. This operation is declared under rest.operation, beside MedList’s.

What is served

One operation, on the service base itself: GET [base]/metadata returns the CapabilityStatement, which lists this operation under rest.operation and names CARIN’s responder statement in instantiates. The operation accepts POST only, with Content-Type: application/fhir+json, and it needs an API key (see Authentication). It takes no parameters: it answers synchronously, and its input is the message Bundle sent as the body. Every request is a Bundle of type message whose first entry is a MessageHeader. The header’s eventCoding says which of two messages it is, from the system http://hl7.org/fhir/us/carin-rtpbc/CodeSystem/rtpbc-event-type:

Price a medication without patient details

The request names the product and the quantity in its Claim, masks the patient and the coverage with a data-absent-reason of masked, and names either a pharmacy in Claim.provider or one or more ZIP codes in its preferredPharmacyPostalCode extension.
The request.json below is the synthetic example from the haau3 FHIR Implementation Guide: 30 lisinopril 5 mg tablets (RxNorm 311354) at pharmacies in ZIP code 33131.
request.json
The answer today: the request was read, and no price source returned a price.

Check a member’s cost

The member’s request carries their Patient and Coverage, the prescription as a MedicationRequest, its prescriber as a Practitioner, and the pharmacy as an Organization named by its NPI or NCPDP provider id. The Coverage carries the payer’s identifier in payor.identifier: it is how the member’s insurer is found, so a member’s request without it is refused. For prices without patient details, send rtpbc-request-non-phi instead. Some insurers answer only with the member’s own authorization. When this service connects to one, your app will send the member’s authorization with each request, and it will be passed to that insurer for that one request and kept nowhere. How it is sent will be documented here before then.
request.json
The answer today: no insurer this service connects to answers for the coverage’s payer. The member’s Patient comes back as it was sent, as CARIN requires.

Reading the answer

The answer is a Bundle of type message. Its MessageHeader answers yours: response.identifier is your MessageHeader.id, and response.code is ok whenever the request was read. Its one ClaimResponse carries either prices and coverage in item, or the reason there are none in error, never both, as CARIN requires. ClaimResponse.request.identifier is your Claim’s identifier. A member’s answer also carries your Patient, at the same fullUrl. When no connected source can answer, ClaimResponse.outcome is error and error[0].code carries a code from https://haau3.com/fhir/CodeSystem/pharmacy-benefits-processing-code: An NCPDP reject code in an answer comes from the source that sent it, unchanged.

What is refused

The whole request is refused with HTTP 400 and an OperationOutcome before any source is asked when it holds:
  • a query parameter: the operation takes none;
  • a body that is not a Bundle of type message (a Parameters resource is refused by name), or a first entry that is not a MessageHeader;
  • an event other than the two above (issue code not-supported);
  • a list over its cap (issue code too-long): 20 entries, 5 focus references, 10 codings on a product, 10 ZIP codes, 10 identifiers on a pharmacy, 5 payors and 20 classes on a coverage;
  • an element the CARIN profiles require that is missing, such as the MessageHeader.id, the Claim’s one identifier and one item, or a quantity with no unit;
  • a reference that names no entry in the Bundle, or names one of the wrong type, resolved as FHIR resolves references inside a Bundle;
  • a product with no NDC (11 digits, no dashes) or RxNorm code: text alone cannot be priced;
  • a ZIP code that is not five digits or ZIP+4;
  • in a request without patient details, a patient or coverage that is not masked, or no pharmacy and no ZIP code;
  • in a member’s request, no Coverage, or a Coverage with no payor.identifier;
  • malformed content, or a body over 1 MiB (HTTP 413).
The OperationOutcome names the element by its FHIRPath in expression, and never repeats a value you sent.

Errors

Every error is a FHIR OperationOutcome. The statuses every service shares, among them 401, 405, 406, 415, 429 and 500, are on Errors, limits and request ids. This operation is not offered inside a batch Bundle. A batch entry that names it is refused by name.

Data handling

This operation is stateless. Nothing a request carries is stored, cached or logged, apart from a request ID you choose to send, and each check is a point-in-time answer: nothing from an earlier request bears on a later one. A refusal names the element, never a value. Every answer carries Cache-Control: no-store, errors included, so no cache between you and the service keeps one. A member’s request is protected health information. The haau3 terms reserve patient data for services designated to receive it under an executed agreement, and this operation is one of them. Send a member’s request only under such an agreement with haau3. The platform project that serves it runs under Google Cloud’s Business Associate Agreement.

Specification

The contract is the CARIN Consumer Real-Time Pharmacy Benefit Check, STU2 ballot, and the haau3 FHIR Implementation Guide’s Pharmacy Benefits page: