Docs / Response model

Response model

A response has two parts: artifacts (the data) and requestStatus (what happened to each thing you asked for). Read both — an empty artifact list with a declined status is a perfectly normal, successful exchange.

const response = await requestCheckin(myRequest);

response.artifacts;      // [{ id, mediaType, fulfills: [...], value, fhirVersion? }]
response.requestStatus;  // [{ item: "allergies", status: "fulfilled" }]

What's already been checked

Before your code sees a response, the kit has:

What it has not done is judge the clinical content. A conformant response can still contain a medication list that's a year stale. Protocol validity is not data quality — that judgment stays yours.

Per-item status

Status Means
fulfilled Shared as asked
partial Some of it — e.g. two of five years of history
unavailable The app doesn't have it
declined The patient chose not to share this item
unsupported The app can't handle this kind of ask
error Something went wrong on the wallet side

Per-item declines are ordinary. Show the patient what came through, and offer your own form for the rest rather than treating a partial share as a failure.

const byItem = new Map(response.requestStatus.map((s) => [s.item, s.status]));
for (const item of myRequest.items) {
  const status = byItem.get(item.id);
  if (status !== "fulfilled") promptManuallyFor(item);
}

Getting at the data

Artifacts point back at the items they satisfy — one artifact can cover several items, and one item can be covered by several artifacts.

function resourcesFor(response, itemId) {
  return response.artifacts
    .filter((a) => a.fulfills.includes(itemId) && a.mediaType === "application/fhir+json")
    .flatMap((a) => {
      const v = a.value;
      return v?.resourceType === "Bundle"
        ? (v.entry ?? []).map((e) => e.resource).filter(Boolean)
        : [v];
    });
}

const allergies = resourcesFor(response, "allergies")
  .filter((r) => r.resourceType === "AllergyIntolerance");

SMART Health Card artifacts carry value.verifiableCredential — an array of JWS strings. Keep the JWS if you store them: it's the only thing that carries the issuer's signature. Unpacking it into plain FHIR throws that away.

The pattern worth copying: fill the gaps, don't re-ask

The interesting move isn't dumping the response into a chart. It's using it to shorten what you ask the patient.

US Core requires an allergy's substance and clinical status, but reaction and criticality are optional — so real records routinely arrive as "Latex, and nothing else." Your form knows what's missing and can ask only for that:

const rows = allergies.map((a) => ({
  name: a.code?.text,
  reactions: (a.reaction ?? []).flatMap((r) => r.manifestation ?? []).map((m) => m.text),
  criticality: a.criticality,
}));

const needsDetail = rows.filter(
  (row) => row.reactions.length === 0 || !row.criticality || row.criticality === "unable-to-assess",
);

Then the patient confirms what's known and supplies only what isn't — and you can mark which fields they contributed, which is exactly the information a nurse wants when reconciling. The allergy example implements this end to end, including a manual-entry path that lands in the same review.

Two things that follow from doing it this way:

Storing it

The kit stops here on purpose — see Writing FHIR for the optional helper, or write it however your system wants. What matters is that patient- supplied data is labelled as such wherever it lands, so a human can tell it apart from what a clinician entered.

Next: Wallets and browser support · Writing FHIR