Skip to main content
Pharmacy Benefits is not yet open to customers. Try it now in the Sandbox, the platform API’s test environment at https://sandbox.haau3.com, where Example Health Plan, a test insurer, answers the CARIN Consumer Real-Time Pharmacy Benefit Check (STU2 ballot, 2.0.0-ballot) as a payer would, so a client written for a payer runs against it with only its base URL changed. It answers for a small cast of test members, and each medication in its formulary table always gets the same answer, so every outcome can be rehearsed on demand: a price with a copay, an alternative drug, not covered, prior authorization, a quantity limit and two errors.
Send test members only, never a real person. Example Health Plan answers only for the test members below, whose details are made up. A request for anyone else is answered with the error no-test-member and nothing more, and a request naming another payer reaches no one. Nothing you send is stored, cached or logged. See Data handling.
Every organization can call it on every plan, with no key or with a test key (see the Sandbox). A test key’s requests count toward a minute of their own, at your plan’s number and apart from your production requests, and never toward the plan’s month. The open-source SDKs for TypeScript, Python and Go call every operation of this service, and each operation in the API reference has a sample in all three and in cURL.

Service base

The Sandbox’s FHIR base is production’s, https://api.haau3.com/v1/fhir/r4, on the Sandbox’s own host. It takes calls with no key or a test key, and a live key gets 403 live-key-in-sandbox. It answers application/fhir+json; fhirVersion=4.0, and every error on it is a FHIR OperationOutcome. The Sandbox’s first address, https://api.haau3.com/v1/sandbox/fhir/r4, also answers this check with a test key. It is deprecated: call the Sandbox’s own host instead (see the first address).

What is served

metadata returns the Sandbox’s CapabilityStatement, which names CARIN’s responder statement in instantiates and CARIN’s two request messages, the member’s request (rtpbc-request) and the request without patient details (rtpbc-request-non-phi), as the messages it receives. It also lists every resource and operation the Sandbox answers; the Sandbox lists the other services’ examples. $process-message takes a member’s request and answers it in the same exchange, at the same path as Pharmacy Benefits in production. It reads, routes and answers a request as Pharmacy Benefits does, with two of its checks left out: every organization may call it, and it needs no business associate agreement and no HTEST label, since only its test insurer can answer. Pharmacy Benefits needs both for a member’s request (see Patient data), so a request the Sandbox answers may still be refused there. It accepts POST only, with Content-Type: application/fhir+json, and takes no parameters.

Test members

Send a test member’s member ID in any of the places CARIN carries one:
  • Coverage.subscriberId;
  • the Coverage’s rxid class, the member ID on a pharmacy benefit card (http://terminology.hl7.org/CodeSystem/coverage-class);
  • a Patient.identifier whose system is https://example-health-plan.test/member-id.
One of them is enough. Names and birth dates are not compared, so a typo never blocks a test. Two places that name two different test members name no one. Example Health Plan’s Payer ID is SANDBOX-EHP, and the examples name the payer by it, in a Coverage.payor.identifier whose type is payerid from http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType. A member’s request is routed by that Payer ID, as the platform’s Pharmacy Benefits finds a member’s insurer: SANDBOX-EHP, or the guide’s example payer EXAMPLE-PAYER, reaches Example Health Plan, and any other Payer ID, or a payer named without one, is answered no-responder-for-coverage, with no one asked.

Formulary table

A medication is matched by its RxNorm code or its NDC, in the Claim’s item.productOrService.coding. The codes and displays are from RxNorm; the two drugs of CARIN’s own examples, Prozac and Zepatier, keep CARIN’s amounts. The amounts are fixed whatever the quantity you ask about.

What the test data does not cover

Example Health Plan lets you rehearse the shape of a member’s answer and each outcome above. It is not a model of a real plan, and it does not have:
  • a deductible, coinsurance tiers, or a running total of what a member has paid this year;
  • step therapy, or any restriction beyond the ones in the table;
  • prices that differ by pharmacy, network or mail order: the pharmacy you name changes nothing;
  • amounts that follow the quantity or the days’ supply;
  • a price for the request without patient details, which price sources give: it is answered no-price-source, since the Sandbox connects none;
  • a member’s own sign-in: you send your platform API key, where a payer takes the member’s access token.
It enforces CARIN as published and nothing more, so a production payer that requires more may refuse a request this test insurer accepts.

Send a request

The member’s request is the same message the Pharmacy Benefits page documents: a Bundle of type message whose first entry is a MessageHeader with the event rtpbc-request, then the Claim, the member’s Patient and Coverage, the prescription (MedicationRequest), its prescriber and the pharmacy. That page’s member example answers here as it is, since its member is Yuk Sample and its payer the guide’s EXAMPLE-PAYER.
That sends no key, which the Sandbox allows at 30 requests an hour from each IP address. With a test key, add -H "Authorization: Bearer $HAAU3_API_KEY" for your plan’s requests a minute.
The request, request.json:
request.json
The answer:
answer.json
To try every outcome, change the member ID, or the medication’s coding in both the Claim and the MedicationRequest, to another row of the tables above.

Reading the answer

The answer is a Bundle of type message: a MessageHeader answering yours, one ClaimResponse, and your Patient as you sent it. The examples label every resource HTEST (http://terminology.hl7.org/CodeSystem/v3-ActReason), so their answers’ Patient keeps the label and a copy you keep stays marked as test data. Example Health Plan’s ClaimResponse names the insurer by its Payer ID, SANDBOX-EHP, and carries either an answer in item or an error in error, never both.
  • item is the medication you asked about. Its restriction is the rtpbc-benefitRestriction extension (covered, prior-auth, quantity-limit or not-covered), and what the member pays is in adjudication, each amount coded by CARIN’s pay type (copay, coinsurance, eligible or total).
  • addItem is an alternative drug, where the row has one: marked rtpbc-isAlternative, with its own restrictions and amounts and the quantity you asked about.
  • processNote always starts with one note saying the answer is synthetic, which test member it is for and which row answered. A limit’s words, such as Quantity limit: 9 tablets per 30 days, are a further note that the item points to with noteNumber. The last note names Example Health Plan and when it answered, as Pharmacy Benefits names a member’s insurer.
  • error carries one code from https://haau3.com/fhir/CodeSystem/sandbox-error: no-test-member (no test member matched) or insurer-declined (the insurer declined the request), with outcome error.
A request no one could be asked about carries Pharmacy Benefits’ own code instead, from https://haau3.com/fhir/CodeSystem/pharmacy-benefits-processing-code, with outcome error: no-responder-for-coverage for a member’s request naming another payer, and no-price-source for the request without patient details (rtpbc-request-non-phi), since the Sandbox connects no price source.

What is refused

A request is read by the same rules as Pharmacy Benefits’, so a request this base reads, the platform’s FHIR base reads too, though production also needs an agreement or HTEST labels for a member’s request (above). A request it refuses is answered 400 with an OperationOutcome that names the element by its FHIRPath, never a value you sent: a body that is not a message Bundle, an event other than the two above, a list over its cap, an element the CARIN profiles require that is missing, a reference that names no entry in the Bundle, a product with no NDC or RxNorm code, or a member’s request with no Coverage or no payer identifier. It takes Pharmacy Benefits’ Payer-Authorization header too. Example Health Plan needs no member’s authorization, so the header is sent nowhere, but it is checked as Pharmacy Benefits checks it: one that is not Bearer and a token is refused 400, and so is one sent with the request without patient details.

Errors

The statuses every service shares, among them 401, 405, 406, 415, 429 and 500, are on Errors, limits and request ids.

Data handling

Example Health Plan answers with synthetic data, for its test members only, and is not an operation for patient data. It is stateless all the same: nothing a request carries is stored, cached or logged, apart from a request ID you choose to send. The log records whether the request was answered and what became of it, as counts and fixed words, never a member or a medication, and every answer of $process-message carries Cache-Control: no-store. See Patient data.

Specification